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


14

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

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

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

감사합니다, 도리 나


파일 번호는 파일 이름에만 의존합니까?
Arronical

또한 파일에는 항상 5 자리가 있으며 접미사와 접두사가 항상 같은가요?
Arronical

예, 항상 5 자리입니다. 첫 질문이 맞는지 잘 모르겠습니다. 다른 파일 이름을 가진 파일은 다르며 00012, 00024 등의 숫자를 갖는 이러한 특정 파일이 필요합니다.
Dorina

3
@ 도리 나 질문을 편집 하고 명확하게하십시오. 모든 것을 바꾼다!
terdon

2
그리고 그들은 모두 같은 디렉토리에 있습니다.
Sergiy 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)'

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

$ 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 셸에 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

제대로 작동합니다.


5
rm수천 번 전화 하는 것은 상당히 느릴 수 있습니다. echo대신 파일 이름을 제안 하고 루프 출력을 xargs rm(필요에 따라 옵션 추가)에 파이프하십시오 for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --.
David Foerster

제안 된 속도 향상을 포함하도록 편집했습니다.
Arronical

실제로 55999 개의 파일이있는 디렉토리에서 테스트 한 후 원래 버전은 2 분 48 xargs초 , 5 분 1 초가 걸렸습니다. echo@DavidFoerster의 오버 헤드 때문일 수 있습니까?
Arronical

이상한. 60.000 파일의 경우 tmpfs에서 1m11.450s / 0m10.695s / 0m16.800s로 0m0.659s / 0m0.545s / 0m0.380s (real / user / sys)를 time { for f in *; do echo "$f"; done | xargs rm; }얻습니다 time { for f in *; do rm "$f"; done; }. 배쉬는 v4.3.11, 커널은 v4.4.19입니다.
David Foerster

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

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


나는 이것에 대한 코드 골프를 좋아한다.
David Foerster

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 != 0. awk명령을 단축 할 수 awk 'NR%12'모듈로 연산자의 결과는 부울 값으로 해석됩니다 그리고이 때문에 {print}암시 어쨌든 이루어집니다.

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

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


3
: 당신의 접근 방식에 문제가 몇 가지 있습니다 -depth전에 할 필요가 -name; ii) 파일 이름에 공백이 있으면 실패합니다. iii) 파일이 오름차순으로 나열된다고 가정하고 awk있지만 (실제로는 테스트 대상 임) 거의 그렇지 않습니다. 따라서 임의의 파일 세트가 삭제됩니다.
terdon

디오! 당신은 옳습니다, 내 나쁜 (댓글 편집). 잘못된 게재 위치로 인해 오류가 발생하여를 기억하지 못했습니다 -depth. 그럼에도 불구하고, 이것이 여기서 가장 적은 문제였습니다. 가장 중요한 것은 OP가 원하는 파일이 아닌 임의의 파일 세트를 삭제한다는 것입니다.
terdon

아, 아뇨, -depth가치가 없으며 생각하는 것과 반대입니다. man find"-depth 디렉토리 자체보다 먼저 각 디렉토리의 내용을 처리 하십시오 ."를 참조하십시오 . 따라서 이것은 실제로 서브 디렉토리로 내려 가고 곳곳에 혼란을 초래할 것입니다.
terdon

I) 모두 -depth n와는 -maxdepth n존재한다. 전자는 깊이가 정확히 n이어야하며 후자는 <= n 일 수 있습니다. II). 예, 그것은 나쁘지만이 특정 예에서는 걱정하지 않습니다. find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rmnull 바이트를 레코드 구분 기호로 사용하는을 사용 하여 문제를 해결할 수 있습니다 (파일 이름에는 허용되지 않음). III) 다시 한번,이 경우 가정은 합리적이다. 그렇지 않으면 및 sort -n사이를 삽입 하거나 파일로 리디렉션 하여 원하는대로 정렬 할 수 있습니다. findawkfind
user593851

3
아, 아마도 OSX를 사용하고있을 것입니다. 그것은 매우 다른 구현입니다 find. 그러나 다시 주요 문제는 find정렬 된 목록 을 반환 한다고 가정한다는 것 입니다. 그렇지 않습니다.
terdon

0

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

접근 방식이 마음에 들지만 filename일관되지 않은 경우 어떻게 파트 를 생성 합니까?
Arronical
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.