本記事は シェルスクリプトのカレンダー | Advent Calendar 2021 - Qiita 17日目の記事だ。
シェルスクリプトのカレンダー | Advent Calendar 2021 - Qiita
殆どカレンダーが埋まってなかったので、思いついたネタで埋めちゃえ埋めちゃえ。
今回は、 bash 系列 (bash, zsh 等) の プロセス置換 (process substitution) 機能の話だ。
このプロセス置換は POSIX 互換の機能では無いため、以降の例は ash 系列 (busybox hush (ash), dash 等) では利用できない。
bash 系の プロセス置換 という機能と、 tee コマンドを組み合わせて、 同時に複数のコマンドにパイプできるらしい。
詳しい説明を読んでも、正直頭がさっぱり追いつかないが…
要は、 引数部分でファイルを指定するべき場所で、コマンドの標準入出力を代用できる機能ということか。
例えば、 "cat <(ls ./a) <(ls ./b)
" とすれば、 "ls ./a
" の内容と "ls ./b
" の内容が連結されて出力されるし、
"command0 | tee >(command1) | command2
" とすれば、 command0 の標準出力が、 command1 の標準入力と、 command2 の標準入力両方に渡される。
また、「ファイルの読み書きの代替」となるため、 "command0 2> >(command1)
" のようにリダイレクト先のファイル名の替わりにプロセス置換を使えば、標準エラーだけを command1 の標準入力に渡すことができる。
ちょっとその尖った使い所を考えてみる。
例えばこんな、標準出力と標準エラーを吐き出すスクリプトがあったとしよう。
$ cat <<'EOF' > ./testecho.sh
#!/bin/bash
echo "stdout1" >&1
echo "stderr1" >&2
echo "stdout2" >&1
echo "stderr2" >&2
EOF
$ chmod u+x ./testecho.sh
このスクリプトを実行し、 標準エラーだけ command2 に渡して、 標準出力は command1 に渡したい場合、 プロセス置換を使うと以下のようにできる。
$ ./testecho.sh 2> >(command2) | command1
# -> 各コマンドの入力
# command1:
# stdout1
# stdout2
# command2:
# stderr1
# stderr2
ここで更に、 標準出力と標準エラーの両方を command2 に渡して、 標準出力だけを command1 に渡したい場合、 ちょっと複雑になるが以下のようにして実現できる。
$ ./testecho.sh 2>&1 > >(tee >(command1)) | command2
# -> 各コマンドの入力
# command1:
# stdout1
# stdout2
# command2:
# stdout1
# stderr1
# stderr2
# stdout2
少し複雑なので分解して考えてみよう。
testecho.sh
のリダイレクト部分 (赤枠) を考える。
> ファイルパス
") の表記だ。>(cmd_list)
") が使われており、 標準出力が tee
の標準入力へ書き込まれている。
tee
のファイル出力もまた、プロセス置換を使って command1
の標準入力に渡される。testecho.sh
の標準出力だけが、 command1
の標準入力に渡されることになる。tee
の標準出力のほう、 これは ./testecho.sh
を実行した標準出力に戻ってくる。testecho.sh
のリダイレクトに話を戻すと、 その次のリダイレクト (水色下線部) の "2>&1
" によって、 tee
の標準出力と ./testecho.sh
の標準エラーが、標準出力側に統合される。command2
に渡される。testecho.sh
の標準出力と標準エラーの両方が、 command2
の標準入力に渡されることになる。ちなみに、 ./testecho.sh の標準出力と標準エラーの出力速度が早いと、上記の出力例のように command2 に渡される標準出力と標準エラーが順不同になってしまう。
なお、以下のようにやっても同じ結果になるはずだ。
$ ./testecho.sh > >(command2) 2>&1 > >(tee >(command1))
例えば、以下のようにすると、 コマンドの標準出力だけメールを出しつつ、メールの内容と 標準エラーの両方を journal に書き込む事ができる。
./testecho.sh 2>&1 > >(tee >(/usr/sbin/sendmail [email protected])) | /usr/bin/systemd-cat
ただ、どうせ journal に記録するなら、 -t
オプションを使って、以下の 3 つを識別子で分けて記録したほうが良いかもしれない。
./testecho.sh 2> >(/usr/bin/systemd-cat -t cmderr) > >(tee >(/usr/bin/systemd-cat -t mailout /usr/sbin/sendmail -v [email protected]) | /usr/bin/systemd-cat -t cmdout)
うーん、 ここまで来ると初見で動作を理解できる気がしない。
参考:
記事の初稿でリダイレクトの参照順の説明が誤っていたので訂正。ただ、詳しい仕組みは難しくて説明しきれないのでググって。 ↩︎
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント