如何使用 unix 实用程序 (sed/tr/awk) 用非转义等效项替换所有转义序列

Posted

技术标签:

【中文标题】如何使用 unix 实用程序 (sed/tr/awk) 用非转义等效项替换所有转义序列【英文标题】:How to replace all escape sequences with non-escaped equivalent with unix utilities (sed/tr/awk) 【发布时间】:2021-10-24 03:20:56 【问题描述】:

我正在为显示过滤器处理一个 Wireshark 配置文件 (dfilter_buttons),并希望打印出给定名称的过滤器。文件内容如下:

示例输入

"TRUE","test","sip contains \x22Hello, world\x5cx22\x22",""

结果输出应该替换掉转义序列,所以我可以稍后在我的脚本中使用它们:

期望的输出

sip contains "Hello, world\x22"

我的第一关是这样的:

当前解析器

filter_name=test
awk -v filter_name="$filter_name" 'BEGIN FS="\",\"" ($2 == filter_name) print $3' "$config_file"

我的输出是这样的:

电流输出

sip contains \x22Hello, world\x5cx22\x22

我知道我可以通过管道传输到 sed 并匹配这两个确切的序列来处理这两个确切的转义序列,但是有没有一种通用的方法来替换所有的转义序列?我构建的未来过滤器可能会使用更多的转义序列,而不仅仅是 " 和 ,并且我想处理未来的场景。

【问题讨论】:

printf 'sip contains \x22Hello, world\x5cx22\x22\n' 似乎是一个不错的起点。 (虽然我相信解析 \x22 的是内置的 bash,而且有些 printf 不处理十六进制) 【参考方案1】:

使用gnu-awk,您可以使用splitgensubstrtonum 函数来做到这一点:

awk -F '","' -v filt='test' '$2 == filt n = split($3, subj, /\\x[0-9a-fA-F]2/, seps); for (i=1; i<n; ++i) printf "%s%c", subj[i], strtonum("0" substr(seps[i], 2)); print subj[i]' file

sip contains "Hello, world\x22"

更易读的形式:

awk -F '","' -v filt='test' '
$2 == filt 
   n = split($3, subj, /\\x[0-9a-fA-F]2/, seps)
   for (i=1; i<n; ++i)
      printf "%s%c", subj[i], strtonum("0" substr(seps[i], 2))
   print subj[i]
' file

说明:

使用-F '","',我们使用分隔符","分割输入 $2 == filt 我们为$2 == "test" 条件过滤输入 使用 /\\x[0-9a-fA-F]2/ 作为正则表达式(匹配 2 位十六进制字符串)我们拆分 $3 并将拆分标记保存到数组 subj 并将匹配的分隔符保存到数组 seps 使用substr,我们删除第一个字符,即\\,并在前面加上0 使用strtonum我们将十六进制字符串转换为等效的ascii数字 在printf 中使用%c 打印对应的ascii 字符 最后一个for 循环使用subjseps 数组元素返回$3

【讨论】:

空字符从何而来? @RustyLemur: seps 数组的长度将比subj 数组少一个,这就是最后一个 NUL 字符的原因。我现在已经修好了。 @RustyLemur 仅供参考 null 是一个字符串,NUL 是一个字符。【参考方案2】:

将 GNU awk 用于 FPAT、gensub()、strtonum() 和 match() 的第三个参数:

$ cat tst.awk
BEGIN  FPAT="([^,]*)|(\"[^\"]*\")"; OFS="," 
$2 == ("\"" filter_name "\"") 
    gsub(/^"|"$/,"",$3)
    while ( match($3,/(\\x[0-9a-fA-F]2)(.*)/,a) ) 
        printf "%s%c", substr($3,1,RSTART-1), strtonum(gensub(/./,0,1,a[1]))
        $3 = a[2]
    
    print $3

$ awk -v filter_name='test' -f tst.awk file
sip contains "Hello, world\x22"

以上假设您的转义序列始终是\x,后跟正好 2 个十六进制数字。它隔离输入中的每个\xHH 字符串,将\ 替换为该字符串中的0,以便strtonum() 可以将字符串转换为数字,然后在printf 格式化字符串中使用%c 进行转换该数字转换为一个字符。

请注意,GNU awk 有一个调试器(请参阅https://www.gnu.org/software/gawk/manual/gawk.html#Debugger),因此如果您不确定程序的任何部分是做什么的,您可以在调试器(-D)中运行它并跟踪它,例如在下面我设置一个断点来告诉 awk 在脚本的第 1 行 (b 1) 停止,然后开始运行 (r) 和步骤 (s) 通过脚本打印 $3 的值 (@ 987654335@) 在每一行,所以我可以看到它在gsub() 之后的变化:

$ awk -D -v filter_name='test' -f tst.awk file
gawk> b 1
Breakpoint 1 set at file `tst.awk', line 1
gawk> r
Starting program:
Stopping in BEGIN ...
Breakpoint 1, main() at `tst.awk':1
1       BEGIN  FPAT="([^,]*)|(\"[^\"]*\")"; OFS="," 
gawk> p $3
$3 = uninitialized field
gawk> s
Stopping in Rule ...
2       $2 == "\"" filter_name "\"" 
gawk> p $3
$3 = "\"sip contains \\x22Hello, world\\x5cx22\\x22\""
gawk> s
3           gsub(/^"|"$/,"",$3)
gawk> p $3
$3 = "\"sip contains \\x22Hello, world\\x5cx22\\x22\""
gawk> s
4           while ( match($3,/(\\x[0-9a-fA-F]2)(.*)/,a) ) 
gawk> p $3
$3 = "sip contains \\x22Hello, world\\x5cx22\\x22"

【讨论】:

gsub 是干什么用的? 删除第三个,分隔字段"sip contains \x22Hello, world\x5cx22\x22"的字符串开头和结尾的引号,因为您不希望它在输出中被引用。您可以将print 语句添加到脚本中,以查看字段、变量等在对其进行任何操作之前和之后的值,以了解它们是什么以及它们发生了什么。 明白了,谢谢!我弄错了“|”作为文字管道字符,而不是逻辑或分隔符。 @RustyLemur 我添加了一些说明来展示如何使用 gawk 调试器在执行脚本时查看变量的值。

以上是关于如何使用 unix 实用程序 (sed/tr/awk) 用非转义等效项替换所有转义序列的主要内容,如果未能解决你的问题,请参考以下文章

是否有 Unix 实用程序可以将时间戳添加到标准输入?

unix 文件实用程序:魔术语法

用于替换某些字符的unix实用程序[重复]

一套有用的 Unix 实用程序 | Linux 中国

sh shell脚本函数和unix实用程序

sh shell脚本函数和unix实用程序