디렉터리에서 동일한 접두사를 공유하는 각 파일 그룹에 대해 가장 최근 n개 파일을 제외하고 모두 삭제합니다.

디렉터리에서 동일한 접두사를 공유하는 각 파일 그룹에 대해 가장 최근 n개 파일을 제외하고 모두 삭제합니다.

n내 질문은 단순히 " 디렉토리에서 가장 최근 파일을 제외한 모든 파일 삭제"를 요구하는 일부 이전 질문과 약간 다릅니다 .

각 파일 그룹이 임의의 접두사를 공유하고 각 그룹에 최소한 하나의 파일이 있는 다양한 '그룹' 파일이 포함된 디렉토리가 있습니다. 이러한 접두어를 미리 알지 못하고, 그룹이 몇 개나 있는지도 모릅니다.

편집: 사실, 나는 파일 이름에 대해 뭔가를 알고 있습니다. 즉, 그들은 모두 패턴을 따른다는 것입니다 prefix-some_digits-some_digits.tar.bz2. 여기서 중요한 것은 부분이며 , 각 부분에는 숫자나 대시가 없다고 prefix가정할 수 있습니다 .prefix

스크립트 에서 다음을 수행하고 싶습니다 bash.

  1. 지정된 디렉터리를 탐색하여 기존 '그룹'을 모두 식별하고 각 파일 그룹에 대해 n그룹의 가장 최근 파일만 제외하고 모두 삭제합니다.

  2. n그룹에 대한 파일 수가 다음보다 적으면 해당 그룹에 대해 아무 작업도 수행하지 마십시오. 즉, 해당 그룹에 대한 파일을 삭제하지 마십시오.

에서 위의 작업을 수행하는 강력하고 안전한 방법은 무엇입니까 bash? 명령을 단계별로 설명해 주시겠습니까?

답변1

스크립트:

#!/bin/bash

# Get Prefixes

PREFIXES=$(ls | grep -Po '^(.*)(?!HT\d{4})-(.*)-(.*).tar.bz2$' | awk -F'-' '{print $1}' | uniq)

if [ -z "$1" ]; then
  echo need a number of keep files.
  exit 1
else
  NUMKEEP=$1
fi

for PREFIX in ${PREFIXES}; do

  ALL_FILES=$(ls -t ${PREFIX}*)

  if [ $(echo ${ALL_FILES} | wc -w) -lt $NUMKEEP ]; then
    echo Not enough files to be kept. Quit.
    continue
  fi

  KEEP=$(ls -t ${PREFIX}* | head -n${NUMKEEP})

  for file in $ALL_FILES ; do
    if [[ "$KEEP" =~ "$file" ]]; then
      echo keeping $file
    else
      echo RM $file
    fi
  done
done

설명:

  • 접두사를 계산합니다.
    • 정규식을 따르는 모든 파일을 찾아 something-something-something.tar.bz2첫 번째 대시까지 첫 번째 부분만 잘라서 고유하게 만듭니다.
    • 결과는 정규화된 목록입니다.PREFIXES
  • 모두 반복합니다 PREFIXES.
  • ALL_FILES다음으로 계산PREFIX
  • 파일 의 양이 ALL_FILES보관할 파일 수보다 적은지 확인 -> true인 경우 여기서 중지하고 제거할 항목이 없습니다.
  • KEEP가장 최근 NUMKEEP파일 인 파일을 계산합니다 .
  • 반복하여 ALL_FILES주어진 파일이 파일 목록에 없는지 확인하십시오 KEEP. 그렇다면 제거하십시오.

실행 시 결과 예:

$ ./remove-old.sh 2
keeping bar-01-01.tar.bz2
keeping bar-01-02.tar.bz2
RM bar-01-03.tar.bz2
RM bar-01-04.tar.bz2
RM bar-01-05.tar.bz2
RM bar-01-06.tar.bz2
keeping foo-01-06.tar.bz2
keeping foo-01-05.tar.bz2
RM foo-01-04.tar.bz2
RM foo-01-03.tar.bz2
RM foo-01-02.tar.bz2

$ ./remove-old.sh 8
Not enough files to be kept. Quit.
Not enough files to be kept. Quit.

답변2

요청하신 대로 이 답변은 빠르고 지저분한 답변이 아니라 요청하신 대로 "견고하고 안전한" 경향이 있습니다.

sh이식성: 이 답변은 , find, sed, sort, ls, grep, xargs및 가 포함된 모든 시스템에서 작동합니다 rm.

스크립트는 큰 디렉터리에서 질식해서는 안 됩니다. 쉘 파일 이름 확장은 수행되지 않습니다(파일이 너무 많으면 질식할 수 있지만 이는 엄청난 숫자입니다).

이 답변에서는 접두사에 대시( -)가 포함되지 않는다고 가정합니다.

설계상 스크립트에는 제거될 파일만 나열됩니다. 스크립트에서 주석 처리된 while루프 의 출력을 파이프하여 파일을 제거하도록 할 수 있습니다 . xargs -d '/n' rm이렇게 하면 제거 코드를 활성화하기 전에 스크립트를 쉽게 테스트할 수 있습니다.

#!/bin/sh -e

NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1

find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
sed 's/-.*//; s,^\./,,' |
sort -u |
while read prefix
do
    ls -t | grep  "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
done # | xargs -d '\n' rm --

N 매개변수(보관할 파일 수)의 기본값은 64000입니다(즉, 모든 파일이 보관됨).

주석이 달린 코드

명령줄 인수를 가져오고 추가로 정수를 확인합니다. 지정되지 않은 경우 매개변수 기본값은 64000(실질적으로 모두)입니다.

NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1

현재 디렉터리에서 파일 이름 형식과 일치하는 모든 파일을 찾습니다.

find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |

접두사 가져오기: 접두사 뒤의 모든 항목을 제거하고 시작 부분의 "./"를 제거합니다.

sed 's/-.*//; s,^\./,,' |

접두사를 정렬하고 중복 항목을 제거합니다( -u-- 고유).

sort -u |

각 접두사 및 프로세스를 읽으십시오.

while read prefix
do

시간별로 정렬된 디렉터리의 모든 파일을 나열하고, 현재 접두사에 대한 파일을 선택하고, 유지하려는 파일 이외의 모든 줄을 삭제합니다.

    ls -t | grep  "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"

테스트를 위해 파일을 제거하는 코드를 주석 처리합니다. xargs를 사용하면 명령줄 길이나 파일 이름의 공백 문제를 피할 수 있습니다. 스크립트가 로그를 생성하도록 하려면 예를 -v들어 다음을 추가하세요 . 제거 코드를 활성화하려면 다음을 제거하십시오 .rmrm -v --#

done # | xargs -d '\n' rm --

이것이 효과가 있다면 이 답변을 수락하고 투표해 주십시오. 감사해요.

답변3

어휘 순서로 나열될 때 파일이 접두사별로 그룹화되어 있다고 가정하겠습니다. 이는 다른 그룹의 접미사인 접두사가 있는 그룹이 없음을 의미합니다. 예를 들어 및 foo-1-2-3.tar.bz2사이에 들어갈 수 있는 그룹은 없습니다 . 이 가정하에 모든 파일을 나열할 수 있으며 접두사(또는 첫 번째 파일)의 변경을 감지하면 새 그룹이 생성됩니다.foo-1-1.tar.bz2foo-1-2.tar.bz2

#!/bin/bash
n=$1; shift   # number of files to keep in each group
shopt extglob
previous_prefix=-
for x in *-+([0-9])-+([0-9]).tar.bz2; do
  # Step 1: skip the file if its prefix has already been processed
  this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2}
  if [[ "$this_prefix" == "$previous_prefix" ]]; then
    continue
  fi
  previous_prefix=$this_prefix
  # Step 2: process all the files with the current prefix
  keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2
done

이제 우리는 문제에 직면했습니다명시적 목록 중에서 가장 오래된 파일 결정.

파일 이름에 개행 문자나 문자 ls그대로 표시되지 않는 문자가 포함되어 있지 않다고 가정하면 다음을 사용하여 구현할 수 있습니다 ls.

keep_latest () (
  n=$1; shift
  if [ "$#" -le "$n" ]; then return; fi
  unset IFS; set -f
  set -- $(ls -t)
  shift "$n"
  rm -- "$@"
)

답변4

나는 이것이 태그되어 있다는 것을 알고 있지만 bash다음을 사용하면 더 쉬울 것이라고 생각합니다 zsh.

#!/usr/bin/env zsh

N=$(($1 + 1))                         # calculate Nth to last
typeset -U prefixes                   # declare array with unique elements
prefixes=(*.tar.bz2(:s,-,/,:h))       # save prefixes in the array
for p in $prefixes                    # for each prefix
do
arr=(${p}*.tar.bz2)                   # save filenames starting with prefix in arr
if [[ ${#arr} -gt $1 ]]               # if number of elements is greather than $1
then
print -rl -- ${p}*.tar.bz2(Om[1,-$N]) # print all filenames but the most recent N 
fi
done

스크립트는 하나의 인수를 허용합니다.N(파일 수)
(:s,-,/,:h)는 glob 수정자이며, :s첫 번째를 -다음으로 /바꾸고 :h헤드(마지막 슬래시까지의 부분)를 추출합니다(이 경우에는 하나뿐이므로 첫 번째 슬래시이기도 합니다).
(Om[1,-$N])glob 한정자는 Om다음으로 시작하는 파일을 정렬합니다. 가장 오래된 파일을 선택하고 [1,-$N]처음부터 N번째부터 마지막 ​​파일까지 선택합니다.
결과가 만족스러우면 파일을 실제로 삭제하려면 다음과 같이 print -rl바꾸 십시오.rm

#!/usr/bin/env zsh

typeset -U prefixes
prefixes=(*.tar.bz2(:s,-,/,:h))
for p in $prefixes
arr=(${p}*.tar.bz2) && [[ ${#arr} -gt $1 ]] && rm -- ${p}*.tar.bz2(Om[1,-$(($1+1))])

관련 정보