web-dev-qa-db-ja.com

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

Filename.12345.endという形式の数千のファイルがあります。私は12番目ごとのファイルのみを保持したいので、file.00012.end、file.00024.end ... file.99996.endをすべて削除します。

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

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

ありがとう、ドリナ

14
Dorina

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

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

さらに次のように凝縮できます。

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

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

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()に渡され、ファイル(ディレクトリは除く)が削除されます。

18
terdon

ファイル名の形式が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コマンドで削除されます。これを最初に実行するときに、rmechoに置き換えて、期待どおりになっていることを確認できます。削除するファイル。

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

ディレクトリについて警告する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

正しく動作します。

12
Arronical

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

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

6
Nykakin

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

 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回ファイルを削除します。

1
Terrik

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

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

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

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

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

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

0
user593851

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
0
delt