ファイルをBashで上書きしたい場合、これは簡単です
echo "Hello world" > hosts
これはファイル記述子では機能しないようです
$ exec 3<> hosts
$ echo "Hello world" >&3
$ cat hosts
Hello world
$ echo "Hello world" >&3
$ cat hosts
Hello world
Hello world
ファイルをBashで上書きしたい場合、これは簡単です
echo "Hello world" > hosts
これはファイル記述子では機能しないようです
$ exec 3<> hosts
$ echo "Hello world" >&3
$ cat hosts
Hello world
$ echo "Hello world" >&3
$ cat hosts
Hello world
Hello world
そのとおりです。ファイルを開くモードは、シェルが を呼び出すときに決定されますopen(2)
。FDをDUP2
(任意の言語で) 開いている場合、ファイルを開いたときに設定されたフラグは、開いている FD 間で共有されます。あなたの場合、O_TRUNC
ファイルが実際に開かれたときにのみ指定できます。
知っておくべき重要なことは、モードとさまざまなフラグが決定されるのは、ファイルが<file
、>file
、または類似のものを使用して開かれたときだけであるということです。修飾子を使用して FD をコピーすると&
、元の FD を指す「エイリアス」が基本的に作成され、元の FD と同じ状態がすべて保持されます。ファイルを切り捨てるには、ファイルを再度開く必要があります。
ファイル記述子を簡単に試してみたい場合は、これが私のデバッグ機能です。
lsfd() {
local ofd=${ofd:-2} target=${target:-$BASHPID}
while [[ $1 == -* ]]; do
if [[ -z $2 || $2 == *[![:digit:]]* ]]; then
cat
return 1
fi
case ${1##+(-)} in
u)
shift
ofd=$1
shift
;;
t)
shift
target=$1
shift
;;
h|\?|help)
cat
return
esac
done <<EOF
USAGE: ${FUNCNAME} [-h|-?|--help] [-u <fd>] [ -t <PID> ] [<fd1> <fd2> <fd3>...]
This is a small lsof wrapper which displays the open
file descriptors of the current BASHPID. If no FDs are given,
the default FDs to display are {0..20}. ofd can also be set in the
environment.
-u <fd>: Use fd for output. Defaults to stderr. Overrides ofd set in the environment.
-t <PID>: Use PID instead of BASHPID. Overrides "target" set in the environment.
EOF
IFS=, local -a 'fds=('"${*:-{0..20\}}"')' 'fds=("${fds[*]}")'
lsof -a -p $target -d "$fds" +f g -- >&${ofd}
}
stdin を閉じたくないのは、stdin を閉じると問題が発生する場合があるためです。最初に保存されます。
$ ( { lsfd 3; cat <&3; } {savefd}<&0 <<<'hi' 3>&0- <&"${savefd}" )
COMMAND PID USER FD TYPE FILE-FLAG DEVICE SIZE/OFF NODE NAME
bash 920 ormaaj 3r REG LG 0,22 3 59975426 /tmp/sh-thd-8305926351 (deleted)
hi
ご覧のとおり、3>&0-
オペレーターを使用して FD 0 を 3 に移動しても、ファイルは開かれたままO_RDONLY
です。>
orの選択は<
コピー記述子では任意であり、演算子の左側の FD が省略されている場合にのみデフォルトを決定するのに役立ちます。
新しい独立した FD を開きたい場合は、次のようなものが機能します。
$ ( {
cat <&4 >/dev/null; lsfd 3 4; echo there >&4; cat </dev/fd/3
} {savefd}<&0 <<<'hi' 3>&0- 4<>/dev/fd/3 <&"${savefd}"
)
COMMAND PID USER FD TYPE FILE-FLAG DEVICE SIZE/OFF NODE NAME
bash 2410 ormaaj 3r REG LG 0,22 3 59996561 /tmp/sh-thd-8305914274 (deleted)
bash 2410 ormaaj 4u REG RW,LG 0,22 3 59996561 /tmp/sh-thd-8305914274 (deleted)
hi
there
これで、FD 4 は、単に複製されるのではなく、FD 3 が指しているファイルに対して実際に「再オープン」されます (上記のように、ファイルが既にunlink(2)
'd' されている場合でも)。最初に FD 4 が開かれ、 を使用して最後までシークされcat
、次に追加の行がファイルに書き込まれます。一方、FD 3 のシーク位置はまだ先頭にあるため、結果のファイル全体を端末にカテゴライズできます。
同じ原則があなたのケースにも適用できます。
$ { echo 'Hello world'; echo 'hi' >/dev/fd/1; } >hosts; cat hosts
hi
または、2 つの別々のコマンドを使用して、ファイルを 2 回開いて閉じる方がよいでしょうexec
。
exec
絶対に必要でない限り、ファイル記述子を開くためだけに使用することは避けたいと思います。ファイルを明示的に閉じることを忘れないでください。代わりにコマンドのグループ化を使用すると、ファイルは自動的に閉じられ、実際には「スコープ」が与えられます。これは原理的には Python のwith
ステートメントに似ています。これで多少の混乱は防げたかもしれません。
も参照してください。