- PR -

ディスクリプタについて

1
投稿者投稿内容
saratoga
常連さん
会議室デビュー日: 2001/11/26
投稿数: 28
投稿日時: 2004-10-03 23:13
こんにちは。
今回は bash について質問があります。

bash には標準出力やエラー出力が用意されていますが、
それをリダイレクトする際には何か制限などありますでしょうか?

というのは、今回初めてリダイレクトについて
調べてみたのですが、イメージと違う結果になってしまう場合があります。

例えば、標準出力とエラー出力が出るスクリプトを作成し
それぞれを同じファイルに出力してみます。

test.sh > log.txt 2>&1
これは意味がわかります。標準出力がファイルに、
エラー出力は標準出力と合わせてファイルに書き出されます。

test.sh 2> log.txt > log.txt
これはエラー出力のみファイルに書き出されます。
先にエラー出力の方をファイルに切り替えて、
標準出力は上書きするのではなく、既にファイルオープンされてて、
書き込みができなかったのでしょうか?

test.sh > log.txt 2> log.txt
また、こうしても上記と結果が同じになります。なぜ?
エラー出力の方が優先されるのでしょうか?

この辺が疑問でハマりまくっています。
どなたか、この疑問を解消してくれる方はおりませんでしょうか?

ご教授よろしくお願いします。
saratoga
常連さん
会議室デビュー日: 2001/11/26
投稿数: 28
投稿日時: 2004-10-03 23:23
追伸です。

test.sh 2> log.txt >& log.txt
この場合は、標準出力もエラー出力もファイルに書かれます。
man bash したところ、>& は > log.txt 2>&1 と同等みたいですね。
この場合は、2> log.txt でエラー出力先がファイルになり、
>& log.txt で標準出力先がファイルになり、エラー出力も
標準出力と同じ場所に書き出されます。
これは、イメージ通りの結果となります。

しかし、下記のように順番を逆にすると結果が変わります。
test.sh >& log.txt 2> log.txt
この場合はエラー出力のみがファイルに書き出されます。
>& log.txt は > log.txt 2>&1 と同等で、
標準出力はファイルに書き出されると思うのですが、
なぜエラー出力しか書き出されないのでしょうか。
下記のように変えても同じ結果です。う〜ん・・・。
test.sh >& log.txt > log.txt

先ほどの質問でも気になってるのですが、
標準出力とエラー出力に何か優先度の関係とかあるのでしょうか?
ちいにぃ
大ベテラン
会議室デビュー日: 2002/05/28
投稿数: 244
投稿日時: 2004-10-04 02:26
私もよくわかっていないので、うまく説明できないのですが、

man bash すると、次のように書かれています。

引用:

リダイレクトの順番には意味がある点に注意してください。例え
ば、次のコマンド

ls > dirlist 2>&1

は 標準出力と標準エラー出力を両方ともファイル dirlist に書
き込みますが、次のコマンド

ls 2>&1 > dirlist

では標準出力だけがファイル dirlist に書き込まれます。な ぜ
な ら後者の場合には、標準エラー出力は dirlist にリダイレク
トされる前の標準出力の複製となるからです。



まあ、それはさておき。
リダイレクトを実現するにはシステムコールの open/dup2、ライブラリの
open64/dup2 を使っています。

ですので、次のようにして strace や ltrace を使ってログを取って
調べると理解しやすいかも。

$ strace -o log1 bash -c 'echo hello >a 2>&1'
$ strace -o log2 bash -c 'echo hello 2>&1 >a'
$ ltrace -o log3 bash -c 'echo hello >a 2>&1'
$ ltrace -o log4 bash -c 'echo hello 2>&1 >a'

strace では open/dup2/write, ltraceではopen64/dup2/printf に注目。
今回の場合はstraceの方がわかりやすいかも。
MMX
ぬし
会議室デビュー日: 2001/10/26
投稿数: 861
投稿日時: 2004-10-04 10:35
test.sh > log.txt 2> log.txt
また、こうしても上記と結果が同じになります。なぜ?
エラー出力の方が優先されるのでしょうか?

&無しでは、後書きした方が優先されているのでは。

-------------------- r.pl
print STDERR "Oshikiri";
print "Moe";

の順を代えると,そうです

[ メッセージ編集済み 編集者: MMX 編集日時 2004-10-04 10:37 ]
MST
会議室デビュー日: 2004/09/30
投稿数: 2
お住まい・勤務地: 神奈川県
投稿日時: 2007-02-19 17:48
test.sh 2> log.txt > log.txt

これですが、エラー出力と標準出力を
同じファイルにしてしまっているため、
エラー出力を標準出力で上書きしていると思います。
両方出したいのであれば >> 追加リダイレクトして
あげなければいけません。

test.sh >> log.txt 2>&1

また、下記のように記述すると、エラーはlog_error.txtに、
標準出力はlog.txt に書き込まれます

test.sh 2> log_error.txt > log.txt

[ メッセージ編集済み 編集者: MST 編集日時 2007-02-19 18:14 ]
hito
会議室デビュー日: 2007/02/22
投稿数: 5
投稿日時: 2007-02-22 11:17
おそらくですが、「ディスクリプタの決定の順番」と「出力処理の順番」を分けていないことが誤解の原因かと思います。

シェルに与えるリダイレクト記号は、「ディスクリプタを決定」するだけで、出力は後からまとめて行われます。この処理は、シェルに与えられたものが左から処理されていきます。

ディスクリプタの決定後、出力が行われますが、こちらは一般的なOSではディスクリプタのナンバ順、すなわち、
1. 標準入力(0)
2. 標準出力(1)
3. 標準エラー出力(2)
の順に処理されていきます。

なので、まず、
・シェルに与えられたリダイレクト記号を元に、ディスクリプタを決定する。これは左から順番に決定される。
・決定されたディスクリプタに順番に出力する。これは標準入力→標準出力→エラー出力の順で固定。
という動作が行われている、ということを前提に考えていくときれいに解釈できるようになると思います。

……というので普通は済む話なのですが、ちょっとだけシェルによる (たぶんbashをお使いかなと) の例外もありますが、大抵はこれで解釈できると思います。この例外については最後に書きました。

それぞれの例を順番に見てみましょう。

> test.sh > log.txt 2>&1
> これは意味がわかります。標準出力がファイルに、
> エラー出力は標準出力と合わせてファイルに書き出されます。

「ディスクリプタの決定」
1) test.shのディスクリプタ(1:標準出力)は log.txt になります。
2) test.shのディスクリプタ(2:エラー出力)を、ディスクリプタ(1)に変更します。

「出力処理」
3) ディスクリプタ(1:標準出力+エラー出力)を処理します。log.txtに書き込みます。
4) ディスクリプタ(2)は存在しません。

> test.sh 2> log.txt > log.txt
> これはエラー出力のみファイルに書き出されます。
> 先にエラー出力の方をファイルに切り替えて、
> 標準出力は上書きするのではなく、既にファイルオープンされてて、
> 書き込みができなかったのでしょうか?

「ディスクリプタの決定」
1) test.shのディスクリプタ(2:エラー出力)を、log.txt にします。
2) test.shのディスクリプタ(1:標準出力)を、log.txt にします。

「出力処理」
3) ディスクリプタ(1:標準出力)を処理します。log.txtに書き込みます。
4) ディスクリプタ(2:エラー出力)を処理します。log.txtに書き込みます。リダイレクト指定は > でしたから、以前のlog.txtは破棄されます。

> test.sh > log.txt 2> log.txt
> また、こうしても上記と結果が同じになります。なぜ?
> エラー出力の方が優先されるのでしょうか?

「ディスクリプタの決定」
1) test.shのディスクリプタ(1:標準出力)を、log.txt にします。
2) test.shのディスクリプタ(2:エラー出力)を、log.txt にします。

「出力処理」
3) ディスクリプタ(1:標準出力)を処理します。log.txtに書き込みます。
4) ディスクリプタ(2:エラー出力)を処理します。log.txtに書き込みます。リダイレクト指定は > でしたから、以前のlog.txtは破棄されます。

> しかし、下記のように順番を逆にすると結果が変わります。
> test.sh >& log.txt 2> log.txt
> この場合はエラー出力のみがファイルに書き出されます。

まずこのままでは分かりにくいので、">& log.txt" を展開します。
> test.sh > log.txt 2>&1 2> log.txt

「ディスクリプタの決定」
1) test.shのディスクリプタ(1:標準出力)を、log.txt にします。
2) test.shのディスクリプタ(2:エラー出力)を、ディスクリプタ(1)にします。
3) test.shのディスクリプタ(2:エラー出力)を、log.txt にします。2)で行った変更は上書きされ、無視されます。

「出力処理」
3) ディスクリプタ(1:標準出力)を処理します。log.txtに書き込みます。
4) ディスクリプタ(2:エラー出力)を処理します。log.txtに書き込みます。リダイレクト指定は > でしたから、以前のlog.txtは破棄されます。

> 下記のように変えても同じ結果です。う〜ん・・・。
> test.sh >& log.txt > log.txt

で、これが問題の bash による問題です。zshだとこれは標準出力&エラー出力が記録されるのですが、bashだと謎なことに、エラー出力だけ、ということになると思います。
なんでやねーん、と思うかもしれませんが、こういうことです。

これも分かりにくいので、">& log.txt" を展開します。
> test.sh > log.txt 2>&1 > log.txt

「ディスクリプタの決定」
1) test.shのディスクリプタ(1:標準出力)を、log.txt にします。
2) test.shのディスクリプタ(2:エラー出力)を、ディスクリプタ(1)にします。
3) test.shのディスクリプタ(1:標準出力)を、log.txt にします。この時点ではエラー出力はディスクリプタ1として扱われていません。

結果として、ディスクリプタは以下のようになります。
ディスクリプタ1(標準出力)は、log.txtに記録されます。
ディスクリプタ2(エラー出力)は、log.txtに記録されます。これはディスクリプタ1と合わせる配慮をしません(なぜなら、3)で >& した状態からさらに書き換わったから)。

「出力処理」
3) ディスクリプタ(1:標準出力)を処理します。log.txtに書き込みます。
4) ディスクリプタ(2:エラー出力)を処理します。log.txtに書き込みます。以前のlog.txtは破棄されます。

と、いうような話です。
angel
ぬし
会議室デビュー日: 2005/03/17
投稿数: 711
投稿日時: 2007-02-22 11:58
こんにちは。
引用:
hitoさんの書き込み (2007-02-22 11:17) より:
…(略)…


とりあえず、大幅にダウト。
詳細はまた後ほど。

[ メッセージ編集済み 編集者: angel 編集日時 2007-02-22 11:58 ]
angel
ぬし
会議室デビュー日: 2005/03/17
投稿数: 711
投稿日時: 2007-02-22 13:10
詳細です。

まず、ファイルディスクリプタについて。
bash は標準入力・標準出力・標準エラーの区別を特にはつけていません。bash自身が行う処理を除いて。
勿論、それぞれ 0,1,2 がそうだという慣用を考慮はしていますが。
せいぜい、
  • “<ファイル”と“0<ファイル”を同じように扱う
  • “>ファイル”と“1>ファイル”を同じように扱う
  • “>&ファイル”と“>ファイル 2>&1”を同じように扱う
  • パイプに関しては、出力側 1番・入力側 0番を、リダイレクトに先駆けてつなげる

の特例があるくらいでしょう。

次に、入力・出力の処理について。
bash は入力・出力には関与しません。bashが独自に行う処理を除いて。
あくまで入力・出力を行うのは、bash より起動されたプログラムの仕事であり、bash はプログラム起動に先駆けて、ディスクリプタをセットアップするだけです。
セットアップするのは bash の仕事であるため、起動されるプログラムは、ファイルディスクリプタがどんなファイルに紐付いているかは関与しません。( tty であるかどうか等、種類を判定することはあるでしょうが )
まとめると、
  1. bash が、リダイレクト指定に従い、ディスクリプタをセットアップする
     ( 特に指定がなければ、bash の持っているディスクリプタをそのまま引き継がせる )
  2. bash が、コマンドに指定されたプログラムを起動する
  3. プログラムは、セットアップ済みのディスクリプタを用いて入出力を行う
     ( 処理の順番等は、そのプログラムの仕様による )
     ( プログラムが独自にセットアップするディスクリプタの話はまた別です )

というのが基本の動作です。
※ bashビルトインコマンドの場合や、パイプ、関数、サブシェル等々色々細かい話もあるでしょうが、今回は省略します。

最後にリダイレクト指定の違いについて。
&を使う場合は、書いてある順番が重要です。そうでない場合は順番にあまり意味はありません。
問題になりそうなパターンとして、
  1. “>ファイル 2>&1”と“2>&1 >ファイル”の違い
  2. “>ファイル 2>&1”と“>ファイル 2>ファイル”の違い ( 同一のファイルを指定 )

が考えられます。
  1. 前者は、「1番をファイル(出力)に割り当てた後、2番は1番のコピーに割り当てる」
    後者は、「2番を1番のコピーに割り当てた後、1番はファイル(出力)に割り当てる」
    なので、前者は標準出力・標準エラー共に同じファイルに出力されますが、後者では標準エラー 2番は元の 1番と同じ所、通常の対話実行時は tty に出力されることになります。
  2. 前者は「2番は1番のコピーに割り当てる」
    後者は「2番は(1番と同じ)ファイルに(独自に)割り当てる」
    前者のようにコピーであれば、ファイル上の書込み位置の情報も共有されますので、あたかも 1番=2番 となっているような処理が行われます。
    しかし、後者のように独自に割り当てる場合、出力先が同じファイルであったとしても、書込み位置はバラバラ。出力が競合を起こすことになります。( プログラム内で後から出力された方が上書き )
    以下の実行結果を比べてみれば参考になるでしょう。( 1.txt, 1e.txt, 2.txt, 3.txt, 4.txt, 5.txt ができます )
    コード:
    $ for((i=0,j=5;i<5;i++,j++));do echo -n ab$i;echo -n c$j >&2;done >1.txt 2>1e.txt
    
    $ for((i=0,j=5;i<5;i++,j++));do echo -n ab$i;echo -n c$j >&2;done >2.txt 2>&1
    $ for((i=0,j=5;i<5;i++,j++));do echo -n ab$i; echo -n c$j >&2;done >3.txt 2>3.txt
    $ for((i=0,j=5;i<5;i++,j++));do echo -n c$j >&2; echo -n ab$i;done >4.txt 2>&1
    $ for((i=0,j=5;i<5;i++,j++));do echo -n c$j >&2; echo -n ab$i;done >5.txt 2>5.txt



[ メッセージ編集済み 編集者: angel 編集日時 2007-02-22 14:20 ]
1

スキルアップ/キャリアアップ(JOB@IT)