Skip to content

Instantly share code, notes, and snippets.

@jschwinger233
Last active March 8, 2020 09:01
Show Gist options
  • Select an option

  • Save jschwinger233/a2450771c1592aae035e6d912d1bf7f5 to your computer and use it in GitHub Desktop.

Select an option

Save jschwinger233/a2450771c1592aae035e6d912d1bf7f5 to your computer and use it in GitHub Desktop.

Q1: 多次处理 stdout

比如我要先判断 stdout 里是否匹配某模式, 匹配则对 stdout 做进一步处理.

无脑一点, 我们可以把 stdout 写到临时文件里:

tmpfile=$(mktemp)
cmd | tee $tmpfile | inspect $pattern && process $tmpfile
rm -f $tmpfile

本身也足够简单, 可读性高, 也好扩展, 也可以反复读, 适用于各种场景.

如果不满意有文件落地, 那可不太好办了, 非常不好办.

我们有三种可能的手段:

  1. inspectprocess 在同一进程, 形如 inspect $pat && process, 好处是我们可以通过变量来控制分支, 可以完成顺序式编程.
  2. inspectprocess 是父子进程关系, 形如 process > >(inspect $pat), process substitution 保证了父子进程关系, 此时我们可以通过 wait(1) 来完成跨进程同步.
  3. inspectprocess 是非父子关系的两个进程, 形如 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

Q2: stderr 打断进程

在一串 pipeline 中要对部分命令的 stderr 进行模式匹配, 匹配成功就退出进程.

TTY=$(tty) cmd1 | (cmd2 2> >(grep $pat > $TTY) || kill -9 $$) | cmd3

这里的难点是,

  1. 我不想把 cmd2 的 stderr dup 到 stdout 然后交给 cmd3 判断
  2. 我依然想打印出 stderr 到 tty, 而在管道子进程中已经拿不到 tty 了, 只能通过环境变量
  3. exit(1) 是没用的, 只有 kill(1) 能跨进程中断

不过上面的代码其实并不符合要求, 要求对 stderr 匹配模式才退出, 代码是 exit code 非零就退出. 如果要做到题目要求, 那么 $! 捕获 process subsititution 的 pid 又派上用场了:

TTY=$(tty) cmd1 | (cmd2 2> >(grep $pat > $TTY); wait $!; [[ $? != 0 ]] && kill -9 $$) | cmd3

Q3: 怎么做好 cleanup

比如在 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 后能检查临时文件被删除.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment