12番目を除くすべてのファイルを削除する


14

filename.12345.endという形式の数千のファイルがあります。12個ごとにファイルを保持したいだけなので、file.00012.end、file.00024.end ... file.99996.endを削除し、その他すべてを削除します。

ファイルには、ファイル名の前に番号が付いている場合があり、通常は次の形式です。 file.00064.name.99999.end

私はBashシェルを使用していますが、ファイルをループする方法がわからず、数値を取得してnumber%%12=0 、ファイルが削除されているかどうかを確認できません。誰も私を助けることができますか?

ありがとう、ドリナ


ファイルの番号はファイル名だけに依存していますか?
16

また、ファイルは常に5桁で、接尾辞と接頭辞は常に同じですか?
アラニカル

はい、常に5桁です。最初の質問が正しいかどうかわかりません。異なるファイル名を持つファイルは異なっている、と私は番号00012、00024などを持って起こるこれらの特定のファイルを必要とする
Dorina

3
@Dorinaは質問を編集して、それを明確にしてください。それはすべてを変えます!
テルドン

2
そして、それらはすべて同じディレクトリにありますよね?
セルギーKolodyazhnyy

回答:


18

これがPerlソリューションです。これは、数千のファイルに対してはるかに高速です。

perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *

以下にさらに凝縮できます。

perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *

ファイルが多すぎてsimpleを使用できない場合、*次のようなことができます。

perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ && $1 % 12 != 0} readdir($dir)'

速度に関しては、このアプローチと、他の回答の1つで提供されているシェルの比較があります。

$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
  64000   64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

real    2m44.258s
user    0m9.183s
sys     1m7.647s

$ touch file.{01..64}.name.{00001..01000}.end
$ time perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *

real    0m0.610s
user    0m0.317s
sys     0m0.290s

ご覧のとおり、その差は予想通りです。

説明

  • -e単純に言っているperl、コマンドラインで指定されたスクリプトを実行します。
  • @ARGVスクリプトに指定されたすべての引数を含む特別な変数です。を指定しているため*、現在のディレクトリ内のすべてのファイル(およびディレクトリ)が含まれます。
  • grepファイル名のリストを検索し、数字の文字列、ドットとが一致している任意の探しますend/(\d+)\.end/)

  • 番号(\d)はキャプチャグループ(括弧)にあるため、として保存され$1ます。そのため、grepその番号が12の倍数であるかどうかをチェックし、そうでない場合はファイル名が返されます。つまり、配列に@badは削除するファイルのリストが保持されます。

  • 次にリストが渡されunlink()、ファイルが削除されます(ディレクトリは削除されません)。


12

ファイル名の形式がのfile.00064.name.99999.end場合、最初に番号以外をすべて削除する必要があります。forこれにはループを使用します。

また、Bash算術は0で始まる数字を基数8として扱うため、基数10を使用するようにBashシェルに指示する必要があります。

スクリプトとして、ファイルを含むディレクトリで起動するには、次を使用します。

#!/bin/bash

for f in ./*
do
  if [[ -f "$f" ]]; then
    file="${f%.*}"
    if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
      rm "$f"
    fi
  else
    echo "$f is not a file, skipping."
  fi
done

または、この非常に長いいコマンドを使用して同じことを行うことができます。

for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done

すべての部分を説明するには:

  • for f in ./* は、現在のディレクトリ内のすべてのものを意味します、do ....これにより、見つかった各ファイルまたはディレクトリが変数$ fとして設定されます。
  • if [[ -f "$f" ]]見つかったアイテムがファイルかどうかをチェックします。そうでない場合は、そのecho "$f is not...部分にスキップします。これは、誤ってディレクトリを削除し始めないことを意味します。
  • file="${f%.*}" $ file変数を、最後の後に来るファイル名を切り取るファイル名として設定します .
  • if [[ $((10#${file##*.} % 12)) -eq 0 ]]は、メインの算術演算が開始される場所です。拡張子なしで、ファイル名${file##*.}の最後の前のすべてをトリミングします.$(( $num % $num2 ))は、モジュロ演算を使用するBash算術の構文です。10#最初は、Bashに基数10を使用してこれらの厄介な先行0を処理するように指示します。$((10#${file##*.} % 12))次に、ファイル名番号の残りを12で割った残りを残し-ne 0ます。残りがゼロに等しくないかどうかをチェックします。
  • 剰余が0以外の場合、ファイルはrmコマンドで削除されます。これを最初に実行rmするechoときに置き換えて、削除する予定のファイルを取得することを確認できます。

このソリューションは非再帰的です。つまり、現在のディレクトリ内のファイルのみを処理し、サブディレクトリには一切入りません。

if声明echoディレクトリについて警告するコマンドとして本当に必要はありませんrm、それが所有していますのは、そう、ディレクトリに文句を言うと、それらを削除しません。

#!/bin/bash

for f in ./*
do
  file="${f%.*}"
  if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
    rm "$f"
  fi
done

または

for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

正しく動作します。


5
rm数千回の呼び出しは非常に遅くなる可能性があります。echo代わりにファイル名に提案し、ループの出力をパイプしますxargs rm(必要に応じてオプションを追加します)for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
デビッドフォースター

速度改善の提案を含めるように編集しました。

実際、55999個のファイルがあるディレクトリでテストした後、元のバージョンでは2分48 xargs秒かかり、バージョンでは5分1秒かかりました。これはecho@DavidFoersterのオーバーヘッドによるものでしょうか?
-Arronical

奇数。60.000ファイルの場合、tmpfsで0m0.659s / 0m0.545s / 0m0.380s(real / user / sys)とtime { for f in *; do echo "$f"; done | xargs rm; }vs 1m11.450s / 0m10.695s / 0m16.800sを取得しtime { for f in *; do rm "$f"; done; }ます。Bashはv4.3.11、カーネルはv4.4.19です。
デビッドフォースター

6

Bashブラケット拡張を使用して、12番目ごとの番号を含む名前を生成できます。テストデータを作成しましょう

$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames

次に、以下を使用できます

$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end                 file.00036.end  file.00060.end  file.00084.end
file.00024.end.name.99999.end  file.00048.end  file.00072.end  file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job

しかし、大量のファイルでは動作が絶望的に​​遅くなります-何千もの名前を生成するのに時間とメモリがかかります-したがって、実際の効率的なソリューションよりもトリックです。


私はこれに関するコードゴルフが好きです。
デビッドフォースター

1

少し長いですが、私の頭に浮かんだものです。

 for num in $(seq 1 1 11) ; do
     for sequence in $(seq -f %05g $num 12 99999) ; do
         rm file.$sequence.end.99999;
     done
 done

説明:12番目のファイルごとに11回削除します。


0

すべての謙虚さで、この解決策は他の答えよりもはるかに優れていると思います:

find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm

簡単な説明:最初に、でファイルのリストを生成しますfind。名前が次で終わるすべてのファイルを取得します.endが1で(つまり、サブフォルダーではなく、作業ディレクトリに直接あります。サブフォルダーがない場合は省略できます)。出力リストはアルファベット順にソートされます。

次にawk、そのリストをにパイプします。ここNRでは、行番号である特別な変数を使用します。ファイルを印刷することにより、12番目ごとのファイルを除外しますNR%12 != 0awkコマンドは、に短縮することができるawk 'NR%12'モジュロ演算子の結果は、ブール値として解釈取得しているため、{print}暗黙的にとにかく行われます。

これで、削除する必要があるファイルのリストができました。これは、xargsとrmで実行できます。引数として標準入力でxargs指定されたコマンド(rm)を実行します。

多くのファイルがある場合、「引数リストが長すぎます」などのエラーが表示されます(私のマシンでは、その制限は256 kBであり、POSIXに必要な最小値は4096バイトです)。これは-n 100、100行ごとに引数を分割するフラグ(行ではなく、ファイル名にスペースがある場合に注意するもの)を使用して、rm引数が100だけの個別のコマンドを実行することで回避できます。


3
アプローチにはいくつかの問題があります 。-depth前にある必要があり-nameます。ii)ファイル名に空白が含まれている場合、これは失敗します。iii)ファイルは昇順でリストされると想定しています(awkこれがテスト対象です)が、これはほとんど間違いなくそうではありません。したがって、これによりファイルのランダムなセットが削除されます。
テルドン

ど!あなたは全く正しい、私の悪い(コメント編集)。間違った配置のためにエラーが出ましたが、覚えていませんでした-depth。それでも、それはここでの問題の中で最も少なかった、最も重要なものは、あなたがOPの望んでいるものではなく、ランダムなファイルのセットを削除しているということです。
テルドン

ああ、そしていや、-depth値をとらないし、あなたが考えるとは反対のことをします。man find「-depth各ディレクトリのコンテンツをディレクトリ自体の前に処理します。」を参照してください。したがって、これは実際にサブディレクトリに降りて、あちこちで大混乱を引き起こします。
テルドン

I)両方-depth n-maxdepth n存在。前者は深さが正確にnである必要があり、後者では<= nになります。II)。はい、それは悪いことですが、この特定の例にとっては問題ではありません。find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rmを使用して修正できます。レコード区切り文字としてヌルバイトを使用します(ファイル名では使用できません)。III)もう一度、この場合、仮定は合理的です。そうしないと、挿入することができsort -nfindawk、またはリダイレクトをfindファイルにし、同じようしかし、あなたがそれを並べ替えます。
user593851

3
ああ、あなたはおそらくOSXを使用しています。これはの非常に異なる実装ですfind。ただし、繰り返しますが、主な問題はfind、ソートされたリストを返すと想定していることです。そうではありません。
テルドン

0

bashのみを使用する場合の最初のアプローチは、次のとおりです。その後、保存した12の倍数のファイルを元の場所に戻します。したがって、次のようなものが機能する可能性があります。

cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
  padded_n="`echo -n "00000${n}" | tail -c 5`"
  mv "filename${padded_n}.end" keep_these_files/
  n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files

私はこのアプローチが好きですが、filename一貫性がない場合、どのようにパーツを生成しますか?
16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.