모든 12 번째 파일을 제외하고 모두 제거 12 번째

filename.12345.end 형식의 파일이 수천 개 있습니다. 나는 모든 12 번째 파일 만 유지하기를 원하므로 file.00012.end, file.00024.end … file.99996.end 및 다른 모든 것을 삭제하십시오.

파일 이름 앞에 파일 번호가 더있을 수도 있으며 일반적으로 다음과 같은 형식입니다. file.00064.name.99999.end

Bash 셸을 사용하여 파일을 반복하는 방법을 파악한 다음 번호를 알아 내고 number%%12=0 파일 이 삭제 되는지 여부를 확인할 수 없습니다. 누구든지 나를 도울 수 있습니까?

감사합니다, 도리 나



답변

다음은 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)'

속도와 관련하여 다음은이 접근법과 다른 답변 중 하나에서 제공되는 쉘을 비교 한 것입니다.

$ 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()파일은 제거되지만 디렉토리는 제거되지 않습니다.


답변

파일 이름이 형식 인 file.00064.name.99999.end경우 먼저 번호를 제외한 모든 항목을 잘라 내야합니다. 이를 위해 for루프를 사용합니다 .

우리는 또한 Bash 셸에 10을 사용하도록 지시해야합니다 .Bash 산술은 0으로 시작하는 숫자를 8로 처리하므로 문제가 발생합니다.

파일을 포함하는 디렉토리에서 시작할 때 스크립트로 사용하려면 다음을 사용하십시오.

#!/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

제대로 작동합니다.


답변

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

많은 양의 파일에 대해 절망적으로 느리게 작동합니다. 수천 개의 이름을 생성하는 데 시간과 메모리가 필요하므로 실제 효율적인 솔루션보다 더 트릭입니다.


답변

조금 길지만 내 마음에 온 것입니다.

 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 번씩 삭제하십시오.


답변

모든 겸손에서이 솔루션은 다른 답변보다 훨씬 훌륭하다고 생각합니다.

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

약간의 설명 : 먼저로 파일 목록을 생성합니다 find. 이름이 끝나는 모든 파일을 얻습니다..end 깊이가 1 (즉, 하위 폴더가 아닌 작업 디렉토리에 직접 있습니다. 하위 폴더가없는 경우 제외 할 수 있음). 출력 목록은 알파벳순으로 정렬됩니다.

그런 다음 해당 목록을으로 파이프합니다 awk. 여기서 NR행 번호 인 특수 변수 를 사용합니다 . 우리는 어디에 파일을 인쇄하여 모든 12 번째 파일을 제외합니다 NR%12 != 0. awk명령을 단축 할 수 awk 'NR%12'모듈로 연산자의 결과는 부울 값으로 해석됩니다 그리고이 때문에 {print}암시 어쨌든 이루어집니다.

이제 xargs와 rm을 사용하여 삭제할 수있는 파일 목록을 만들었습니다. 표준 입력을 인수로 사용 xargs하여 지정된 명령 ( rm)을 실행합니다 .

파일이 많으면 ‘인수 목록이 너무 깁니다'(제한이 256 kB이고 POSIX에 필요한 최소값이 4096 바이트 임)와 같은 오류가 발생합니다. 이것은 -n 100플래그 로 피할 수 있습니다. 플래그는 인수를 100 단어마다 나누고 (파일 이름에 공백이있는 경우주의해야 할 것) rm100 개의 인수 만 가진 별도의 명령을 실행합니다 .


답변

bash 만 사용하는 첫 번째 방법은 다음과 같습니다. 1. 유지하려는 모든 파일을 다른 디렉토리 (예 : filename의 숫자가 12의 배수 인 모든 파일)로 이동 한 다음 2. 디렉토리의 나머지 파일을 모두 삭제하십시오. 그런 다음 3. 여러 개의 파일을 원래 위치로 되돌려 놓습니다. 따라서 다음과 같이 작동 할 수 있습니다.

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