標準出力と標準エラー出力を扱うときに
たまに間違って 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 |
&1
で 1が指している stdout を2へコピーしている。
fd | 中身(出力先) |
---|---|
1 | /dev/null |
2 | stdout |
1
が指している内容を /dev/null に書き換えた
標準出力をファイルに出して2つを合流させる
takuya@~$ ruby cmd.rb > /dev/null 2>&1 takuya@~$
これは、次のように一つずつ、処理が2回動作していた。
そう考えると理解しやすい
1個めの処理
fd | 中身(出力先) |
---|---|
1 | /dev/null |
2 | stderr |
2個めの処理
1
が指している内容を /dev/null に書き換える
fd | 中身(出力先) |
---|---|
1 | /dev/null |
2 | /dev/null |
&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 |
2番目の指定 1>&2
でこうなる
fd | 中身(出力先) |
---|---|
1 | stdout |
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 |
2 | stdout |
3 | stderr |
出来上がり
fd | 中身(出力先) |
---|---|
1 | stderr |
2 | stdout |
3 | stderr |
リダイレクトの指定順に注意する
1を〇〇へ、2を〇〇へとおぼえてしまうと、間違うことが多いので、 1 の指している先を2へコピーなど、言葉を少し変えて記憶したほうが無難でしょうね。
&1 記号の意味
2>&1 2>/dev/null
これは &1
が 1 の指している先
を示すポインタで、 ファイル名の代わりに使っているだけだと考えて
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