比如我要先判断 stdout 里是否匹配某模式, 匹配则对 stdout 做进一步处理.
无脑一点, 我们可以把 stdout 写到临时文件里:
tmpfile=$(mktemp)
cmd | tee $tmpfile | inspect $pattern && process $tmpfile
rm -f $tmpfile
本身也足够简单, 可读性高, 也好扩展, 也可以反复读, 适用于各种场景.
如果不满意有文件落地, 那可不太好办了, 非常不好办.
我们有三种可能的手段:
inspect和process在同一进程, 形如inspect $pat && process, 好处是我们可以通过变量来控制分支, 可以完成顺序式编程.inspect和process是父子进程关系, 形如process > >(inspect $pat), process substitution 保证了父子进程关系, 此时我们可以通过wait(1)来完成跨进程同步.inspect和process是非父子关系的两个进程, 形如inspect $pat | process, 我们只能让inspect过滤.
下面来演示一下, 例题简单一点, 检查 etcdctl 输出里是否有 ModRevision, 有的话把全体输出到终端.
思路一的做法:
etcdctl get key -w fields | (while read line; do [[ "$line" =~ ModRevision ]] && found=1; content="$line"$'\n'"$content"; done && [ -n "$found" ] && echo "$content")
可以看到我们用 $found 来 toggle process.
思路二:
etcdctl get /a -w fields | (content="$(cat -)"; echo "$content" > >(grep -q ModRevision); wait $!; [[ "$?" == 0 ]] && echo "$content" )
里面使用了 $! 这一 bash4.4+ 才有的变量来获取 process substitution 的 pid, 然后 wait 直到 inspect 完成.
思路三和思路一的大部分代码相同,
etcdctl get key -w fields | (while read line; do [[ "$line" =~ ModRevision ]] && found=1; content="$line"$'\n'"$content"; done && [ -n "$found" ] && echo "$content") | cat
要注意最后那个 cat 才是 process 进程, 中间一大串都是 inspect, 虽然代码和思路一相似, 但是这只是因为题目的 process 是打印到终端, 如果是写到文件那立刻就不同了.
如果是特殊一点的场景, 比如是匹配模式后抠字符, 那么多半会有合并的做法, 而实际上大部分场景我们都是面对格式很固定的数据.
比如如果模式和要抠的字符在同行, awk 的 filter + print columns 的这一套就非常合适.
cmd | awk '/PAT/ {print $NF}'
如果是跨行, 但是模式我们很清楚, 比如要抠的字符在模式附近几行, gnu/grep + regex 就能很容易上手:
cmd | grep $pat -A4 | grep -Po '\w{64}'
如果模式复杂, 因为 gnu/grep 的正则不能抠 capture group, 加上 pcre 的向前环视只能是固定字符, 在某些场景下可能 read 更简单:
cmd | while read line; do read key _ value <<<$line; [[ "$key" == PAT ]] && echo $value; done
在一串 pipeline 中要对部分命令的 stderr 进行模式匹配, 匹配成功就退出进程.
TTY=$(tty) cmd1 | (cmd2 2> >(grep $pat > $TTY) || kill -9 $$) | cmd3
这里的难点是,
- 我不想把 cmd2 的 stderr dup 到 stdout 然后交给 cmd3 判断
- 我依然想打印出 stderr 到 tty, 而在管道子进程中已经拿不到 tty 了, 只能通过环境变量
exit(1)是没用的, 只有kill(1)能跨进程中断
不过上面的代码其实并不符合要求, 要求对 stderr 匹配模式才退出, 代码是 exit code 非零就退出. 如果要做到题目要求, 那么 $! 捕获 process subsititution 的 pid 又派上用场了:
TTY=$(tty) cmd1 | (cmd2 2> >(grep $pat > $TTY); wait $!; [[ $? != 0 ]] && kill -9 $$) | cmd3
比如在 Q1 里的无脑解法创建了一个临时文件, 怎么尽可能保证临时文件能在进程退出后被清理掉.
我们可以用一个装饰器脚本, 名字叫 ONTEMP:
#!/bin/bash
echo "Creating tempfile"
tempfile=$(mktemp)
echo "Executing command: $@"
eval $@
echo "Removing tempfile"
rm -f $tempfile
不过这还不够, 我们还能处理好信号等情况:
function cleanup() {
echo "Removing tempfile"
rm -f $tempfile
}
trap cleanup EXIT
利用好 trap(1) 可以很好地应对各种情况, 不过对于 kill -9 就不行了... 那也没办法了.
最终的代码:
#!/bin/bash
echo "Creating tempfile"
tempfile=$(mktemp)
function cleanup() {
echo "Removing tempfile"
rm -f $tempfile
}
trap cleanup EXIT
echo "Executing command: $@"
eval $@
使用:
ONTEMP sleep 20
Ctrl-C 后能检查临时文件被删除.