가장 오래된 파일을 이동하는 쉘 스크립트?

가장 오래된 파일을 이동하는 쉘 스크립트?

가장 오래된 파일 20개를 한 폴더에서 다른 폴더로 이동하는 스크립트를 작성하는 방법은 무엇입니까? 폴더에서 가장 오래된 파일을 가져오는 방법이 있나요?

답변1

ls다음의 출력을 구문 분석합니다.신뢰할 수 없는.

대신 find파일을 찾고 sort타임스탬프별로 정렬하는 데 사용하세요. 예를 들어:

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    # do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

이게 다 뭐하는 거야?

먼저, 이 명령은 현재 디렉터리( )에서 모든 파일과 디렉터리를 find찾고 현재 디렉터리( )의 하위 디렉터리에서는 검색하지 않은 후 다음을 인쇄합니다..-maxdepth 1

  • 타임스탬프
  • 공간
  • 파일의 상대 경로
  • 널 문자

타임스탬프가 중요합니다. 형식 %T@지정자는 파일의 "마지막 수정 시간"(mtime)을 나타내는 와 분수 초를 포함하여 "1970년 이후의 초 수"를 나타내는 -printf로 구분됩니다 .T@

공백은 임의의 구분 기호일 뿐입니다. 파일의 전체 경로는 나중에 참조할 수 있도록 하기 위한 것이며, NULL 문자는 파일 이름에 잘못된 문자이므로 파일 경로 끝에 도달했는지 확인할 수 있게 해주는 종결자입니다. 문서.

2>/dev/null사용자가 액세스할 수 없는 파일을 제외하도록 포함시켰지만 해당 파일이 제외되었다는 오류 메시지는 표시되지 않습니다.

이 명령의 결과 find는 현재 디렉터리의 모든 디렉터리 목록입니다. 목록은 으로 파이프되어 다음을 sort나타냅니다.

  • -zNULL을 개행 문자가 아닌 줄 종결자로 처리합니다.
  • -n숫자순으로 정렬

1970년 이후 초 수가 항상 증가하므로 타임스탬프 수가 가장 적은 파일이 필요합니다. 첫 번째 결과 sort는 가장 낮은 번호의 타임스탬프가 포함된 행이 됩니다. 남은 것은 파일 이름을 추출하는 것뿐입니다.

find, 파이프라인의 결과는 sort다음을 통과합니다.프로세스 교체while마치 표준 입력의 파일인 것처럼 읽혀지는 곳입니다 . 입력을 처리하기 위해 while차례로 호출됩니다 read.

read변수를 공백으로 설정한 맥락에서 IFS이는 공백이 구분 기호로 부적절하게 해석되지 않음을 의미합니다. 이스케이프 확장을 비활성화하고 줄 끝 구분 기호를 NULL로 만들어 파이프의 출력과 일치시킨다는 것을 read알 수 있습니다 .-r-d $'\0'findsort

첫 번째 데이터 블록은 타임스탬프와 공백이 앞에 오는 가장 오래된 파일 경로를 나타내며 변수로 읽혀집니다 line. 다음,매개변수 대체표현식과 함께 사용하면 #*문자열 시작 부분부터 첫 번째 공백을 포함하여 모든 문자를 null로 바꿉니다. 이렇게 하면 수정 타임스탬프가 제거되고 파일의 전체 경로만 남습니다.

이 시점에서 파일 이름이 저장 $file되고 원하는 대로 무엇이든 할 수 있습니다. 무언가를 완료하면 $file명령문 while이 반복되고 read명령이 다시 실행되어 다음 청크와 다음 파일 이름을 추출합니다.

더 쉬운 방법은 없을까요?

아니요. 더 간단한 접근 방식에는 결함이 있습니다.

사용 ls -t하고 파이프하는 경우 head또는 tail(또는아무것) 파일 이름에 개행 문자가 있는 파일에서는 중단됩니다. mv $(anything)이름에 공백이 포함된 파일은 손상될 수 있습니다 . mv "$(anything)"이름 뒤에 개행 문자가 있는 파일은 손상될 수 있습니다. 이렇게 read하지 않으면 이름 -d $'\0'에 공백이 포함된 파일이 손상됩니다.

더 간단한 접근 방식으로 충분하다고 확신하는 특정 상황이 있을 수 있지만, 가능하다면 그러한 가정을 스크립트에 작성해서는 안 됩니다.

해결책

#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    echo mv "$file" "$dest"
    let limit-=1
    [[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

다음과 같이 전화하세요.

move-oldest /mnt/backup/ /var/log/foo/ 20

가장 오래된 20개 파일을 에서 으로 이동 /var/log/foo/합니다 /mnt/backup/.

파일을 포함하고 있으니 참고해주세요그리고목차. 파일의 경우 통화 -type f에 추가하면 됩니다.find

감사해요

감사해요엔조팁그리고파벨 타니코프이 답변이 개선되었습니다.

답변2

이를 달성하기 위해 GNU find를 사용할 수 있습니다:

find -maxdepth 1 -type f -printf '%T@ %p\n' \
  | sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
  | xargs echo mv -t dest_dir

여기서 find는 수정 시간(1970년 이후 초 단위)과 현재 디렉터리의 각 파일 이름을 인쇄하며 출력은 첫 번째 필드에 따라 정렬되고 가장 오래된 20개는 필터링되어 OK 명령을 테스트한 경우 로 이동됩니다 dest_dir. echo삭제 해주세요.

답변3

사용할 수 있는 zsh에서 가장 쉽습니다.Om 글로벌 예선날짜(가장 오래된 것부터) 및 한정자별로 일치 항목을 정렬하여 [1,20]상위 20개 일치 항목만 유지합니다.

mv -- *(Om[1,20]) target/

D도트 파일도 포함하려면 한정자를 추가하세요. .디렉터리가 아닌 일반 파일만 일치시키려면 해당 파일을 추가하세요.

zsh가 없으면 Perl 한 줄짜리 코드가 있습니다(80자 미만으로 할 수 있지만 명확하게 하려면 추가 비용이 듭니다).

perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'

그러나 이는 최대 초까지만 정확합니다(수정 시간(사용 가능한 경우)의 나노초 부분은 고려되지 않음).

날짜별로 파일을 정렬하는 것은 POSIX 도구나 bash 또는 ksh만 사용하는 것은 어려운 일입니다. 를 사용하면 쉽게 이 작업을 수행할 수 있지만 ls구문 분석된 출력 ls에는 버그가 있으므로 파일 이름에 개행 문자 이외의 인쇄 가능한 문자만 포함된 경우에만 작동합니다.

ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done

답변4

파일 이름에 개행 문자(무엇이든 포함)를 포함해야 하는 요구 사항을 충족하는 bash 예제를 아직 게시한 사람이 없으므로 여기에 하나를 게시합니다. 가장 오래된 3개(mdate) 일반 파일을 이동합니다.

move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p\0" |sort -znk1 | { 
  while IFS= read -d $'\0' -r file; do
      printf "%s\0" "${file#*$'\t'}"
      ((--move==0)) && break
  done } |xargs -0 mv -t dest

이것은테스트 데이터파편

# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest

여기있어검사 결과파편

  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*

관련 정보