【问题标题】:Replace strings with evaluated string based on matched group (elegant way, not using for .. in)用基于匹配组的评估字符串替换字符串(优雅的方式,不使用 for .. in)
【发布时间】:2012-12-31 14:01:25
【问题描述】:

我正在寻找一种方法来用另一个字符串替换由正则表达式匹配的文件字符串,该字符串将从匹配的字符串中生成/评估。

比如我想替换这个文件中的时间戳(timestamp + duration)

1357222500 3600 ...
Maybe intermediate strings...
1357226100 3600 ...
Maybe intermediate strings...
...

通过人类可读的日期表示(日期范围)。

直到现在,我一直使用像 Bash 这样的 shell 脚本来遍历每一行,匹配行 X,获取匹配的组字符串并在处理后打印该行,例如这种方式(从内存中):

IFS="
"
for L in `cat file.txt`; do
  if [[ "${L}" =~ ^([0-9]{1,10})\ ([0-9]{1,4})\ .*$ ]]; then
    # Written as three lines for better readability/recognition
    echo -n "`date --date=@${BASH_REMATCH[1]}` - "
    echo -n "`date --date=@$(( ${BASH_REMATCH[1]} + ${BASH_REMATCH[2]} ))`"
    echo ""
  else
    echo "$L"
  fi
done

我想知道虚构的(?)“sed-2.0”是否有类似的东西:

cat file.txt | sed-2.0 's+/^\([0-9]\{1,10\}\) \([0-9]\{1,4\}\) .*$+`date --date="@\1"` - `date --date="@$(( \1 + \2 ))`'

而 sed-2.0 替换中的反引号将被评估为传递匹配组 \1\2 的 shell 命令。

我知道这不会按预期工作,但我想写这样的东西。

编辑 1

编辑上述问题:在 Bash 脚本示例的 if 中添加了缺少的 echo ""

这应该是预期的输出:

Do 3. Jan 15:15:00 CET 2013 - Do 3. Jan 16:15:00 CET 2013
Maybe intermediate strings...
Do 3. Jan 16:15:00 CET 2013 - Do 3. Jan 17:15:00 CET 2013
Maybe intermediate strings...
...

请注意,时间戳取决于时区。

编辑 2

编辑上述问题: 修复了 Bash 脚本示例的语法错误,添加了注释。

编辑 3

编辑上述问题: 修复了 Bash 脚本示例的语法错误。将短语“old-school example”更改为“Bash script example”。


Kentglenn jackman 的回答总结

这两种方法有很大的不同:执行时间。我已经比较了所有四种方法,结果如下:

gawk 使用strftime()

/usr/bin/time gawk '/^[0-9]+ [0-9]+ / {t1=$1; $1=strftime("%c -",t1); $2=strftime("%c",t1+$2)} 1' /tmp/test
...
0.06user 0.12system 0:00.30elapsed 60%CPU (0avgtext+0avgdata 1148maxresident)k
0inputs+0outputs (0major+327minor)pagefaults 0swaps

gawk 通过getline (Gnu AWK Manual) 使用执行

/usr/bin/time gawk '/^[0-9]{1,10} [0-9]{1,4}/{l=$1+$2; "date --date=@"$1|getline d1; "date --date=@"l|getline d2;print d1" - "d2;next;}1' /tmp/test
...
1.89user 7.59system 0:10.34elapsed 91%CPU (0avgtext+0avgdata 5376maxresident)k
0inputs+0outputs (0major+557419minor)pagefaults 0swaps

自定义 Bash 脚本

./sed-2.0.sh /tmp/test
...
3.98user 10.33system 0:15.41elapsed 92%CPU (0avgtext+0avgdata 1536maxresident)k
0inputs+0outputs (0major+759829minor)pagefaults 0swaps

sed 使用e 选项

/usr/bin/time sed -r 's#^([0-9]{1,10}) ([0-9]{1,4})(.*$)#echo $(date --date=@\1 )" - "$(date --date=@$((\1+\2)))#ge' /tmp/test
...
3.88user 16.76system 0:21.89elapsed 94%CPU (0avgtext+0avgdata 1272maxresident)k
0inputs+0outputs (0major+1253409minor)pagefaults 0swaps

输入数据

for N in `seq 1 1000`; do echo -e "$(( 1357226100 + ( $N * 3600 ) )) 3600 ...\nSomething else ..." >> /tmp/test ; done

我们可以看到使用strffime() 方法的AWK 是最快的。但即使是 Bash 脚本也比使用 shell 执行的 sed 更快。

Kent 向我们展示了一种更通用、更通用的方式来完成我的要求。我的问题实际上不仅限于我的时间戳示例。在这种情况下,我必须这样做(用人类可读的日期表示替换时间戳 + 持续时间),但我遇到了必须执行其他代码的情况。

glenn jackman 向我们展示了一个特定的解决方案,它适用于您可以直接在 AWK 中进行字符串操作和计算的情况。

因此,这取决于您拥有的时间(或您的脚本可能运行的时间)、数据量以及应首选哪种方法的用例。

【问题讨论】:

  • 你有 gnu sed 吗?你能添加一个预期的输出吗?
  • 是的,Gnu sed 和 perl 都可用。添加了预期的输出。
  • 旁注:使用$(< file) 而不是$(cat file)。见here

标签: linux shell unix sed awk


【解决方案1】:

根据您的示例输入:

gawk '/^[0-9]+ [0-9]+ / {t1=$1; $1=strftime("%c -",t1); $2=strftime("%c",t1+$2)} 1'

输出

Thu 03 Jan 2013 09:15:00 AM EST - Thu 03 Jan 2013 10:15:00 AM EST ...
Maybe intermediate strings...
Thu 03 Jan 2013 10:15:00 AM EST - Thu 03 Jan 2013 11:15:00 AM EST ...
Maybe intermediate strings...
...

【讨论】:

  • 在我的例子中的情况下 +1 strftime。事实上,它的速度比执行 shell 快。时间戳只是一个例子,我也遇到过必须执行外部程序或做更复杂工作的情况。我将在我的帖子中添加一个“讨论”部分,显示 awk+strftime、带有e 选项的 sed 和 bash 脚本之间的区别。
【解决方案2】:

awk oneliner:(日期时间格式可能与您的输出不同)

awk '/^[0-9]{1,10} [0-9]{1,4}/{l=$1+$2; "date --date=@"$1|getline d1; "date --date=@"l|getline d2;print d1" - "d2;next;}1' file

测试:

kent$  echo "1357222500 3600 ...
Maybe intermediate strings...
1357226100 3600 ...
Maybe intermediate strings...
..."|awk '/^[0-9]{1,10} [0-9]{1,4}/{l=$1+$2; "date --date=@"$1|getline d1; "date --date=@"l|getline d2;print d1" - "d2;next;}1'    
Thu Jan  3 15:15:00 CET 2013 - Thu Jan  3 16:15:00 CET 2013
Maybe intermediate strings...
Thu Jan  3 15:15:00 CET 2013 - Thu Jan  3 17:15:00 CET 2013
Maybe intermediate strings...
...

Gnu sed

如果您有 gnu sed,则通过应用 gnu sed 的s/foo/shell cmds/ge ,您的“不工作” sed 行的想法可以在现实世界中工作,见下文:

sed -r 's#^([0-9]{1,10}) ([0-9]{1,4})(.*$)#echo $(date --date=@\1 )" - "$(date --date=@$((\1+\2)))#ge'  file

测试

kent$  echo "1357222500 3600 ...
Maybe intermediate strings...
1357226100 3600 ...
Maybe intermediate strings...
..."|sed -r 's#^([0-9]{1,10}) ([0-9]{1,4})(.*$)#echo $(date --date=@\1 )" - "$(date --date=@$((\1+\2)))#ge'                                                                 
Thu Jan 3 15:15:00 CET 2013 - Thu Jan 3 16:15:00 CET 2013
Maybe intermediate strings...
Thu Jan 3 16:15:00 CET 2013 - Thu Jan 3 17:15:00 CET 2013
Maybe intermediate strings...
...

如果我愿意解决这个问题,我个人会选择 awk。因为它简单易写。

最后我粘贴我的 sed/awk 版本信息:

kent$  sed --version|head -1
sed (GNU sed) 4.2.2

kent$  awk -V|head -1
GNU Awk 4.0.1

【讨论】:

  • awk 示例看起来不错,但不适用于 gawk 3.1.7。
  • 好的,我明白了,它需要 gawk 4.0 吗?
  • @try-catch-finally 将--re-interval 选项添加到您的 awk 行并重试。顺便说一句,我更新了答案并添加了 sed 解决方案。
  • 太好了,--re-interval gawk 示例也适用于 3.1.7。我更喜欢 sed 变体,我认为它更优雅。我不知道e 选项。精彩的。如果可以的话,我会给你的答案投票 10 次有用。 ;)
  • @try-catch-finally Gern geschehen! (我在您的输出中看到了语言环境)^_*
猜你喜欢
  • 2011-04-25
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-07
  • 1970-01-01
  • 2015-10-12
  • 2022-01-19
相关资源
最近更新 更多