単独で動作するプロセスの標準入出力
Linuxでは、すべてのものはファイルとして扱われます。例えば、キーボード入力や画面出力もファイルとして扱われます。
具体的に見ていきましょう。まず以下のように入力してみます。この出力にはいろいろな知見が含まれます。順に説明します。
# ls -l /proc/self/fd
total 0
lrwx------ 1 root root 64 Mar 19 19:26 0 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 19 19:26 1 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 19 19:26 2 -> /dev/pts/1
lr-x------ 1 root root 64 Mar 19 19:26 3 -> /proc/147982/fd
/proc/self/fd
Linuxカーネルが提供する仮想ファイルシステム上のディレクトリです。
オープンしているファイルへのシンボリックリンクが格納されています。シンボリックリンクのファイル名はファイルデスクリプタ番号です。
詳しくは、以下の記事を参照ください。
ファイルデスクリプタ0, 1, 2
多くのケースで、プロセスはファイルデスクリプタ番号0, 1, 2の3つのファイルがオープンされた状態で起動されます。
この3つは、次のように用途が決まっています。
- 0: 標準入力。プロセスが情報を入力するのに使用されます
- 1: 標準出力。プロセスが通常出力を行うのに使用されます
- 2: 標準エラー出力。プロセスがエラー出力を行うのに使用されます
/dev/pts/[数値]
上記のlsでは、ファイルデスクリプタ0,1,2に対して、どれも/dev/pts/1がオープンされています。
この
/dev/pts/[数値]は、xtermなどの端末に相当するファイルです。
キーボードからの入力はそのファイル
/dev/pts/[数値]に追記されているようにプロセスからは見えます。
プロセスが、ファイル
/dev/pts/[数値]に書き込んだ内容は、対応する端末に文字列として表示されます。
この様子を図示すると次のようになります。
出力をファイルにリダイレクトする場合
標準出力をファイルに設定
次のようにコマンドの出力をファイルに保存することはよくあります。
ls -l /proc/self/fd > output.txt
作成されたファイル
output.txtの内容(すなわち、lsがオープンしているファイル)は次のとおりです。
total 0
lrwx------ 1 root root 64 Mar 21 08:20 0 -> /dev/pts/1
l-wx------ 1 root root 64 Mar 21 08:20 1 -> /root/output.txt
lrwx------ 1 root root 64 Mar 21 08:20 2 -> /dev/pts/1
lr-x------ 1 root root 64 Mar 21 08:20 3 -> /proc/160788/fd
つまり、コマンドライン上で
>を使ってファイルへ保存するとき、lsコマンドの標準出力はリダイレクトされたファイルに
なっていることが分かります。以下のその様子を図示します。
標準エラー出力もファイルに設定
上記のように
2>とその右側にファイル名を記載すると、そのファイルが標準エラー出力になります。
ls -l /proc/self/fd > output.txt 2> error.txt
output.txtの内容を確認すると以下のようになっています。
total 0
lrwx------ 1 root root 64 Mar 21 01:00 0 -> /dev/pts/5
l-wx------ 1 root root 64 Mar 21 01:00 1 -> /root/output.txt
l-wx------ 1 root root 64 Mar 21 01:00 2 -> /root/error.txt
lr-x------ 1 root root 64 Mar 21 01:00 3 -> /proc/75664/fd
以下にこの状態を図示します。
標準出力と標準エラー出力を同じファイルに設定
標準出力と標準エラー出力を同じファイルに保存する場合、以下のように
2&>1を使います。
これはファイルデスクリプタ1(標準出力)をファイルディスクリプタ2(標準エラー出力)にコピーするという指示です。
そのため、
> output.txtの後に記載する必要があります。
ls -l /proc/self/fd > output.txt 2&>1
output.txtの内容は次のとおりです。
total 0
lrwx------ 1 root root 64 Mar 21 02:33 0 -> /dev/pts/1
l-wx------ 1 root root 64 Mar 21 02:33 1 -> /root/output.txt
l-wx------ 1 root root 64 Mar 21 02:33 2 -> /root/output.txt
lr-x------ 1 root root 64 Mar 21 02:33 3 -> /proc/75694/fd
この状態を図示すると以下のようになります。
入力のリダイレクト
lessでファイル読む場合
以下のようにlessで引数にファイルを指定した場合、ファイルデスクリプタはどうなっているでしょうか?
less a.txt
この場合、4番に引数で指定した
a.txtが割り当てられています。標準入力は端末のままです。
lessは、標準入出力とは別に引数で与えられたファイルをオープンしたということになります。
なお、このlessのPIDの調べ方は、次節の「
パイプでプロセス間の入出力を連結する場合」で説明します。
total 0
lrwx------ 1 root root 64 Mar 21 03:11 0 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 21 03:11 1 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 21 03:11 2 -> /dev/pts/1
lr-x------ 1 root root 64 Mar 21 03:11 3 -> /dev/tty
lr-x------ 1 root root 64 Mar 21 03:11 4 -> /root/a.txt
この状態を図示すると以下のようになります。
標準入力にファイルを設定
一方、次のようなコマンドラインではどうなるでしょうか?
less < a.txt
答えは次のとおり、標準入力は
<の右側に指定したファイルになっています。
この違いについては、この記事の最後の「
標準入出力は誰が設定するのか?」で詳しく説明します。
total 0
lr-x------ 1 root root 64 Mar 21 04:29 0 -> /root/a.txt
lrwx------ 1 root root 64 Mar 21 04:29 1 -> /dev/pts/6
lrwx------ 1 root root 64 Mar 21 04:29 2 -> /dev/pts/6
lr-x------ 1 root root 64 Mar 21 04:29 3 -> /dev/tty
また、これまで同じく図も示します。
パイプでプロセス間の入出力を連結する場合
Linuxでは以下のようにパイプを使うケースも多いと思います。
cat a.txt | grep abc
この場合の標準入出力を見てみます。
ただし、上記のコマンドは一瞬で終了するため、実行中の/proc/[PID]/fdを調べることができません。
そのため、少しコマンドを工夫しますが、その前にcatやgrepを実行するbashのPID(プロセスID)を調べておきます。
変数$$には、実行中のbashのPIDが格納されています。以下のとおり、操作しているbashの74570であり、そのbashから実行されたコマンドの親PIDは、74570になります。
$ echo $$
74570
次いで以下のコマンドを実行します。catに引数がありません。
この場合、catは標準入力すなわちキーボードから入力を待っている状態になり、ファイルを読み込んだ時のように瞬時には終了しなくなります。
cat | grep abc
ここでcatとgrepのPID(プロセスID)を調べます。同じPCの別の端末で、先ほど調べたbashのPIDを親にもつプロセスを探します。
$ ps --ppid 74570
TIME CMD
75104 pts/6 00:00:00 cat
75110 pts/6 00:00:00 grep
catとgrepのPIDはそれぞれ75104と75110であることが分かりました。
それぞれについて、
/proc/[PID]/fd以下のファイルを見ます。
$ ls -l /proc/75104/fd
total 0
lrwx------ 1 root root 64 Mar 20 09:01 0 -> /dev/pts/1
l-wx------ 1 root root 64 Mar 20 09:01 1 -> 'pipe:[936222]'
lrwx------ 1 root root 64 Mar 20 09:01 2 -> /dev/pts/1
$ ls -l /proc/75110/fd
total 0
lr-x------ 1 root root 64 Mar 20 09:01 0 -> 'pipe:[936222]'
lrwx------ 1 root root 64 Mar 20 09:01 1 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 20 09:01 2 -> /dev/pts/1
上記の結果から、catの標準出力が
pipe:[936222]というのになっており、grepの標準入力も同じく
pipe:[936222]になっています。
これは、下図のようにcatがパイプというファイルに出力を書き込み、grepは同じパイプというファイルからデータを読み出していることを意味します。
また、catの標準入力や標準エラー出力、grepの標準出力と標準エラー出力は、端末である
/dev/pts/1となっています。つまり、catは端末を通じてキーボードからデータを入力し、grepが標準出力に書き込んだデータは、端末に表示されることが分かります。
標準入出力は誰が設定するのか?
このように、リダイレクトやパイプを使うことで起動されるプロセスの標準入出力が変化します。
標準入出力は、誰がいつ設定しているのでしょうか?答えは、起動されるプロセスの親プロセスです。
端末でコマンドを起動しているなら、bashなどのシェルが親プロセスです。
bashが、コマンドライン上のリダイレクトやパイプの記号を解釈して、子プロセスの標準入出力をユーザの指示通りになるように設定して起動します。