ファイルの最後の行を除くすべての行を削除しようとしましたが、file.txt は空ではありませんが、次のコマンドは機能しませんでした。
$cat file.txt |tail -1 > file.txt
$cat file.txt
なぜそうなのですか?
パイプラインを介してファイルから同じファイルにリダイレクトすることは安全ではありません。最初のステージの読み取りを開始するfile.txt
前に、パイプラインの最後のステージを設定するときにシェルによって上書きされるtail
と、空の出力になります。
代わりに次の操作を行います。
tail -1 file.txt >file.txt.new && mv file.txt.new file.txt
...まあ、実際には、製品コードではそうしないでください。特に、セキュリティが重視される環境で root として実行している場合は、次の方法がより適切です。
tempfile="$(mktemp file.txt.XXXXXX)"
chown --reference=file.txt -- "$tempfile"
chmod --reference=file.txt -- "$tempfile"
tail -1 file.txt >"$tempfile" && mv -- "$tempfile" file.txt
<<<
別のアプローチ (プラットフォーム上で暗黙的に作成しない限り、一時ファイルを回避する) は次のとおりです。
lastline="$(tail -1 file.txt)"; cat >file.txt <<<"$lastline"
(上記の実装は bash 固有ですが、最後の行に「--version」が含まれている場合など、echo が機能しない場合に機能します)。
最後に、moreutils のスポンジを使用できます:
tail -1 file.txt | sponge file.txt
sed を使用して、ファイルから最後の行を除くすべての行を削除できます。
sed -i '$!d' file
「cat」が実行される前に、Bash はすでに「file.txt」を書き込み用に開いており、その内容を消去しています。
一般に、同じステートメントで読み取り中のファイルに書き込みをしないでください。これは、上記のように別のファイルに書き込むことで回避できます。
$cat file.txt | テール -1 >別のファイル.txt $mv anotherfile.txt file.txtまたは、moreutilsのスポンジのようなユーティリティを使用して:
$cat file.txt | テール -1 | スポンジファイル.txtこれは、スポンジが出力ファイルを開く前に入力ストリームが終了するまで待機するためです。
tmp=$(テール -1 file.txt); echo $tmp > file.txt;
これは、Linux シェルでうまく機能します。
replace_with_filter() {
local filename="$1"; shift
local dd_output byte_count filter_status dd_status
dd_output=$("$@" <"$filename" | dd conv=notrunc of="$filename" 2>&1; echo "${PIPESTATUS[@]}")
{ read; read; read -r byte_count _; read filter_status dd_status; } <<<"$dd_output"
(( filter_status > 0 )) && return "$filter_status"
(( dd_status > 0 )) && return "$dd_status"
dd bs=1 seek="$byte_count" if=/dev/null of="$filename"
}
replace_with_filter file.txt tail -1
dd
の「notrunc」オプションは、フィルタリングされたコンテンツを元の場所に書き戻すために使用されますがdd
、ファイルを実際に切り捨てるには、(バイト数で) 再度必要です。新しいファイル サイズが古いファイル サイズ以上の場合、2 回目のdd
呼び出しは必要ありません。
ファイル コピー方法に対するこの利点は、次のとおりです。1) 追加のディスク領域が不要、2) 大きなファイルでのパフォーマンスが向上、3) 純粋なシェル (dd 以外)。
コマンド文字列を bash に送信すると、次の処理が行われます。
「cat」が読み取りを開始するまでに、「file.txt」は「tail」によってすでに切り詰められています。
これはすべて Unix とシェル環境の設計の一部であり、元の Bourne シェルにまでさかのぼります。これは機能であり、バグではありません。
この場合だけに使用できます
猫 < file.txt | (rm file.txt; テール -1 > file.txt)これにより、「(...)」内のサブシェルで接続「cat」の直前に「file.txt」が開きます。「rm file.txt」は、サブシェルが「tail」の書き込み用にディスクを開く前にディスクから参照を削除しますが、内容は「cat」に渡される開かれた記述子を介して、標準入力を閉じるまで引き続き利用できます。したがって、このコマンドが終了するか、「file.txt」の内容が失われることを確認してください。
echo "$(tail -1 file.txt)" > file.txt
Lewis Baumstark が言うように、同じファイル名に書き込んでいるのは好ましくありません。
これは、シェルが「file.txt」を開き、「cat file.txt」が実行される前にリダイレクトを行うために切り捨てるためです。だから、あなたがしなければならない
tail -1 file.txt > file2.txt; mv file2.txt file.txt
同じファイル名に書き戻すという事実が気に入らないようです。次のようにすると動作します。
$cat file.txt | tail -1 > anotherfile.txt
tail -1 > file.txt
パイプライン内のコマンドが実行される前に再書き込みが行われるため、ファイルが上書きされ、 cat が空のファイルを読み取ることになります。