web-dev-qa-db-ja.com

ファイル記述子はどのように機能しますか?

誰かがこれがうまくいかない理由を教えてもらえますか?ファイル記述子で遊んでいますが、少し迷っています。

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

最初の3行は正常に実行されますが、最後の2行はエラーになります。どうして?

66
Trcx

ファイル記述子0、1、および2は、それぞれstdin、stdout、およびstderr用です。

ファイル記述子3、4、.. 9は追加ファイル用です。それらを使用するには、まずそれらを開く必要があります。例えば:

exec 3<> /tmp/foo  #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.

詳細については、 高度なBashスクリプトガイド:第20章I/Oリダイレクト をご覧ください。

97
dogbane

これは古い質問ですが、説明が必要なことです。

Carl Norumとdogbaneの答えは正しいですが、仮定はスクリプトを変更して動作するようにすることですです。

私が指摘したいのは、スクリプトを変更する必要はありません

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

別の方法で呼び出すと機能します。

./fdtest 3>&1 4>&1

これは、ファイル記述子3および4を1(標準出力)にリダイレクトすることを意味します。

ポイントはスクリプトは完全に問題ありません1と2(stdoutとstderr)以外の記述子に書き込みたい場合それらの記述子が親プロセスによって提供される場合.

このスクリプトは4つの異なるファイルに書き込むことができるため、実際には非常に興味深い例です。

./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt

これで、出力が4つの個別のファイルになりました。

$ for f in file*; do echo $f:; cat $f; done
file1.txt:
This
file2.txt:
is
file3.txt:
a
file4.txt:
test.

より興味深いとは、プログラムが実際にファイルを開かないため、それらのファイルへの書き込み許可が必要ないことです。

たとえば、Sudo -sを実行してユーザーをrootに変更し、rootとしてディレクトリを作成し、次のコマンドを通常のユーザー(この場合はrsp)として次のように実行しようとします。

# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt'

エラーが発生します:

bash: file1.txt: Permission denied

しかしsuの外部でリダイレクトを行う場合:

# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt

(一重引用符の違いに注意してください)動作しますそして、私は得る:

# ls -alp
total 56
drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./
drwxrwxr-x 3 rsp  rsp  4096 Jun 23 15:01 ../
-rw-r--r-- 1 root root    5 Jun 23 15:05 file1.txt
-rw-r--r-- 1 root root   39 Jun 23 15:05 file2.txt
-rw-r--r-- 1 root root    2 Jun 23 15:05 file3.txt
-rw-r--r-- 1 root root    6 Jun 23 15:05 file4.txt

これは、ルートが所有するディレクトリ内のルートが所有する4つのファイルです-スクリプトにアクセス許可がなかった場合でもそれらのファイルを作成します。

別の例は、chroot jailまたはコンテナを使用して、rootとして実行されてもそれらのファイルにアクセスできない場所でプログラムを実行し、実際にファイル全体にアクセスすることなく、必要な場所でそれらの記述子を外部にリダイレクトしますこのスクリプトにシステムまたは他の何か。

ポイントはあなたは非常に興味深く有用なメカニズムを発見したです。他の回答で提案されているように、スクリプト内のすべてのファイルを開く必要はありません。スクリプトの呼び出し中にそれらをリダイレクトすると便利な場合があります。

まとめると、これ:

echo "This"

実際には次と同等です:

echo "This" >&1

プログラムを次のように実行します。

./program >file.txt

次と同じです:

./program 1>file.txt

数字の1は単なるデフォルトの数字であり、標準出力です。

しかし、このプログラムでも:

#!/bin/bash
echo "This"

「不正な記述子」エラーが発生する可能性があります。どうやって?として実行する場合:

./fdtest2 >&-

出力は次のようになります。

./fdtest2: line 2: echo: write error: Bad file descriptor

>&-1>&-と同じ)を追加すると、標準出力を閉じることを意味します。 2>&-を追加すると、stderrを閉じることになります。

より複雑なこともできます。元のスクリプト:

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

だけで実行した場合:

./fdtest

プリント:

This
is
./fdtest: line 4: 3: Bad file descriptor
./fdtest: line 5: 4: Bad file descriptor

ただし、記述子3と4を機能させることはできますが、1番を実行すると失敗します。

./fdtest 3>&1 4>&1 1>&-

以下を出力します:

./fdtest: line 2: echo: write error: Bad file descriptor
is
a
test.

1と2の両方の記述子を失敗させたい場合は、次のように実行します。

./fdtest 3>&1 4>&1 1>&- 2>&-

あなたが得る:

a
test.

どうして?何も失敗しませんでしたか? しましたしかし、stderrはありません(ファイル記述子番号2)エラーメッセージは表示されませんでした!

この方法を実験して、記述子とそのリダイレクトがどのように機能するかを理解することは非常に役立つと思います。

あなたのスクリプトは確かに非常に興味深い例です-そして、私はそれを主張しますそれはまったく壊れていません、あなたはちょうどそれを間違って使っていました!:)

57
rsp

これらのファイル記述子は何もポイントしていないため、失敗しています!通常のデフォルトのファイル記述子は、標準入力0、標準出力1、および標準エラーストリーム2。スクリプトは他のファイルを開かないため、他の有効なファイル記述子はありません。 execを使用して、bashでファイルを開くことができます。以下に例を変更します。

#!/bin/bash
exec 3> out1     # open file 'out1' for writing, assign to fd 3
exec 4> out2     # open file 'out2' for writing, assign to fd 4

echo "This"      # output to fd 1 (stdout)
echo "is" >&2    # output to fd 2 (stderr)
echo "a" >&3     # output to fd 3
echo "test." >&4 # output to fd 4

そして今、それを実行します:

$ ls
script
$ ./script 
This
is
$ ls
out1    out2    script
$ cat out*
a
test.
$

ご覧のとおり、追加の出力が要求されたファイルに送信されました。

18
Carl Norum

@ rattClimbs からの回答の rspからの回答 および コメント内の質問に回答 に追加します。

ファイル記述子が開いているかどうかを早めにリダイレクトして、失敗した場合は、目的の番号付きファイル記述子を/dev/nullなどのように開きます。スクリプト内で定期的にこれを行い、追加のファイル記述子を活用して、return #を超えて追加の詳細または応答を返します。

script.sh

#!/bin/bash
2>/dev/null >&3 || exec 3>/dev/null
2>/dev/null >&4 || exec 4>/dev/null

echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

Stderrは/dev/nullにリダイレクトされ、可能なbash: #: Bad file descriptor応答を破棄し、||を使用して、前のコマンドがゼロ以外のステータスで終了するときにexec #>/dev/nullコマンドを処理します。ファイル記述子が既に開かれている場合、2つのテストはゼロステータスを返し、exec ...コマンドは実行されません。

リダイレクトなしでスクリプトを呼び出すと、次の結果が得られます。

# ./script.sh
This
is

この場合、atestのリダイレクトは/dev/nullに出荷されます。

定義されたリダイレクトを使用してスクリプトを呼び出すと、次のようになります。

# ./script.sh 3>temp.txt 4>>temp.txt
This
is
# cat temp.txt
a
test.

最初のリダイレクト3>temp.txtはファイルtemp.txtを上書きし、4>>temp.txtはファイルに追加します。

最終的に、/dev/null以外の何かが必要な場合は、スクリプト内でリダイレクトするデフォルトファイルを定義できます。または、スクリプトの実行方法を変更して、追加のファイル記述子を任意の場所にリダイレクトできます。

0
AGipson