次のようなコマンドがよく知られています:
cat filename | some_sed_command >filename
出力リダイレクトがコマンドの前に実行されると、ファイルfilenameが消去され、ファイル名が切り捨てられます。
次の方法で問題を解決できます。
cat file | some_sed_command | tee file >/dev/null
しかし、これがどのような場合でも機能するかどうかはわかりません。ファイル(およびsedコマンドの結果)が非常に大きい場合はどうなりますか?オペレーティングシステムは、まだ読み取られていないコンテンツを上書きすることをどのように回避できますか?どのような場合でも動作するスポンジコマンドもあることがわかります。Tシャツよりも「安全」ですか。
次の方法で問題を解決できます。
cat file | some_sed_command | tee file >/dev/null
いいえ。
file
は切り捨てられますが、cat file | some_sed_command | tee file >/dev/null
がfile
を切り捨てないという保証はありません。
それはすべて、予想されるものとは対照的に、最初に処理されるコマンドに依存します。 パイプ内のコマンドは左から右に処理されません 。どのコマンドが最初に選択されるかについての保証はありません。したがって、ランダムに選択されたと考えて、neverシェルが問題のあるコマンドを選択しないことに頼ることもできます。
3つのコマンド間で最初に問題のコマンドが選択される可能性は、2つのコマンド間で最初に問題のコマンドが選択される可能性よりも低いため、file
が切り捨てられる可能性は低くなりますが、それでも起こります。
script.sh
:
#!/bin/bash
for ((i=0; i<100; i++)); do
cat >file <<-EOF
foo
bar
EOF
cat file |
sed 's/bar/baz/' |
tee file >/dev/null
[ -s file ] &&
echo 'Not truncated' ||
echo 'Truncated'
done |
sort |
uniq -c
rm file
% bash script.sh
93 Not truncated
7 Truncated
% bash script.sh
98 Not truncated
2 Truncated
% bash script.sh
100 Not truncated
neverはcat file | some_sed_command | tee file >/dev/null
のようなものを使用します。 Oliが提案したようにsponge
を使用します。
代替として、より厳しい環境および/または比較的小さなファイルの場合、here文字列とコマンド置換を使用して、コマンドを実行する前にファイルを読み取ることができます:
$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
特にsed
には、その-i
インプレース引数を使用できます。開いたファイルに保存するだけです。例:
sed -i 's/ /-/g' filename
あなたがsed
以上をしていると仮定して、より強力な何かをしたい場合は、sponge
(moreutils
パッケージから)ですべてをバッファリングすることができます。 tee
に似ていますが、機能はほとんどありません。ただし、基本的な使用方法については、ドロップインの代わりになります。
cat file | some_sed_command | sponge file >/dev/null
それは安全ですか?絶対に。おそらく制限がありますので、巨大な(sedでインプレース編集できない)場合は、2番目のファイルを編集してから、そのファイルをmv
して元のファイル名に戻したい場合があります。これはアトミックである必要があります(したがって、これらのファイルに依存するものは、常にアクセスする必要がある場合に壊れません)。
ああ、しかしsponge
が唯一のオプションではありません。これを適切に機能させるためにmoreutils
を取得する必要はありません。次の2つの要件を満たす限り、どのメカニズムも機能します。
OPが言及しているよく知られている問題は、パイプラインでコマンドの実行を開始する前にパイプが機能するために必要なすべてのファイルをシェルが作成することであるため、実際に切り捨てられるのはシェルですコマンドのいずれかが実行を開始する前に、出力ファイル(残念ながら入力ファイルでもあります)。
tee
コマンドは、最初の要件を満たしていても機能しません。2番目の要件を満たさないためです。開始直後に常に出力ファイルを作成するため、基本的にパイプを作成するのと同じくらい悪いです。出力ファイルに。 (実際には、出力ファイルが切り捨てられる前に非決定的なランダム遅延が発生するため、実際にはそうではないが、実際には機能しないと思われるかもしれません。)
したがって、この問題を解決するために必要なのは、出力を生成する前にすべての入力をバッファリングし、出力ファイル名をパラメータとして受け入れることができるコマンドであり、出力をパイプする必要はありません出力ファイル。そのようなコマンドの1つはshuf
です。したがって、以下はsponge
と同じことを実現します。
shuf --output=file --random-source=/dev/zero
--random-source=/dev/zero
部分はshuf
をだましてシャッフルを一切行わずにその処理を実行するため、入力を変更せずにバッファリングします。
ExモードでVimを使用できます。
ex -sc '%!some_sed_command' -cx filename
%
すべての行を選択
!
コマンドを実行
x
保存して終了