ファイル記述子の読み取りに失敗しました。

ファイル記述子の読み取りに失敗しました。

この質問は、ファイル記述子を読み書きすることに関するものです。次の例をご覧ください。

#!/bin/sh

file='somefile'

# open fd 3 rw
exec 3<> "$file"

# write something to fd 3
printf "%s\n%s\n" "foo" "bar" >&3

# reading from fd 3 works
cat "/proc/$$/fd/3"

# only works if the printf line is removed
cat <&3

exit 0

スクリプトは以下を出力します。

foo
bar

予想出力:

foo
bar
foo
bar

ファイル記述子を開いて書き込むことに成功しました。読書も同じだproc/$$/fd/3。しかし、これは持ち運びが容易ではないcat <&3何も出力しません。ただし、ファイルディスクリプタが作成されていない場合(たとえば、その行のコメントアウトを削除するなど)はprintf機能します。

なぜ動作しないのか、cat <&3そしてポータブルファイル記述子(POSIXシェル)から内容全体をどのように読み取ることができますか?

答え1

cat <&3ファイルの終わりに達するまで、ファイルから読み取る操作を正確に実行します。呼び出すと、ファイル記述子のファイルの場所は最後に離れた場所、つまりファイルの終わりです。 (読み取りと書き込みのための別の場所ではなく、1つのファイルの場所があります。)

cat /proc/$$/fd/3同じことはしませんcat <&3。別の記述子で同じファイルを開きます。各ファイル記述子には固有の場所があり、ファイルを読み取るために開くと場所が0に設定されるため、このコマンドはファイル全体を印刷し、スクリプトには影響しません。

作成した内容を再度読み取るには、ファイルを再度開くか、ファイル記述子を巻き戻す必要があります(たとえば、場所を0に設定)。 POSIXシェルとほとんどのsh実装にはこれを行う組み込み方法はありません(ksh93に1つあります)。追求すべきユーティリティは1つだけです。ddしかし、今後のみナビゲートできます。 (これから飛び越えることができる他のユーティリティがありますが、これは役に立ちません。)

唯一のポータブルソリューションは、ファイル名を覚えており、必要に応じて何度も開くことだと思います。ファイルが通常のファイルではない場合は、以前のバージョンを確認できない場合がありますのでご注意ください。

答え2

#!/bin/sh

exec 3>file
exec 4<file

printf "%s\n" "foo" "bar" >&3
cat <&4

読み取りと書き込みに別々のファイル記述子を使用すると、ファイル内の別の場所を取得できます。書き込みは読み取り位置を変更しません。

答え3

ksh93を使用すると、以下を見つけることができます。

#!/usr/bin/env ksh93
file='somefile'
exec 3<> "$file"
printf "%s\n%s\n" "foo" "bar" >&3
cat "/proc/$$/fd/3"
exec 3>#((0))
cat <&3
exit 0

私は得る:

foo
bar
foo
bar

予想通り。

答え4

再読み込みしたいファイルディスクリプタで作業している場合は、ここからドキュメント本文に移植できます。

exec 4<somefile 3<<plus
$(    printf %s\\n some random lines
        cat <&4
)
plus
cat <&3

このソリューションは完璧ではないかもしれません。たとえば、ここではドキュメントファイル記述子として見つけることができる移植可能な方法はありません。コマンド置換はcat標準出力に記録されている内容から続く空白行を削除しますが、これらの問題はすべて簡単に処理できます。

まず、リダイレクトの範囲は含まれる複合コマンドに制限されているため、必要な繰り返しを処理するためにループに入れ子にすることは簡単な問題です。コマンドサブメニュー自体内でループを実装したり、必要に応じてインタラクティブ/dev/ttyシェルを呼び出すこともできます。ただし、ほとんどの場合、区切り文書を関数定義にリンクすることをお勧めします。

fn() { : do something w/ fd3
} 3<<INPUT
$1
$(:gen some output;:maybe cat or head <stdin)
INPUT
while IFS= read -r line; do fn "$line"; done

他のheredocによって書かれた内容を読むことも可能です。

cat 4<<SAVED <<AND
$(  cat)
SAVED
$(  printf %s\\n some random lines
     cat <&4)
AND

またはこれを繰り返すことができます...

until test && cat <&4
do exec 3<&4 4<<IN
$(: gen output; cat <&3)
IN
done 4<infile

もちろん、fn内部で上記のようなものを呼び出すこともできます。

末尾の空行問題について -もしこれは問題です。その後、echo .コマンドの最後にsubを追加し、fdを読みながら最後の行を削除したことを確認してくださいsed \$d <&"$fd"

しかし、かなり安全ですが、シェルでこれらのファイルを繰り返すのは一般的に悪い考えです。生成された各ファイルには少なくとも1つのフォークがあることに注意してください。シェルはfdsを割り当て、ユーティリティはそれを処理します。sedまたは、スクリプトでループを実行し、awk標準ユーティリティを使用してファイルデータを操作し、シェルを使用して出力として指定します。これは一般的にベストプラクティスです。

関連情報