如何使用 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
,您可以使用split
、gensub
和strtonum
函数来做到这一点:
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
循环使用subj
和seps
数组元素返回$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) 用非转义等效项替换所有转义序列的主要内容,如果未能解决你的问题,请参考以下文章