37
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「1>/dev/null 2>&1と2>&1 1>/dev/nullの違い」へのご指摘の調査

Last updated at Posted at 2017-03-02

前回の記事へのご指摘

前回の記事を執筆したところ、ご指摘を頂けたのですが、
私の理解が追いついていないところがありました。それが以下です。

a. どこまで行っても 1 は標準出力と同義 ( 同様に 2 は標準エラーと同義 )
b. 子は親のファイルディスクリプタを引き継ぐ
c. リダイレクトを指定すると、親のものを引き継がずに切り替わりが起こる
d. 子は親の標準出力と同じである必要はない

では、順にみていきます。

a.のご指摘の調査

ファイルディスクリプタ0は標準入力、1を標準出力、2を標準エラー出力と呼ばれます。
その関係性が崩れることはありません。そのことは、Linux上のファイルを見る事でも窺い知れます。

# ls -l /dev/std*
lrwxrwxrwx. 1 root root 15 11月  6 11:32 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root root 15 11月  6 11:32 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root root 15 11月  6 11:32 /dev/stdout -> /proc/self/fd/1

この通り、stdinはfd0、stdoutはfd1、stderrはfd2を指し示しています。
では、fdの0から2は何を指しているのでしょうか。

# ls -l /proc/self/fd/{0..2}
lrwx------. 1 root root 64  3月  2 22:30 /proc/self/fd/0 -> /dev/pts/1
lrwx------. 1 root root 64  3月  2 22:30 /proc/self/fd/1 -> /dev/pts/1
lrwx------. 1 root root 64  3月  2 22:30 /proc/self/fd/2 -> /dev/pts/1

/dev/pts/1ですので、疑似端末(sshした時などの接続先端末)となります。
そして、このfdの向き先を最終的に変えるのがリダイレクトとなります。

私は1の先に標準出力があるものだと思っていたのですが、
正しくは1こそが標準出力と呼ばれるもの、そのものだったということですね。

以上より、

a. どこまで行っても 1 は標準出力と同義 ( 同様に 2 は標準エラーと同義 )

については、ご指摘の通りとなります。

b.のご指摘の調査

b. 子は親のファイルディスクリプタを引き継ぐ

これはforkを調べたら分かりました。

The child inherits copies of the parent's set of open file descriptors.

ただ、そのものを引き継ぐわけではなく、コピーを引き継ぐようです。
つまり、プロセス毎にファイルディスクリプタ自体は別ですが、指し示す先は同じです。

c.のご指摘の調査

c. リダイレクトを指定すると、親のものを引き継がずに切り替わりが起こる

これについては、straceを使って、どのような処理が
リダイレクト時に内部的に行われているのかを見て判断します。

では、前回と同じコマンド(正しいほう)でトレースしてみます。

strace -o redirect.txt -ff bash -c "ls dada mama 1>/dev/null 2>&1"

一応straceのオプションについて説明します。
-oはトレース結果をファイル出力します。
-ffは、子プロセスまでトレースし、プロセス毎にファイルを分けて出力するオプションです。

尚、bashにコマンドをいったん嚙ませないと、うまくリダイレクト結果が取れませんでした。

見ていきます。

# ls
dada  redirect.txt.817  redirect.txt.818
# grep "/dev/null" redirect.txt.817
execve("/bin/bash", ["bash", "-c", "ls dada mama 1>/dev/null 2>&1"], [/* 18 v
ars */]) = 0

なぜかプロセスが二つ生成されてました。
どうやら、817(親プロセス側)ではリダイレクトらしき事は行われていないようです。
これは、リダイレクトを親プロセス側で実施してしまうと、その後の親プロセスの処理も
リダイレクトの影響を意図せず受けてしまうからかな、と思います。
では、818側を見てみます。

# grep "/dev/null" redirect.txt.818
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

/dev/nullがopenされています。
openはファイルを開き、オープンしたファイルの
ファイルディスクリプタを返すシステムコールです。
今回は、/dev/nullが3でopenされているようです。
周辺のログを見てみましょう。

# grep -A 6 "/dev/null" redirect.txt.818
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(1, 2)                              = 2
fcntl(1, F_GETFD)                       = 0
execve("/bin/ls", ["ls", "dada", "mama"], [/* 17 vars */]) = 0
brk(0)                                  = 0x1014000

リダイレクトを行っている部分が見えましたね。
リダイレクトの正体はdup2です。
dup2は、二つ目の引数のファイルディスクリプタを
一つ目の引数のファイルディスクリプタの複製として作る、
というシステムコールです。

一連の流れをまとめると、

  1. プロセスをcloneする(子プロセスを作る)
  2. /dev/nullをファイルディスクリプタ3で開く
  3. 1を3(/dev/null)の複製として作成
  4. 3をクローズ
  5. 2を1(/dev/null)の複製として作成
  6. 2にエラーメッセージを、1にls結果を出力(いずれも/dev/nullに対して出力)

ということをしています。

従いまして、リダイレクトした際、親のものを引き継がずに切り替わりが起こるわけではなく、
親のファイルディスクリプタを引き継いだ上で、dup2によってファイルディスクリプタの
複製を行い、指し示す先を切り替えています。

ついでなので、だめなほう(2>&1 1>/dev/null)の動きもみてみます。

# strace -o redirect2.txt -ff bash -c "ls dada mama 2>&1 1>
/dev/null"
ls: mama にアクセスできません: そのようなファイルやディレクトリはありません
# ls
dada  redirect2.txt.3820  redirect2.txt.3821

やはり、プロセスは二つのようです。中見ます。

dup2(1, 2)                              = 2
fcntl(1, F_GETFD)                       = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1)                              = 1
close(3)                                = 0
execve("/bin/ls", ["ls", "dada", "mama"], [/* 17 vars */]) = 0
brk(0)                                  = 0xa8e000

先ほどと逆になっただけですね。

  1. プロセスをcloneする(子プロセスを作る)
  2. 2を1の複製として作成(先は一緒なので意味なし)
  3. /dev/nullをファイルディスクリプタ3で開く
  4. 1を3(/dev/null)の複製として作成
  5. 3をクローズ
  6. 2にエラーメッセージを、1(/dev/null)にls結果を出力

となります。

d.のご指摘の調査

d. 子は親の標準出力と同じである必要はない

cの検証の通り、切り替わった状態で子供が生まれるわけでないので、
恐らくこれ以外の言い回しになると思います。

自分の無知さを痛感しました。
それなりに時間をかけて調べたつもりだったのに、まだまだでしたね…。
angel_p_57さん、本当にありがとうございました。

取りあえず、前記事は黒歴史として残しておいて、別途ちゃんとしたやつを書いて、
そちらに誘導するようにします。

37
31
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?