AWK 实战总结 -- 参数的传递
[TOC]
本文为 AWK 实战时遇到的一个 debug 记录.
区分 arguments 与 input files
背景
我想实现这么一种功能, 有一批 markdown 文件用来发布 blog, 因此需要在其开头(第一行前)插入 front-matter, 如何使用脚本避免一个个地复制粘贴?
首先我把通用的 front-matter 格式放入 front-matter.txt 文件中, 然后希望读取此文件依次插入到所有 .md 文件头部.
下面是 front-matter.txt (jason 格式 + 加上一个 html 的标签)
1 | --- |
思路
- 常规 shell 脚本(最容易想到的方法)
复制 front-matter.txt 到中间文件 temp, 然后利用>>
把 cat a.md 追加式重新定向到 temp 中完成拼接.
1 | temp=$(mktemp -p .) |
这种思路的一种变化, 使用 pipe + 重定向:
1 | file='a.md' |
- sed 指令原地修改(最优雅, 文本的插入与替换上还是 sed 更有优势.)
命令行一行指令即可搞定
1 | sed -i '1r "front-matter.txt" a.md' |
- AWK getline 输入
想多练习一下 AWK, 因此我尝试使用 AWK 脚本解决此问题.
首先写一个通用的 AWK 脚本 insert.awk
1 | NR==1{ |
调用 insert.awk
1 | awk -f insert.AWK a.md front-matte.txt > temp |
此时就出现了问题, 我发现导出的 temp 文件不仅是开头, 在结尾处也被插入了 front-matter.txt 完完整整的内容. debug 后我突然发现原来是自己对 AWK 命令行参数的理解没有到位.
AWK 命令行输入依次为
1 | ARGV[1]=a.md |
我预想的是 ARGV[1]
作为 input file, ARGV[2]
作为 argument, 也就是被读入的文件. 但是其实这两个参数都是 input file, 都会被处理. 当处理 ARGV[1]
时, ARGV[2]=front-matte.txt
生效, 处理没有问题. 接下来 AWK 会 shift 到 front-matte.txt
, 此时的 ARGV[1]=front-matte.txt
, ARGV[2]
为空, 因此, ARGV[1]=front-matte.txt
里的内容被原原本本地打印了出来(insert.AWK 的最后一行代码).
解决
AWK 有 -v
的选项可以把参数传入, 以实现把参数以及输入文件区分开来. 修改过后的程序如下:
1 | NR==1{ |
调用时如下
1 | AWK -f insert.AWK a.md -v read_file="front-matte.txt" > temp |
如果不使用 AWK 脚本而是直接在命令行输入整条命令的话, 有更加简洁方式, 即在命令里直接把文件传进去:
1 | awk 'NR==1{while((getline line < "./front-matte.txt") > 0 ) {t=t line "\n";}\ |
拓展–AWK 与 shell 之间的参数传递
除了上面的方式, 还有别的方式可以在 AWK 与 shell 之间传递参数吗?
我查了一下, 整理如下:
AWK 中使用 shell 中的变量
"'$var'"
: 使双括号变为单括号的常量, 传递给 AWK.
1 | var="test" |
注意如果 var
中含空格, 为了 shell 不把空格作为分格符, 便应该如下使用(再套一层 ""
最内侧):
1 | var="this is a test" |
'"$var"'
: 单引号在外, 双引号在内. 类似地, 如果变量含空格, 变为'""$var""'
.export
变量, 使用ENVIRON["var"]
形式, 获取环境变量的值:
1 | var="this is a test" |
- -v 选项(适合于变量个数不多的场景)
1 | var="this is a test" |
- 使用管道
模式 echo $1 $2 | awk "$AWKSCRIPT"
. 一个具体的例子如下, 计算直角三角形斜边长度.
例子来源于Advanced Bash-Scripting Guide
1 |
|
- 使用 indirect reference
下面是对输入的 filename
的第 column_number
进行 sum up 的代码片段.
可以注意到使用 indirect reference \$${column_number}
可以向 awk 传递参数.
并且不需要要像常规的 indirect reference 那样进行 eval \$${varname}
.
代码来自Advanced Bash-Scripting Guide.
1 | filename=$1 # Name of file to operate on. |
这几种方法还是有些不同的, 尤其是涉及到一些 元字符(Meta Characters) 的转义时. 虽然有 POSIX 标准规定了元字符, 但是很多既存的环境里, 元字符的使用很不统一, 例如不同 shell, 不同文本处理程序 awk perl 等. 这时 ""
还是 ''
在最内侧就有很大的区别了. 单引号 ''
里面禁止对符号进行解析, 所见即所得, 双引号 ""
阻止部分解析, 但是允许一部分规则进行展开, 再加上引用单双引号的环境对不同字符的格式要求不同, 上面所说的第一二种方法的结果有可能是不同的. 例如, 如果使用 "' '' "
即用双引号包裹单引号的形式在进行 AWK 的正则表达式模式匹配时, 外层的 ""
也会变成待匹配的字符, 而 ' "" '
即单引号包裹双引号的形式则不会匹配 ""
. 例子如下:
1 | $ echo -e "aa\nbb" > t #构造输入文本 t |
而第三第四种方式则更加通用一些, 强制所见即所得. 例子如下, 希望使用 AWK 将 q.md
文件中的 ![](NAME.png)
替换为 <img src="NAME.png" style="zoom:50%;" />
, 批量将显示的图片的尺寸缩小一半. 如果使用第一二种方法 <img src="" style="zoom:50%;" />
这个字符串拆开的 2 个字符串不太好处理, 可能需要插入各种转义符, 再传入 AWK 中需要再用 AWK 的方式重新理解一遍. 使用第三种方法的例子如下:
1 | a='<img src="';b='" style="zoom:50%;" />';export a;export b; cat q.md | grep "\[\](" | awk -F"(" '{print $2}'| awk -F")" '{print $1}'| xargs -I file awk -i inplace '/(!)(\[\]\('file'\))/ {r=ENVIRON["a"] "'file'" ENVIRON["b"];num=gsub(/(!)(\[\]\('file'\))/,r);};{print}' q.md |
AWK 向 shell 变量传递值
eval
命令执行 AWK 语句.
1 | eval $(awk '{printf("var1=%s; var2=%s; var3=%s;",$1,$2,$3)}' abc.txt) |
参考链接: https://blog.csdn.net/wsclinux/article/details/72885277
AWK 实战总结 -- 参数的传递