问题引出
最近,在写调试脚本的时候有这样的需求:
QNX 的 gdb 可以通过 info meminfo 得到当前进程的内存映射信息。
下面是一个例子:
/proc/boot/img_codec_tga.so
text=00003000 bytes @ 0x008d4000
flags=00010571
debug=00000000
offset=0000000001104000
data=00001000 bytes @ 0x00ad7000
flags=00010372
debug=00203000
offset=0000000001107000
dev=0x802
ino=0xc0000078
/proc/boot/img_codec_sgi.so
text=00002000 bytes @ 0x00ad8000
flags=00010571
debug=00000000
offset=0000000001100000
data=00001000 bytes @ 0x00cda000
flags=00010372
debug=00202000
offset=0000000001102000
dev=0x802
ino=0xc0000077
而 QNX 的 gdb 不支持自动加载符号,所以我想使用脚本把这段文本输出到文件中,
然后处理成 add-symbol-file libxxx.so 0x?????? 的形式,方便我的调试。
显然,持变量、分支、循环的 awk 比较适合做这件事,大致处理逻辑也很简单,读到
/proc/boot/* 的时候把他放在变量 file 里面,等到读到 text= 的时候,
再一并输出成 add-symbol-file 的形式就行,我们很容易写出一个这样的 awk 脚本。
#!/usr/bin/env -S awk -f
BEGIN {
printf "#!/usr/bin/env -S gdb -q -x\n\n"
printf "target remote localhost:1234\n\n"
prefix = ENVIRON["QNX_TARGET"] "/x86_64/lib/dll/"
}
/^\/proc\/boot\/img_codec_/ {
file = $0
file = gensub(/.*(img_codec_.+)/, prefix "\\1.sym", "g", file)
}
/^\s+text=/ {
split($0, a, "@ ")
text_addr = a[2]
printf "add-symbol-file %s %s\n", file, text_addr
}
该脚本的输出也很漂亮
#!/usr/bin/env -S gdb -q -x
target remote localhost:1234
add-symbol-file /home/chengzi/qnx700/target/qnx7/x86_64/lib/dll/img_codec_tga.so.sym 0x008d4000
add-symbol-file /home/chengzi/qnx700/target/qnx7/x86_64/lib/dll/img_codec_sgi.so.sym 0x00ad8000
但是,问题来了,我们能不能用 sed 去处理这件事呢。
保持空间
我一直认为 sed 的命令是 vimscript 的子集,毕竟常用的指令都很相似,比如 a c s p 等。
但其实,sed 有自己特殊的命令。下面摘自 sed 的 man 文档
h H Copy/append pattern space to hold space.
g G Copy/append hold space to pattern space.
x Exchange the contents of the hold and pattern spaces.
其实,sed 为了方便文本操作,有两套空间,一套是模式空间,一套是保持空间 (hold space)。
正常我们使用 sed 操作文本的时候,都是使用模式空间,sed 每一轮循环,
都会从输入中读入下一行到模式空间;除此以外,所有文本操作的指令,都是操作模式空间,
比如 s 命令,就是在模式空间进行替换,p 命令,就是把模式空间打印出来。
而保存空间的作用就是可以把当前模式空间的内容先保存下来,
避免 sed 在下一轮循环把这段文本弄丢了。
下面,举一个 Stack Overflow 上的高赞例子,看看保持空间的妙用。
echo '1
2
3' | sed -n '1!G;h;$p'
这里的有三条语句,其中 G 和 p 有 range 前缀,1!G 表明除了第一行,
其他行都执行 G,$p 表示在文件末尾行执行 p。
我们模拟一下过程:
| NR | Op | Pattern Space | Hold Space |
|---|---|---|---|
| 1 | h | 1 | 1 |
| 2 | G | 2 1 | 1 |
| 2 | h | 2 1 | 2 1 |
| 3 | G | 3 2 1 | 2 1 |
| 3 | h | 3 2 1 | 3 2 1 |
| 3 | p |
所以,命令最后输出为
3
2
1
实践
其实,写 sed 脚本时,就可以把保持空间当成 awk 的一个变量,
所以 sed 也可以较为方便的解决上面的问题。
#!/bin/bash
PREFIX="$QNX_TARGET/x86_64/lib/dll/"
sed -n "
/^\/proc\/boot\/img_codec_/ {
s#^/proc/boot/\(.*\)#add-symbol-file ${PREFIX}\1.sym #
h
n
s/.*@ \(0x[0-9a-fA-F]*\).*/\1/
H
x
s/\n/ /
p
}
" $1
当然,较为复杂的文本还是用 awk 甚至 python 脚本来处理较为方便,
sed 还是能力有限,比如上面的环境变量获取还得依赖 shell 的能力,
它更适合一些批量的简单操作。