【问题标题】:Need SED or AWK script to do strlen optimization需要 SED 或 AWK 脚本来做 strlen 优化
【发布时间】:2013-04-01 14:11:34
【问题描述】:

我只需要一点帮助,因为我很少接触 sed 或 awk。我正在尝试替换

String1.append("Hello");   // regexp to find this is: \w*\.append\(".*"\) 

String1.append("Hello", 5);  // note it has to figure out the length of "Hello"

我需要在数十万个文件中进行搜索和替换。而“你好可以是任何东西......包括“\n\n\n”,它应该是 3 而不是 6。示例:

s.append("\n\n\n");  ---> s.append("\n\n\n", 3);

提前感谢您的帮助...我想我需要 awk 来执行此操作,所以我现在正在阅读有关 awk 基础知识的教程...

【问题讨论】:

  • 目标语言是Java,对吧?如果是 C,您可能会查看 Coccinelle。也许 Java 也存在类似的情况。
  • 那么s.append(foo) 会发生什么,还是永远不会发生?
  • 目标语言是 C++。这是我试图添加此优化的一堆 .cpp 文件。 @Idav1s:你的意思是 foo 是一个变量吗?这就是我的正则表达式查找带引号的静态字符串的原因。
  • 刚看了一下 Coccinelle,这对我正在尝试做的事情来说太过分了。我想要一个使用正则表达式的简单命令行脚本。 (我的意思是我可以在 C++ 中做到这一点,但它确实不是这项工作的最佳工具。)
  • 除了将“\n”计为换行符之外,这很简单,因为“\n”的含义取决于上下文。如果仅打印为“\n”,则它是一个换行符,但作为“\\n”的一部分,它是一个反斜杠,后跟字母“n”。

标签: regex sed awk


【解决方案1】:

由于您想在一些包含代码的文件上运行它,下面是一个完整功能的示例:

$ cat file
foo() {
   String1.append("Hello");
   if (bar) {
      s.append("\n\n\n");
   }
   else {
      s.append("\n\\n\n\\\n");
   }
}
$
$ cat tst.awk
match($0,/[[:alnum:]_]+\.append\(".*"\)/) {
    split(substr($0,RSTART,RLENGTH), orig, /"/)

    head = substr($0,1,RSTART-1) orig[1]
    tail = orig[3] substr($0,RSTART+RLENGTH)

    tgt = orig[2]
    gsub(/[\\][\\]/,"X",tgt)
    gsub(/[\\]/,"",tgt)

    $0 = sprintf("%s\"%s\", %d%s", head, orig[2], length(tgt), tail)
}
{ print }
$
$ awk -f tst.awk file
foo() {
   String1.append("Hello", 5);
   if (bar) {
      s.append("\n\n\n", 3);
   }
   else {
      s.append("\n\\n\n\\\n", 6);
   }
}

为了可移植性,我将原始发布问题中示例中的“\w”替换为 POSIX 等效的“[[:alnum:]_]”。 "\w" 可以与 GNU awk 和其他一些工具一起使用,但不是所有工具和所有 awk。

【讨论】:

  • 我在使用这个脚本时遇到了一些麻烦,因为它似乎改变了文件中的很多空白(不仅仅是它附加的行)。你知道怎么解决吗?
【解决方案2】:

既然是 C++,你有没有考虑过使用预处理器?此外,您可以考虑使用sizeof 运算符,而不是自己计算每个字符串的长度。

#define append(x) appendSize(x, sizeof(x))

当然,这假定传递给append 的“x”始终是字符串文字(但是,如果不是,那么查找和替换脚本也将不起作用)。

【讨论】:

  • +1 用于 sizeof(),如果这是 C++(从发布的代码来看,它也可能是其他语言)。我可能仍然会进行搜索/替换,而不是仅仅为了代码清晰而引入宏。实际上,如果这是 C++,你不能只修改它定义中的方法,以便 append(foo) 调用 append(foo, strlen(foo))?
  • @Dan:你是什么意思“没有查找和替换脚本也可以工作?”这正是这个问题所要问的,使用正则表达式很容易只找到字符串文字并应用优化。
  • @Ed: 当然...... std::string 已经有一个标准的 append(const string&) 方法。这是一个专门针对 AVOID strlen 的优化! :)
  • @mrjustinmooser 我的意思是查找和替换脚本只有在 append 的参数始终是字符串文字而不是变量时才有效。例如,如何让 sed、awk 或 perl 脚本找出这行代码 String1.append(foo) 的字符串长度?它无法完成,因为foo 的长度在编译时是未知的。如果您确实选择使用查找和替换脚本,我仍然建议使用sizeof 而不是让脚本计算字符串长度。它不太容易出错(它不会被字符串中的转义字符绊倒)。
  • @EdMorton 我不确定append("foo") 是否不如append("foo", 3)append("foo", sizeof("foo") - 1) 清晰。特别是如果append 被大量使用,听起来确实如此。
【解决方案3】:

这可能更适合作为评论,但更难显示信息。在我看来,尝试通过修改源来优化字符串长度可能不是最好的解决方案。也许有一个很好的理由,但是把它留给编译器可能是一个更好的解决方案(而且更简单)。根据编译器和选项,甚至可能不使用 strlen() 调用。编译器可以计算出常量字符串的长度。例如,考虑一下:

int main(int argc, char** argv)
{
   string s = argv[1];
   cout << s << endl;
   s.append( " stuff" );
   cout << s << endl;
   return 0;
}

使用 -O (g++ -O file.c) 编译时,汇编的相关位是:

400ad2:       ba 06 00 00 00          mov    $0x6,%edx
400ad7:       be 6c 0c 40 00          mov    $0x400c6c,%esi
400adc:       48 89 e7                mov    %rsp,%rdi
400adf:       e8 0c fe ff ff          callq  4008f0 <_ZNSs6appendEPKcm@plt>

注意第一条mov 指令。它的长度 6 已经算出来了。

-O2 与 Microsoft 编译器 (v16.00.40219.01) 一起使用会产生类似的结果:

0000005C: 6A 06              push        6
0000005E: 68 00 00 00 00     push        offset ??_C@_06PNGALGA@?5stuff?$AA@
00000063: 8D 4C 24 0C        lea         ecx,[esp+0Ch]
00000067: E8 00 00 00 00     call        ?append@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAEAAV12@PBDI@Z

【讨论】:

  • 嗨,马克,虽然编译器当然知道它的长度为 6,但它怎么能足够聪明地调用正确的“追加”方法(不调用 strlen 的方法?)你假设我'正在使用 std::string?不幸的是,我坚持使用实现 std::string 接口的第 3 方字符串(就附加方法而言)
  • 即使使用第 3 方库,您也可以在调用中使用 strlen()。许多(大多数?)现代编译器会将硬编码的strlen( "abcde" ) 替换为常量5
  • 你确定吗?我知道 String.Append("Foo", strlen("Foo")) 会做你所说的......但听起来你在说 String.Append("Foo") 虽然它在内部调用 strlen( ) 编译器足够聪明,可以用 3 替换它吗?我对此表示怀疑。
  • 否 - 如果将字符串传递给另一个方法,那么编译器将无法进行该优化。如果它是一个宏,那么它可以。
【解决方案4】:

有人请改进这个解决方案:

x='String1.append("Hello");'
len=`echo $x | sed 's/\\//g' | sed 's/\w*\.append("\(.*\)");/\1/' | awk '{print length($0)}'`
echo $x | sed "s/\(\w*\.append(\".*\"\)\(.*\)/\1,$len\2/"

这似乎解决了原来的问题,但多行。

【讨论】:

  • 谢谢,这让我走上了正轨。我最初正在努力将输入字符串输入 awk,因此我可以对其进行 length()。
  • 有一个问题... echo $x | sed 's/\w*\.append("(.*)");/\1/' 返回“shello”,因此 length($0) 返回 6 而不是 5。
  • 试图找出更多关于“/\1/”的东西。我不知道在谷歌中搜索什么来确定是否有办法抓住引号之间的部分。
  • 反向引用,酷!所以我尝试做这样的事情: echo $x | sed 's/\w*\.append("((.*))");/\1/' (注意 .* 周围的额外括号以创建反向引用。然后我尝试使用 \1 引用反向引用. 但这不起作用。只输出整个匹配项,而不是引号之间的部分。
  • 我已经在 bash 上对其进行了测试,它确实有效。可能您应该尝试单独运行管道组件以定位问题。
【解决方案5】:

perl 赞!

x='String1.append("Hello");'
echo $x | perl -pe 's/(\w*\.append\(\")(.*)(\"\);)/my($len)=length($2); $_="$1$2, ${len}$3";/e'

【讨论】:

  • 它不会为“\n\n\n”示例输入或我添加到答案中的其他情况产生预期的输出。
  • perl -pe 's/(\w*\.append\(\")(.*)(\")(\);)/my($len)=length(eval("qq{$2}")); $_="$1$2$3, ${len}$4";/e'
  • @BradGilbert awww,我的 perl fu 似乎很弱。
猜你喜欢
  • 2015-10-12
  • 2020-05-15
  • 2012-09-05
  • 2015-04-24
  • 1970-01-01
  • 1970-01-01
  • 2012-10-19
  • 2019-01-11
  • 2017-04-23
相关资源
最近更新 更多