10

ファイルの最後の行を除くすべての行を削除しようとしましたが、file.txt は空ではありませんが、次のコマンドは機能しませんでした。

$cat file.txt |tail -1 > file.txt

$cat file.txt

なぜそうなのですか?

4

11 に答える 11

19

パイプラインを介してファイルから同じファイルにリダイレクトすることは安全ではありません。最初のステージの読み取りを開始する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
于 2008-09-23T19:33:25.203 に答える
5

sed を使用して、ファイルから最後の行を除くすべての行を削除できます。

sed -i '$!d' file
  • -iは、sed にファイルをその場で置き換えるように指示します。そうしないと、結果が STDOUT に書き込まれます。
  • $は、ファイルの最終行に一致するアドレスです。
  • dは削除コマンドです。この場合、!によって否定されます。、したがって、アドレスに一致しないすべての行が削除されます。
于 2008-09-24T14:56:43.597 に答える
3

「cat」が実行される前に、Bash はすでに「file.txt」を書き込み用に開いており、その内容を消去しています。

一般に、同じステートメントで読み取り中のファイルに書き込みをしないでください。これは、上記のように別のファイルに書き込むことで回避できます。

$cat file.txt | テール -1 >別のファイル.txt
$mv anotherfile.txt file.txt
または、moreutilsのスポンジのようなユーティリティを使用して:
$cat file.txt | テール -1 | スポンジファイル.txt
これは、スポンジが出力ファイルを開く前に入力ストリームが終了するまで待機するためです。

于 2008-09-23T19:39:29.430 に答える
2

tmp=$(テール -1 file.txt); echo $tmp > file.txt;

于 2008-09-23T19:44:42.493 に答える
2

これは、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 以外)。

于 2010-03-25T03:56:59.177 に答える
2

コマンド文字列を bash に送信すると、次の処理が行われます。

  1. I/O パイプを作成します。
  2. 「/usr/bin/tail -1」を開始し、パイプから読み取り、file.txt に書き込みます。
  3. 「/usr/bin/cat file.txt」を開始し、パイプに書き込みます。

「cat」が読み取りを開始するまでに、「file.txt」は「tail」によってすでに切り詰められています。

これはすべて Unix とシェル環境の設計の一部であり、元の Bourne シェルにまでさかのぼります。これは機能であり、バグではありません。

于 2008-09-23T19:43:30.060 に答える
1

この場合だけに使用できます

猫 < file.txt | (rm file.txt; テール -1 > file.txt)
これにより、「(...)」内のサブシェルで接続「cat」の直前に「file.txt」が開きます。「rm file.txt」は、サブシェルが「tail」の書き込み用にディスクを開く前にディスクから参照を削除しますが、内容は「cat」に渡される開かれた記述子を介して、標準入力を閉じるまで引き続き利用できます。したがって、このコマンドが終了するか、「file.txt」の内容が失われることを確認してください。

于 2010-03-25T05:15:54.063 に答える
1
echo "$(tail -1 file.txt)" > file.txt
于 2010-03-25T05:24:30.030 に答える
1

Lewis Baumstark が言うように、同じファイル名に書き込んでいるのは好ましくありません。

これは、シェルが「file.txt」を開き、「cat file.txt」が実行される前にリダイレクトを行うために切り捨てるためです。だから、あなたがしなければならない

tail -1 file.txt > file2.txt; mv file2.txt file.txt
于 2008-09-23T19:34:23.220 に答える
0

同じファイル名に書き戻すという事実が気に入らないようです。次のようにすると動作します。

$cat file.txt | tail -1 > anotherfile.txt
于 2008-09-23T19:31:41.243 に答える
0

tail -1 > file.txtパイプライン内のコマンドが実行される前に再書き込みが行われるため、ファイルが上書きされ、 cat が空のファイルを読み取ることになります。

于 2008-09-23T19:34:37.947 に答える