🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

シェルスクリプト

シェルスクリプトは、UNIX系のOSもしくはコマンドラインインタプリタ向けに記述されたスクリプト。bash/zshといったシェルによって実行されるため、このように呼ばれています。バッチ処理などに使用されており、テキストファイルに書かれた命令を順に実行します。

Q&A

解決済

1回答

635閲覧

bashスクリプト実行時のファイルディスクリプタの振る舞いについて

bpdjb3

総合スコア1

bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

シェルスクリプト

シェルスクリプトは、UNIX系のOSもしくはコマンドラインインタプリタ向けに記述されたスクリプト。bash/zshといったシェルによって実行されるため、このように呼ばれています。バッチ処理などに使用されており、テキストファイルに書かれた命令を順に実行します。

0グッド

0クリップ

投稿2021/03/15 19:16

編集2021/03/16 20:58

概要

現在,シェルスクリプトについて勉強しています.些細なことですが,コマンドを個別で実行してリダイレクトさせた場合と,スクリプト化したものを一括で実行してリダイレクトさせた場合とで実行結果が異なっており,もやもやしています.実行結果が異なる理由と私の理解が誤っている点があればご教授いただきたく考えています.

検証内容と結果

コマンドを個別で実行してリダイレクトさせた場合(i)とスクリプト化したものを一括で実行してリダイレクトさせた場合(ii)とでterminalに出力される結果とリダイレクト先のテキストファイルに出力される結果を確認し,比較しました.

コマンドを個別で実行してリダイレクトさせた場合(i)

terminal

1#(i)-(i)echoコマンドの標準出力結果の参照先(ファイルディスクリプタ1)をtest1.txtに変更 2~/testdir$ echo test-std 1>test1.txt 3~/testdir$ cat test1.txt 4test-std 5#(i)-(ii)echoコマンドの標準エラー出力結果の参照先(ファイルディスクリプタ2)をtest2.txtに変更 6~/testdir$ echo test-std 2>test2.txt 7test-std 8~/testdir$ cat test2.txt 9#(i)-(iii)echoコマンドの標準出力結果の参照先(ファイルディスクリプタ1)を標準エラー出力に変更後,ファイルディスクリプタ1の参照先をtest3.txtに変更 10~/testdir$ echo test-err 1>&2 1>test3.txt 11~/testdir$ cat test3.txt 12test-err 13#(i)-(iv)echoコマンドの標準出力結果の参照先(ファイルディスクリプタ1)を標準エラー出力に変更後,ファイルディスクリプタ2の参照先をtest4.txtに変更 14~/testdir$ echo test-err 1>&2 2>test4.txt 15test-err 16~/testdir$ cat test4.txt

上記のそれぞれの検証内容と結果に対して,想定していたファイルディスクリプタの参照先の変化と出力結果は以下の(i)-(i)~(i)-(iv)に示したそれぞれの表の通りです.いずれの結果もファイルディスクリプタが予想通りの参照先を指定しており,かつ予想通りの結果を出力しています.

(i)-(i).

ファイルディスクリプタecho実行直後1>test1.txt
1標準出力 (出力:test-std)test1.txt (出力:test-std)
2標準エラー出力(出力なし)標準エラー出力(出力なし)
出力先予想結果テスト結果
terminal
test1.txttest-stdtest-std

テスト結果ではtest1.txtにtest-stdが出力されており,terminalには何も出力されていないため,上記の予想結果と一致しています.

(i)-(ii).

ファイルディスクリプタecho実行直後2>test2.txt
1標準出力 (出力:test-std)標準出力 (出力:test-std)
2標準エラー出力(出力なし)test2.txt(出力なし)
出力先予想結果テスト結果
terminaltest-stdtest-std
test2.txt

テスト結果ではtest2.txtに何も出力されておらず,terminalにはtest-stdが出力されているため,上記の予想結果と一致しています.

(i)-(iii).

ファイルディスクリプタecho実行直後1>&21>test3.txt
1標準出力 (出力:test-err)標準エラー出力 (出力:test-err)test3.txt (出力:test-err)
2標準エラー出力(出力なし)標準エラー出力(出力なし)標準エラー出力(出力なし)
出力先予想結果テスト結果
terminal
test3.txttest-errtest-err

テスト結果ではtest3.txtにはtest-errが出力されており,terminalには何も出力されていないため,上記の予想結果と一致しています.

(i)-(iv).

ファイルディスクリプタecho実行直後1>&22>test4.txt
1標準出力 (出力:test-err)標準エラー出力 (出力:test-err)標準エラー出力 (出力:test-err)
2標準エラー出力(出力なし)標準エラー出力(出力なし)test4.txt(出力なし)
出力先予想結果テスト結果
terminaltest-errtest-err
test4.txt

テスト結果ではtest4.txtには何も出力されておらず,terminalにはtest-errが出力されているため,上記の予想結果と一致しています.

スクリプト化したものを一括で実行してリダイレクトさせた場合(ii)

問題は,上記のechoコマンドを一つのスクリプトにまとめ込み,スクリプトを実行させた場合です.スクリプトは以下のとおりです.なお,スクリプト名はtest.shです.

#!/bin/bash echo "test-std" echo "test-err" 1>&2

このスクリプトに対する検証内容と結果を以下に示します.

terminal

1#(ii)-(i)スクリプトの標準出力結果の参照先(ファイルディスクリプタ1)をtest6.txtに変更 2~/testdir$ ./test.sh 1> test6.txt 3test-err 4~/testdir$ cat test6.txt 5test-std 6#(ii)-(ii)スクリプトの標準エラー出力結果の参照先(ファイルディスクリプタ2)をtest5.txtに変更 7~/testdir$ ./test.sh 2> test5.txt 8test-std 9~/testdir$ cat test5.txt 10test-err

上記のそれぞれの検証内容と結果に対して,想定していたファイルディスクリプタの参照先の変化と出力結果は以下の(ii)-(i)と(ii)-(ii)に示したそれぞれの表の通りです.

(ii)-(i).

ファイルディスクリプタスクリプト(1行目)スクリプト(2行目)スクリプト実行直後1>test6.txt
1標準出力 (出力:test-std)標準エラー出力 (出力:test-err)標準出力(出力:test-std)と標準エラー出力(出力:test-err)test6.txt (出力:test-stdとtest-err)
2標準エラー出力(出力なし)標準エラー出力(出力なし)標準エラー出力(出力なし)標準エラー出力(出力なし)
出力先予想結果テスト結果
terminaltest-err
test6.txttest-stdとtest-errtest-std

テスト結果と比較すると,テスト結果ではtest6.txtにtest-stdのみが出力されており,terminalにはtest-errが出力されています.

(ii)-(ii).

ファイルディスクリプタスクリプト実行中(1行目)スクリプト実行中(2行目)スクリプト実行直後2>test5.txt
1標準出力 (出力:test-std)標準エラー出力 (出力:test-err)標準出力(出力:test-std)と標準エラー出力(出力:test-err)標準出力(出力:test-std)と標準エラー出力(出力:test-err)
2標準エラー出力(出力なし)標準エラー出力(出力なし)標準エラー出力(出力なし)test5.txt (出力なし)
出力先予想結果テスト結果
terminaltest-stdとtest-errtest-std
test5.txttest-err

テスト結果と比較すると,テスト結果ではtest5.txtにtest-errのみが出力されており,terminalにはtest-stdが出力されています.

いずれの結果も出力結果test-errの参照先が予想とテスト結果で異なっており,スクリプト実行直後にファイルディスクリプタ2の参照先となっていれば,予想とテスト結果が合致すると言えます.そこでスクリプト2行目の動作を見直してみると,echoでstd-errを標準出力として出力した後に(std-errのファイルディスクリプタは1),1>&2の操作によって,ファイルディスクリプタ1の参照先を標準出力エラーとしているため,やはり標準出力エラーとしてstd-err(ファイルディスクリプタは1)が出力されると予想されます.また,コマンドを個別で実行してリダイレクトさせた場合(i)ではそのように動作していることから,恐らくこの理解で問題がないと予想されます.

質問事項

以上のようにコマンドを個別で実行してリダイレクトさせた場合と,スクリプト化したものを一括で実行してリダイレクトさせた場合とで異なる実行結果が得られています.そこで,以下の2点をご教授いただきたいです.

  1. なぜこうなるのでしょうか.
    例えば,スクリプトで一括で実行してリダイレクトする場合と,個別で実行してリダイレクトする場合とで,途中の処理が異なるのでしょうか.

  2. コマンドの実行からリダイレクトについて,上記の理解であっているのでしょうか.もし,間違っている箇所があれば,ご教授いただきたいです.

宜しくお願いいたします.

補足情報(実行環境)

Ubuntu 20.04.2 LTS
デフォルトシェル /bin/bash

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2021/03/16 14:54

otn さんの回答に加えて、次の 2 つの違いを考えれば良いかと。(シェルスクリプトは後者と同様になります。) ・echo test 1>&2 2>file ・echo test 2>file 1>&2 あと、質問文の書き方ですが、「予想と一致する・異なる」と言いつつ予想そのものが書いてないので読みにくく感じました。
bpdjb3

2021/03/16 20:59

コメントをいただき,ありがとうございます. 二つのコマンドの違いについて,リダイレクトの順番が大切であるということでしょうか. 上記のコマンドは以下のように振る舞うと認識したのですが. 前者のコマンドではファイルディスクリプタの参照先が以下のように振る舞い,echoコマンドはFD1にtestを出力するので,端末上にtestを出力する. FD1:端末(初期状態)→端末(1>&2)→端末(2>file) FD2:端末(初期状態)→端末(1>&2)→file(2>file) 一方で,後者のコマンドではファイルディスクリプタの参照先が以下のように振る舞い,echoコマンドはFD1にtestを出力するので,fileにtestを出力する. FD1:端末(初期状態)→端末(2>file) →file(1>&2) FD2:端末(初期状態)→file(2>file) →file(1>&2) >>あと、質問文の書き方ですが、「予想と一致する・異なる」と言いつつ予想そのものが書いてないので読みにくく感じました。 ありがとうございます.ぜひ今後の質問をする際に参考にさせていただきます.
guest

回答1

0

ベストアンサー

リダイレクトの理解が間違っています。根本的に間違っているのか、途中から理解がおかしいのかは不明。

まず、表ですが「echo実行直後」という見出しがおかしい。リダイレクトは実行前になされます。

コマンド実行前に、リダイレクト処理を左から順に全部行い、それからコマンドを実行します。
echoはファイルディスクリプタ1に文字列を表示するコマンドなので、全てのリダイレクト処理後のファイルディスクリプタ1の割り当て先に文字列を出力します。

(i)-(i).

FDリダイレクト実行前1>test1.txt
1端末ファイルtest1.txt
2端末端末

で、ファイルにtest-stdが出力されます。端末出力は無し。

(i)-(ii).

FDリダイレクト実行前2>test2.txt
1端末端末
2端末ファイルtest2.txt

で、端末にtest-stdが出力されます。ファイルは空です。

(i)-(iii).

FDリダイレクト実行前1>&21>test3.txt
1端末端末ファイルtest3.txt
2端末端末端末

で、ファイルにtest-errが出力されます。端末出力は無し。

(i)-(iv).

FDリダイレクト実行前1>&22>test4.txt
1端末端末端末
2端末端末ファイルtest4.txt

で、端末にtest-errが出力されます。ファイルは空です。

(ii)-(i)
スクリプトの外側(実行前)でリダイレクト処理がされているので、初期状態が異なります。

FDリダイレクト実行前1>&2
1行目の1ファイルtest6.txt対象外なので同左
1行目の2端末対象外なので同左
2行目の1ファイルtest6.txt端末
2行目の2端末端末

で、ファイルにtest-std、端末にtest-errが出力されます。

(ii)-(ii)
スクリプトの外側(実行前)でリダイレクト処理がされているので、初期状態が異なります。

FDリダイレクト実行前1>&2
1行目の1端末対象外なので同左
1行目の2ファイルtest5.txt対象外なので同左
2行目の1端末ファイルtest5.txt
2行目の2ファイルtest5.txtファイルtest5.txt

で、端末にtest-std、ファイルにtest-errが出力されます。

投稿2021/03/15 23:45

otn

総合スコア85882

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bpdjb3

2021/03/16 20:13

ご回答いただき,ありがとうございます. コマンドの実行前にリダイレクト処理によって,ファイルディスクリプタの参照先が決定するということが大きく理解できていないことだったと理解しました.また,スクリプト内にリダイレクト処理が含まれている場合,スクリプトの外側のリダイレクト処理によって,ファイルディスクリプタの参照先が決定した後に,さらにスクリプト内のリダイレクト処理によって,ファイルディスクリプタの参照先が決定するということも理解しました. >>リダイレクトの理解が間違っています。根本的に間違っているのか、途中から理解がおかしいのかは不明。 ご回答をいただくまでは,リダイレクトを"コマンドの実行結果の出力先を変更する操作"と理解していましたが,ご回答をいただいた後は,"ファイルディスクリプタの参照先をコマンドの実行前に変更する操作"と理解しました. もし,よろしければ以下のご質問にもご回答いただければ幸いです. この理解はotnさんのご理解と一致していますか.一致しないようでしたら,otnさんのご理解をご教授いただきたいです.
otn

2021/03/17 03:12

お書きの理解で正しいです。 リダイレクトやパイプ、* によるファイル名マッチ、変数の展開などはシェルの機能であり、コマンド実行に先立って行われます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問