1. ホーム
  2. bash

[解決済み] bashで最新のXファイル以外を削除する

2022-04-21 22:10:50

質問

bashを使ったごく普通のUNIX環境で、あるディレクトリから最新のXファイル以外を削除するコマンドを実行する簡単な方法はないでしょうか?

もう少し具体的な例を挙げると、あるcronジョブが1時間ごとにディレクトリにファイル(例えば、ログファイルやtarで圧縮したバックアップ)を書き出すとします。私は、そのディレクトリ内の最も古いファイルが例えば5個以下になるまで削除する別のcronジョブを実行させる方法が欲しいです。

そして、はっきりさせておきたいのは、存在するのは1つのファイルだけで、それは決して削除されるべきものではないということです。

解決方法は?

既存の回答での問題点

  • スペースや改行が埋め込まれたファイル名を扱えない。
    • を呼び出すソリューションの場合 rm を引用符で囲まれていないコマンド置換に直接適用します ( rm `...` を使用すると、意図しないグロビングが発生する危険性が増えます。
  • ファイルとディレクトリを区別できない (つまり、もし ディレクトリ が、ファイルシステムの直近に更新された5項目の中にたまたま含まれていた場合、実質的に 少ない を適用すると、5つのファイルよりも rm をディレクトリに追加すると失敗します)。

wnoiseの回答 はこれらの問題を解決していますが、その解決策は GNU -特有の(そしてかなり複雑な)ものです。

ここで、プラグマティックに POSIX準拠の解決策 のみが付属しています。 1つの注意点 を埋め込んだファイル名は扱えません。 改行 - しかし、それはほとんどの人にとって現実的な問題ではないと考えています。

ちなみに、一般にパースするのが良くないとされる理由は以下のとおりです。 ls を出力します。 http://mywiki.wooledge.org/ParsingLs

ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}

注)このコマンドは 現在 ディレクトリ になります。 ディレクトリを対象とする 明示的に を使用する場合は、サブシェル ( (...) を使用します。 cd :

(cd /path/to && ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {})

は、以下のコマンドに類似して適用されます。 .

上記は 非効率的 なぜなら xargs を呼び出す必要があります。 rm 別途 各ファイル名に対して .

ただし、お使いのプラットフォーム固有の xargs の実装により、この問題を解決できるかもしれません。


という解決策 と連携します。 GNU xargs を使用することです。 -d '\n' となり、その結果 xargs 入力行を個別の引数とみなし、コマンドラインに収まるだけの引数を渡します。 一度に :

ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --

注)オプション -r ( --no-run-if-empty ) は、確実に rm がある場合は呼び出されません。 入力なし .

という解決策 で動作します。 両方 GNU xargs ビーエスディー xargs (を含む macOS ) - ただし、技術的にはまだ ではない POSIX 準拠 - を使用することです。 -0 を処理するために NUL -で区切られた入力は、まず改行を NUL ( 0x0 ) の文字列も渡すことができ、(通常)すべてのファイル名 すぐに :

ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --

説明する。

  • ls -tp ファイルシステムの項目名を、最近更新されたものから順に表示します。 -t ) が表示され、ディレクトリは末尾に / というようにマークします ( -p ).

    • 注意:それは ls -tp は常にファイル/ディレクトリを出力します。 名前 のみで、フルパスではありません。そのため、カレントディレクトリ以外のディレクトリをターゲットにするには、前述のサブシェルアプローチが必要になります ( (cd /path/to && ls -tp ...) ).
  • grep -v '/$' を省略することで、結果の一覧からディレクトリを除外します。 -v ) の行で、末尾に / ( /$ ).

    • 洞窟 : を使用しているため ディレクトリを指すシンボリックリンク は、技術的にはそれ自体がディレクトリではないので、そのようなシンボリックリンクは ない は除外されます。
  • tail -n +6 はスキップします。 5 のエントリを返し、実質的にすべての ただし 最近更新された5つのファイル。

    を除外するために N ファイルを作成します。 N+1 に渡す必要があります。 tail -n + .

  • xargs -I {} rm -- {} (およびそのバリエーション) を起動し rm をこれらのファイル全てに適用します。もし全くマッチしない場合は xargs は何もしない。

    • xargs -I {} rm -- {} プレースホルダーを定義する {} は、各入力行を表す 全体として ということで rm は、入力行ごとに一回ずつ呼び出されますが、スペースを埋め込んだファイル名は正しく処理されます。
    • -- で始まるファイル名は、すべてのケースで - と間違われることはありません。 オプション によって rm .

A バリエーション 元の問題に対する マッチしたファイルを処理する必要がある場合 個別に または シェル配列に収集される :

# One by one, in a shell loop (POSIX-compliant):
ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done

# One by one, but using a Bash process substitution (<(...), 
# so that the variables inside the `while` loop remain in scope:
while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)

# Collecting the matches in a Bash *array*:
IFS=$'\n' read -d '' -ra files  < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s\n' "${files[@]}" # print array elements