こういうシェル関数があるとする。要はちょっと込み入った Perl スクリプトのラッパー関数を作りたい(↓は例のために単純で無意味な感じにしてある)。
hello(){ cat | perl -nle 'print uc' }
↓これは期待通り動く。
# ps | hello PID TTY TIME CMD 1 PTS/0 00:00:00 BASH 11 PTS/0 00:00:00 PS 12 PTS/0 00:00:00 BASH 13 PTS/0 00:00:00 CAT 14 PTS/0 00:00:00 PERL
↓これも期待通り動く。
# hello < /etc/shells # /ETC/SHELLS: VALID LOGIN SHELLS /BIN/SH /BIN/BASH /USR/BIN/BASH /BIN/RBASH /USR/BIN/RBASH /BIN/DASH /USR/BIN/DASH
Bash のプロセス置換というのを使うと、こういうふうに書けるらしい。
# cat <(ps) PID TTY TIME CMD 1 pts/0 00:00:00 bash 17 pts/0 00:00:00 bash 18 pts/0 00:00:00 cat 19 pts/0 00:00:00 ps
しかし、これ↓は動かない。実行すると止まってしまって、C-c するしかない。
# hello <(ps) ^C
最後のやつがなぜ動かないのか、理由がわからない・・・。Stack Overflow でいくつか読んでみたりもしたけど、理解できる説明を見つけられなかった。
止まってしまうのはたぶん標準入力を待ってしまっているからで、パイプの場合と違うのはサブシェル?の有無とか、プロセス置換が実際にはコマンドの出力(文字列)そのものではなくてファイルディスクリプタ?を指しているからとか、その辺の事情なのだと思うけど、理解できない。どこから理解できてないのかもわからない。
# echo <(ps) /dev/fd/63
自分のユースケースではプロセス置換を使うのは必須ではないので、パイプで十分なのだけど、こういうのどうしても気になってしまって、わかるまで他のことが手につかなくなってしまう。
誰かわかる人いたら教えてください。
シャワー浴びてたら $ hello <(ps)
がダメな理由はわかった。プロセス置換は「あたかもファイルのように」扱える、というものであり、リダイレクトとは別物。なので、要は↓みたいな状態になっている。
❯ cat - /etc/shells ^C
そりゃ標準入力を待つわけだ。標準入力をパイプで渡してやるとブロックしない。一行目に 1
が出る。
❯ echo 1 | cat - /etc/shells 1 # List of acceptable shells for chpass(1). # Ftpd will not allow users to connect who are not using # one of these shells. /bin/bash /bin/csh /bin/dash /bin/ksh /bin/sh /bin/tcsh /bin/zsh
つまり、プロセス置換を標準入力にリダイレクトすれば動く。
❯ hello < <(ps) PID TTY TIME CMD 9764 TTYS000 0:00.38 -ZSH 10324 TTYS000 0:00.00 CAT 10325 TTYS000 0:00.00 PERL -NLE PRINT UC 9801 TTYS001 0:00.17 -ZSH