【问题标题】:Adding Log Statements To Beginning and Ending of All Functions By ShellShell 将日志语句添加到所有函数的开头和结尾
【发布时间】:2015-02-04 23:20:43
【问题描述】:

我有一个项目,它有 1000 多个源 c++ 文件存储在同一目录下的不同文件夹中。我想将日志代码添加到这些 .cpp 文件中定义的所有函数的开头和结尾。请看下面的例子:

    //SomeSrcFile.cpp      
    //Sample  
    ReturnType SomeClass::SomeFunc1(InputParameters)        
    {
         ......
    }

    ReturnType SomeClass::SomeFunc2(InputParameters) {
         ......
    }

输出

    ReturnType SomeClass::SomeFunc1(InputParameters)     
    {
         char FuncName[] = "SomeClass::SomeFunc1()";      //line added 
         printf("%s begins\n", FuncName);      //line added 

         ......

         printf("%s ends\n", FuncName);        //line added 
    } 

    ReturnType SomeClass::SomeFunc2(InputParameters) {
         char FuncName[] = "SomeClass::SomeFunc2()";      //line added 
         printf("%s begins\n", FuncName);      //line added 

         ......

         printf("%s ends\n", FuncName);        //line added
    }      

如何为这种工作编写shell脚本?这里可以用awk吗?

更新

  1. 我认为 AWK 可以像 This Post 那样做到这一点。但我还没想好如何以我的方式去做。

  2. 我只想使用 Bash 或 Python 来执行此操作,因为它们只是我现在熟悉的工具。

更新 2
也许这项工作真的很辛苦,出乎我的意料。但是,如果我不关心准确性怎么办?如果我不关心函数包含在 cmets 等中的情况怎么办?没有简单的方法吗?

【问题讨论】:

  • 请问您想要完成什么?可能有比日志语句更有用的东西。
  • 这样的程序通常也需要解析语法(这不是直接写出来的)。例如,下面的字符串定义char greeting[] = "void SomeFunc1"; 可能会使简单的解析器误认为这是一个真正函数的开始。而且 cmets 可能包含会混淆解析器的字符串,所以它也需要解析 cmets...
  • @HåkonHægland 是对的,如果没有 C 解析器,您将无法可靠地完成这项工作。例如,当 shell 脚本看到 void SomeFunc2(...) { 时,它如何知道它是否位于 C 样式的注释分隔符内?
  • 像这样调试打印语句是个坏主意。但是,如果您要这样做,请考虑像 PRINT("%s begins\n", __func__) 这样的事情,您可以使用预处理器通过 __func__ 获取函数名称,然后定义宏 PRINT,您可以取消定义以删除打印语句。
  • 不要修改你的代码,valgrind --tool=callgrind <program> ... 怎么样。虽然我没有使用它们,但似乎有用于 callgrind output format 的可视化工具,以使其密集格式更易于理解。 KCachegrind 在快速搜索中首先出现。

标签: python linux bash shell awk


【解决方案1】:

参考你贴的帖子,代码如下:

awk 'BEGIN{X=FS}
    { if ($0~/void/ && $0 ~/\(/) split($0,a,FS);split(a[2],b,"(")
      FS="";OFS="";
         for (i=1; i<=NF; i++)
             if ($i == "{") {
                 if (++d == "1") $i=sprintf("{\n\tchar FuncName[] = \"%s()\";\n\tprintf(\"%%s begins\\n\", FuncName);\n",b[1]);
             } else {if ($i == "}") {
                 if (d-- == "1") $i=sprintf("\n\tprintf(\"%%s end\\n\", FuncName);\n\t} ",b[1]);
               }
             } 
       FS=X;OFS=X
     }1' infile.cpp

注意事项:

关键字是无效的,如果定义的函数用其他关键字可以调整

$0~/void/

$0~/(void/int/string)/

【讨论】:

  • 这会将// cats avoid dogs (and wolves) 之类的注释行视为函数定义,并且识别函数返回类型的关键字的方法永远无法用于一般代码,因为函数可以返回用户定义的类型。另外你不用说$0~/void//void/ 是一回事。
  • 感谢您的建议,既然您发布了复杂的,我把我的留给其他人,也许它在某些阶段有用。
【解决方案2】:

如果您不介意它不健壮,这将满足您对简单一致情况的要求,使用 GNU awk 作为第 3 个参数来 match() 和字符类的缩写(例如 \w):

$ cat tst.awk
BEGIN {
    beg = "\tchar FuncName[] = \"%s()\";\n\tprintf(\"%%s begins\\n\", FuncName);\n"
    end = "\n\tprintf(\"%%s ends\\n\", FuncName);"
}
match($0,/^\s*\w+\s+(\w+::\w+)[(][^)]*[)]/,arr) { funcName  = arr[1] }
/{/ && (++braceCnt == 1) { $0 = $0 ORS sprintf(beg,funcName) }
/}/ && (--braceCnt == 0) { $0 = sprintf(end,funcName) ORS $0 }
{ print }

.

$ awk -f tst.awk file
    //SomeSrcFile.cpp
    //Sample
    ReturnType SomeClass::SomeFunc1(InputParameters)
    {
        char FuncName[] = "SomeClass::SomeFunc1()";
        printf("%s begins\n", FuncName);

         ......

        printf("%s ends\n", FuncName);
    }

    ReturnType SomeClass::SomeFunc2(InputParameters) {
        char FuncName[] = "SomeClass::SomeFunc2()";
        printf("%s begins\n", FuncName);

         ......

        printf("%s ends\n", FuncName);
    }

对于其他 awk,只需在正则表达式中使用 [[:space:]] 而不是 \s[[:alnum:]_] 而不是 \w 并使用 match()substr() 和/或 sub()s 的组合来提取与正则表达式匹配的字符串中的函数名,例如:

$ cat tst2.awk
BEGIN {
    beg = "\tchar FuncName[] = \"%s()\";\n\tprintf(\"%%s begins\\n\", FuncName);\n"
    end = "\n\tprintf(\"%%s ends\\n\", FuncName);"
}
/^[[:space:]]*[[:alnum:]_]+[[:space:]]+([[:alnum:]_]+::[[:alnum:]_]+)[(][^)]*[)]/ {
    funcName = $0
    gsub(/^[[:space:]]*[[:alnum:]_]+[[:space:]]+|[(][^)]*[)].*/,"",funcName)
}
/{/ && (++braceCnt == 1) { $0 = $0 ORS sprintf(beg,funcName) }
/}/ && (--braceCnt == 0) { $0 = sprintf(end,funcName) ORS $0 }
{ print }

【讨论】:

    【解决方案3】:

    我建议在您的 C++ 中使用一些不同的方法。

    这只会稍微简化您的 shell 脚本需求,但会产生更好的 C++ 代码。

    简而言之,我建议创建一个 Trace 类和一组宏来部署它。这有很多优点:

    • 易于添加、删除、更改、打印输出。
    • 自动处理文件名、函数名、行号等。
    • 无论函数在哪里退出都可以工作(这是对您的方法的重大改进)

    创建一个 Trace 类,它接受函数名、文件名、行号等参数 - 任何您希望能够打印的内容。每当构造一个 Trace 类时,它都会打印一个函数入口消息。每当 Trace 类超出范围并被破坏时,它将打印一个函数退出消息。

    所以这是类声明:

    // File: Trace.h
    
    class Trace                                                                                                                  
    {
    public:
       // print function entry message
       Trace(const char *name,                                                                                                   
             const char *file,                                                                                                   
             const int line, 
             int traceOn = -1, // used for enabling/disabling Trace printing 
                               // within a particular call chain.
             const void* thisPtr = 0);                                                                                           
    
       // print function exit message
       ~Trace();                                                                                                                 
    
       static bool active;                                                                                                       
    
    private:
       std::string* theFunctionName;                                                                                             
       std::string* theFileName;
       int          theLineNumber;                                                                                               
       const void*  theThisPtr;                                                                                                  
       static int   layer; // used for printout indenting
       static std::list<clock_t> startTimeList; // used for printing timing information                                                                                 
       bool         previouslyActive;                                                                                            
    };
    

    现在让我们看一下对应的宏(这些也在Trace.h中):

    #ifdef TRACE_ENABLED // define the real macros
    #undef TRACE_ENABLED // must be re #defined preceding each '#include "Trace.h" '    
    #define TRACE__     Trace tr_( __FUNCTION__ , __FILE__ , __LINE__, -1, this );                                             
    #define TRACE_OFF__ Trace tr_off_(__FUNCTION__ , __FILE__ , __LINE__, false);                                              
    #define TRACE_ON__  Trace tr_on_(  __FUNCTION__ , __FILE__ , __LINE__, true);                                              
    
    #else // DUMMY MACROS: if TRACE_ENABLED is not defined.                                                                      
    
    #define TRACE__
    #define TRACE_ON__
    #define TRACE_OFF__                                                                                                        
    
    #endif        
    

    现在,要使用 Trace 类,您需要执行以下操作:

    //SomeSrcFile.cpp      
    //Sample  
    
    #define TRACE_ENABLED // comment this out to disable tracing in this file.
    #include "Trace.h"
    
    ReturnType SomeClass::SomeFunc1(InputParameters)        
    {
         TRACE__
         ......
    }
    
    ReturnType SomeClass::SomeFunc2(InputParameters) {
         TRACE__
         ......
    }
    

    现在,Trace 类的潜在实现: // 文件:Trace.C

    #include "Trace.h"
    
    bool Trace::active = true;
    int  Trace::layer = 0;
    std::list<clock_t> Trace::startTimeList;
    
    Trace::Trace(const char *name, 
                 const char *file, 
                 const int line, 
                 int traceOn,  // default: -1
                 const void* thisPtr) // default:  0 
    : theFunctionName(0),
      theFileName(0),
      theLineNumber(0),
      theThisPtr(thisPtr),
      previouslyActive(active)
    {
       if (active)
       {
          theFunctionName = new std::string(name);
          theFileName     = new std::string(file);
          theLineNumber   = line;
    
          std::cout<<"---> Trace: ";
          for( int L = 0; L < layer; ++L )
          {
             std::cout<<"   ";
          }
          std::cout
             <<"{ Entered "
             << *theFileName << ":" 
             << theLineNumber << " " 
             << *theFunctionName << "()";
    
          if(thisPtr > 0)
          {
             std::cout<<"  this = "<< thisPtr;
          }
          std::cout<< std::endl;
    
          layer++;
          startTimeList.push_back(clock());
       }
    
       if(traceOn >= 0)
       {
          active = traceOn;
          if (active && !previouslyActive)
          {
             std::cout<<"+++] Trace: Enabled"<<std::endl;
          }
          else if (previouslyActive && !active)
          {
             std::cout<<"+++[ Trace: Disabled"<<std::endl;
          }
       }
    }
    
    Trace::~Trace()
    {
       if (previouslyActive && !active)
       {
          std::cout<<"+++] Trace: Enabled"<<std::endl;
       }
       else if (active && !previouslyActive)
       {
          std::cout<<"+++[ Trace: Disabled"<<std::endl;
       }
    
       active = previouslyActive;
    
       if (active || theFunctionName)
       {
          layer--;
          double startTime = static_cast<double>(startTimeList.back());
          startTimeList.pop_back();
          double endTime = static_cast<double>(clock());
          double elapsedTime = (endTime-startTime)/CLOCKS_PER_SEC;
    
          std::cout<<"<--- Trace: ";
          for( int L = 0; L < layer; ++L )
          {
             std::cout<<"   ";
          }
    
          std::cout<<"} Leaving "
                   << *theFileName << ":" 
                   << theLineNumber <<" "
                   << *theFunctionName <<"()";
          if(theThisPtr > 0)
          {
             std::cout<<"  this = "<< theThisPtr;
          }
    
          std::cout<<"  elapsedTime = "<<elapsedTime<<"s";
          std::cout<< std::endl;
    
          delete theFunctionName;
          delete theFileName;
       }
    }
    

    【讨论】:

    • 感谢@EdMorton。我删除了领先的__。我假设你是说这条规则适用于 C 预处理器,对吧?
    • 你的回答没有解决修改子目录下1000个c++源文件的问题。
    • @martineau:是的,正如我的“答案”中所述:“这只会稍微简化您的 shell 脚本需求。”所以不,它实际上并没有回答这个问题。我认为它重新引导问题以寻求更好的最终结果。通常,我认为这类事情应该是评论而不是答案,但我当然无法将所有代码都放入评论中。
    • 您的回答大多只是对“如果您的 1000 个 C++ 代码文件以这种方式编写,您就不会遇到问题”的评论。就像他们说的那样,事后诸葛亮通常是 20/20...
    • @martineau:实际上,我的回答是评论说:“您将尝试使用类似 awk 的脚本来做某事,我认为您应该用它来做其他事情。”我是说提出问题的人应该添加一个宏而不是一对打印语句。这与现有代码无关。它的作用是让 awk 任务更容易一些(每个函数体只添加一个词),并获得更有用的最终结果。
    猜你喜欢
    • 2022-12-11
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    • 2021-11-14
    • 2011-10-29
    • 1970-01-01
    • 1970-01-01
    • 2011-03-05
    相关资源
    最近更新 更多