@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Bash のプロセス置換(Process Substitution)がわからない→ちょっとわかった

こういうシェル関数があるとする。要はちょっと込み入った 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