それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

標準出力と標準エラーのリダイレクト(コピー)について

標準出力と標準エラー出力を扱うときに

たまに間違って 1 というファイルが出来てしまって悲しいことになる。

takuya@Desktop$ ll 1
-rw-r--r-- 1 takuya staff 20 2017-04-27 14:51 1

これを防ぐためにちょっと標準出力と標準エラーの扱いをまとめておこうと思う。

標準出力とエラーの出力先の転送(コピー)について

最初に、エラーと出力をするかんたんなプログラムを用意しておきます。

cmd.rb

$stdout.puts "to stdout"
$stderr.puts "to stderr"

普通に実行した場合

takuya@~$ ruby cmd.rb
to stdout
to stderr

エラー出力の転送先を指定する。

takuya@~$ ruby cmd.rb 2>/dev/null
to stdout

標準出力の転送先を指定する。

takuya@~$ ruby cmd.rb 1>/dev/null
to stderr

両方の出力先を指定する。

takuya@~$ ruby cmd.rb 1> stdout.log 2>stderr.log
takuya@~$

画面には何も出ない。

ここまでのおさらいとさらなること

なんとなくわかってるようで、ここまでをシッカリ覚えた上で、どうなってるかをもう少し突っ込んでみてみないと失敗する。

プロセスの実行は、STDOUT/STDERRをそれぞれ繋いで(ファイルディスクリプタのコピー)になることを覚えておく。左が変数名、右が変数の中身(FDが指している先)

cmd.rb の fd 中身(出力先)
1 stdout
2 stderr

エラー出力を /dev/null に

エラー出力を捨てた場合。

takuya@~$ ruby cmd.rb 2>/dev/null
fd 中身(出力先)
1 stdout
2 /dev/null

上記の様に考えると、次からの転送をもう少し理解できる。

標準エラー出力と標準出力を同じにする

エラーと標準出力を合流させる。

takuya@~$ ruby cmd.rb 2>&1
to stdout
to stderr

これはこう考えると良い

fd 中身(出力先)
1 stdout
2 stdout

fd2 に fd1 の内容がコピー(同じに)された。

標準エラーを合流させて、標準出力をファイルへ

takuya@~$ ruby cmd.rb 2>&1 1>/dev/null
to stderr

これは、次のように一つずつの処理が2回動作している。そう考えると理解できる。

fd 中身(出力先)
1 stdout
2 stdout stderr

&1 で 1が指している stdout を2へコピーしている。

fd 中身(出力先)
1 /dev/null stdout
2 stdout

1 が指している内容を /dev/null に書き換えた

標準出力をファイルに出して2つを合流させる

takuya@~$ ruby cmd.rb > /dev/null 2>&1
takuya@~$

これは、次のように一つずつ、処理が2回動作していた。

そう考えると理解しやすい

1個めの処理

fd 中身(出力先)
1 /dev/null stdout
2 stderr

2個めの処理

1 が指している内容を /dev/null に書き換える

fd 中身(出力先)
1 /dev/null
2 /dev/null stderr

&1 で 1が指している内容(/dev/null) を2へコピーしている。

出力先を入れ替える.。

よくあるお題の、出力先の入れ替えも見てみる。

標準エラー出力と標準出力を入れ替えるには?」をここまでの表で見ておくことにする。

この状態を

fd 中身(出力先)
1 stdout
2 stderr

こうしたい

fd 中身(出力先)
1 stderr
2 stdout

つまりコピーしてしまうと。。。

takuya@~$ ruby cmd.rb  2>&1 1>&2
to stdout
to stderr
最初の指定 2>&1 でこうなり
fd 中身(出力先)
1 stdout
2 stdout stderr
2番目の指定 1>&2 でこうなる
fd 中身(出力先)
1 stdoutstdout
2 stdout

あぅ、、、だめじゃんおなじになっちゃう。

コピーするなら、一旦tmp を経由する。

追記コピーのようなことは出来ないので、いったん別の場所に対比しておく

任意のfd へ

ruby cmd.rb  3>&2

最初の状態の以下を書き換えたい

fd 中身(出力先)
1 stdout
2 stderr
3

こうした

fd 中身(出力先)
1 stdout
2 stderr
3 stderr
次にfd2 を書き換える
ruby cmd.rb  3>&2 2>&1
fd 中身(出力先)
1 stdout
2 stderr
3 stderr
最後に、fd1 を書き換える。
ruby cmd.rb  3>&2 2>&1 1>&3
fd 中身(出力先)
1 stderr stdout
2 stdout
3 stderr
出来上がり
fd 中身(出力先)
1 stderr
2 stdout
3 stderr

リダイレクトの指定順に注意する

1を〇〇へ、2を〇〇へとおぼえてしまうと、間違うことが多いので、 1 の指している先を2へコピーなど、言葉を少し変えて記憶したほうが無難でしょうね。

&1 記号の意味

2>&1 
2>/dev/null

これは &11 の指している先 を示すポインタで、 ファイル名の代わりに使っているだけだと考えて

2 >  &1 
2 >  /dev/null

このように敢えてスペースを入れて、対比させると覚えやすい ( >& にスペースを入れられないのだけど、スペースを入れ対比したほうが直感的)

まれに間違って教えてる人や間違って覚えてしまう人がいて

1 と 2 を合流させるから &アンド だとか、1 and 2 で 1 の内容に2 をプラス(追加)するなどと簡便な説明をしてしまってあとで混乱してしまうことになる。

省略記法

1と2の出力先をどちらも /dev/null にしてしまうなら

ruby cmd.rb   >& /dev/null
ruby cmd.rb   &> /dev/null
ruby cmd.rb   &>> /dev/null

此のような書き方も出来る。 此の書き方であれば、1+2を合流したと説明しても差し支えないと思う。

まとめ

## 標準出力の fd1 の指定は省略できる
ruby cmd.rb   1> /dev/null
ruby cmd.rb   > /dev/null

## 両方の出力先を同じにする
ruby cmd.rb   2>&1
ruby cmd.rb   1>&2

## 両方の出力先を同じしてファイルへ
ruby cmd.rb  >/dev/null  2>&1
ruby cmd.rb  1>/dev/null  2>&1
ruby cmd.rb  2>/dev/null  1>&2
ruby cmd.rb   >& /dev/null
ruby cmd.rb   &> /dev/null
ruby cmd.rb   1> /dev/null 2>/dev/null
ruby cmd.rb   1> /dev/null 2>/dev/null


## 出力先を入れ替える。
ruby cmd.rb   3>&1 1>&2  2>&1
ruby cmd.rb   3>&2 2>&1 1>&2