IOリダイレクトとヘッドコマンド


9

.hgignore今日、Cygwin bashシェルからファイルをすばやく編集しようとしていたところ、誤りのある行が追加されました。これが最善の方法であるかどうかはわかりませんがhead -1 .hgignore、問題のある行を削除する方法をすぐに考えました(以前はファイルに1行しかありませんでした)。案の定、実行すると最初の行だけが出力されます。

しかし、出力をリダイレクトしてを使用してファイルを書き換えようとするhead -1 .hgignore > .hgignoreと、ファイルは空でした。なぜこれが起こるのですか?代わりに追加しようとするとhead -1 .hgignore >> .hgignore、正しく追加されますが、これは明らかに望ましい結果ではありません。この場合、切り捨てリダイレクトが機能しないのはなぜですか?


回答:


10

シェルが次のようなコマンドラインを取得するとcommand > file.out、シェル自体がという名前のファイルを開きます(おそらく作成します)file.out。シェルは、ファイル記述子0を、オープンから取得したファイルファイル記述子に設定します。これがI / Oリダイレクションの仕組みです。すべてのプロセスはファイル記述子0、1、2を認識しています。

これについての難しい部分は、開く方法file.outです。ほとんどの場合file.out、オフセット0(つまり切り捨て)で書き込み用にオープンしたいので、シェルがこれを行います。.hgignoreを切り捨て、書き込み用に開き、filedescriptorを0にdupし、次にexecしましたhead。インスタントファイルクローバー。

bashシェルでは、set noclobberこの動作を変更するためにa を実行します。


ああ、なるほど。シェルがコマンドを実行する前にファイルを切り捨てていると思いましたが、理由はわかりませんでした。説明ありがとう!
voithos

10

ブルースはシェルパイプラインでここ起こっていることに答えると思います。

私のお気に入りの小さなユーティリティの1つは、moreutilsspongeからのコマンドです。ターゲット出力ファイルを開いてデータを書き込む前に、使用可能なすべての入力を「吸収」することにより、この問題を正確に解決します。これにより、期待したとおりにパイプラインを記述できます。

$ head -1 .hgignore | sponge .hgignore

貧乏人の解決策は、出力を一時ファイルにパイプして、次にパイプラインが完了した後(たとえば、実行する次のコマンド)は、一時ファイルを元のファイルの場所に戻すことです。

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}

数年後、これを見て、私は思いつきましたhead -1 .hgignore | tee .hgignoreteecoreutilsにあり、特典/副作用として、これはSTDOUTにも書き込みます
voithos

@voithos私の知るtee限り、他のすべてと同様に、インスタンス化されたときに書き込み先のファイルを開いて切り捨てるので、書き込みでファイルを切り捨てる前にファイルの内容を読み取る際の競合状態の主要な問題は解決されません。
カレブ2014年

私が気付いていなかったポイントを実際にあなたは持ち出します-つまり、パイプされたコマンドは順次ではなく即座に開始されます。それは正確ですか?私は、しかし、それをテストしてなかったtee ようだ希望ことを行います。8.13私のマシンにはバージョンがあります。
voithos 2014年

1
@voithosはい、パイプラインのコマンドと関連するすべての入出力チャネルが逆の順序で開始されるため、最初のパイプラインがデータを送信し始めると、パイプラインはデータを受信できるようになります。おそらく小さすぎるデータのチャンクを使用し、それが必要になる前にすべてが読み取りバッファーにキャッシュされたため、テストに欠陥があると思います。teeプログラムは、彼らをバッファリング倍にする設定はありませんが、あなたのファイルを切り捨てます。
カレブ2014年

3

head -n 1 file > file

filehead開始される前に切り捨てられますが、それを書き込んだ場合:

head -n 1 file 1<> file

それはないようだfile読み書きモードで開かれています。ただし、head書き込みが終了してもファイルは切り捨てられないため、上の行は何もしません(head最初の行を上書きし、他の行はそのままにしておきます)。

ただし、headが戻った後、fdが開いている間は、を実行する別のコマンドを呼び出すことができますtruncate

例えば:

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

ここで重要なのは、truncate上記のようにhead、ファイル内の最初の行の直後にあるfd 1のカーソルを移動するだけです。必要のない最初の行を書き換えますが、問題はありません。

POSIXヘッドを使用すると、その最初の行を書き直すことなく、実際に逃げることができます。

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

ここではhead、標準入力でカーソル位置を移動するという事実を使用しています。一方でhead一般的にパフォーマンスを向上させるために、大きな塊でその入力を読んでいました、POSIXがする(可能な場合)、それを必要とするseekことは、それを超えて行っていた場合は、単に最初の行の後にバックします。ただし、すべての実装がこれを行うわけではないことに注意してください。

または、readこの場合は代わりにシェルのコマンドを使用できます。

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file

1
ステファン、上記STDINを使用して達成したものと同様に切り捨てられる標準またはcoreutilsコマンドを知っていますかperl
iruvar

2
@ 1_CR、いいえ。ただし、ファイル内のdd任意の絶対オフセットで切り捨てることができます。あなたがそこから2番目の行と切り捨てのバイトオフセットを決定することができますので、dd bs=1 seek="$offset" of=file
ステファンChazelas

1

リアルマンの解決策は

ed .hgignore
$d
wq

またはワンライナーとして

printf '%s\n' '$d' 'wq' | ed .hgignore

またはGNU sedを使用:

sed -i '$d' .hgignore

(いいえ、冗談です。対話型エディターを使用します。vi .hgignore GddZZ


私は疑問に思いました、:wq以上を使用することには何か利点がありますZZか?
voithos

また、:xこれは私の指が自動的に行うこと
です


ZZと:xは、書き込むものがある場合にのみ書き込みます。:wは、必要かどうかに関係なく、常にファイルをディスクにfsyncします。タブを使用しているので、私は:xaを使用します。
xenoterracide


0

インプレースファイル編集の場合は、JürgenHötzelがsed 's / c / d /' myFileからmyFileへのリダイレクト出力で示しているように、オープンファイルハンドルトリックを使用することもできます。

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed

2
そして、rm .hgignoreあなたの力が落ちた直後に、何時間ものハードワークを奪います。OK、それは問題ではありません.hgignoreが、なぜとにかく複雑なことをするのですか?したがって、私の反対票:技術的には正しいが、非常に悪い考えです。
Gilles「SO-邪悪なことをやめよう」

@ギルズ、おそらくあまり良いアイデアではないかもしれませんが、それはたとえばperl -i(インプレイス編集の場合)何をするかであり、いくつかの実装sed -iがそれを行っても驚くことはありません(ただし、最新バージョンのGNU sedはそうではありません)。
ステファンChazelas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.