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


14

한 폴더에서 다른 폴더로 20 개의 가장 오래된 파일 만 이동시키기위한 스크립트를 작성하려면 어떻게해야합니까? 폴더에서 가장 오래된 파일을 가져 오는 방법이 있습니까?


하위 디렉토리 포함 또는 제외? 그리고 재귀 적으로 (디렉토리 트리에서)해야합니까?
maxschlepzig

2
많은 (대부분?) * nix 파일 시스템은 작성 날짜를 저장하지 않으므로 가장 오래된 파일을 확실하게 결정할 수 없습니다 . 일반적으로 사용 가능한 속성은 atime(마지막 액세스), ctime(마지막 권한 변경) 및 mtime(마지막 수정)입니다. ls -t그리고 발견 printf "%T" 사용 mtime...에 따르면 보인다 링크 내 것을, ext4파티션이 할 수있는 창조의 일을 처리하지만, lsfindstat... (아직) 적절한 옵션이없는
Peter.O

답변:


13

의 출력을 구문 분석 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.

  • 타임 스탬프
  • 우주
  • 파일의 상대 경로
  • NULL 문자

타임 스탬프가 중요합니다. %T@에 대한 형식 지정자 -printf로 분해 T파일 (mtime에)과의 "마지막 수정 시간"표시, @소수 초를 포함하여 "1970 년 이후 초"를 나타냅니다.

공간은 단지 임의의 구분자입니다. 파일의 전체 경로는 나중에 참조 할 수 있도록하며, NULL 문자는 파일 이름에 잘못된 문자이므로 종료 문자는 경로 끝에 도달했음을 알려줍니다. 파일.

내가 포함 한 2>/dev/null사용자가 액세스 권한이없는 파일이 제외되어 있지만, 그들에 대한 오류 메시지가 억제되어 제외되고 너무.

의 결과 find명령 는 현재 디렉토리의 모든 디렉토리 목록입니다. 이 목록은 다음 sort과 같이 지시됩니다.

  • -z 개행 문자 대신 널 종료 문자로 NULL을 처리하십시오.
  • -n 숫자로 정렬

seconds-since-1970이 항상 올라가므로 타임 스탬프가 가장 작은 파일을 원합니다. 첫 번째 결과sort 는 가장 작은 숫자의 타임 스탬프를 포함하는 줄입니다. 남아있는 것은 파일 이름을 추출하는 것입니다.

의 결과 find, sort파이프 라인을 통해 전달되는 프로세스 대체while이 표준 입력에서 파일을 인 것처럼 읽을된다. while차례로 호출read 입력을 처리하기 위해 합니다.

문맥에서 read우리는 IFS변수를 none으로 설정했다 . 이것은 공백이 구분자로 부적절하게 해석되지 않음을 의미한다. read-r이스케이프 확장을 비활성화 -d $'\0'하고 find, sort파이프 라인 의 출력과 일치하여 줄 끝 구분 기호를 NULL로 만듭니다.

타임 스탬프와 공백이 오는 가장 오래된 파일 경로를 나타내는 첫 번째 데이터 청크가 변수로 읽 힙니다 line. 다음으로 매개 변수 대체 는 expression과 함께 사용되며 #*, 이는 문자열의 시작부터 공백을 포함한 첫 번째 공백까지 모든 문자를 아무 것도없이 대체합니다. 수정 타임 스탬프를 제거하고 파일의 전체 경로 만 남겨 둡니다.

이 시점에서 파일 이름이 저장되며 $file원하는대로 작업 할 수 있습니다. 당신이 함께 일을 완료 할 때 문 의지 루프와$filewhileread 명령은 다음 청크와 다음 파일 이름을 추출, 다시 실행됩니다.

더 간단한 방법이 없습니까?

아니요. 간단한 방법은 버그가 있습니다.

를 사용 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.

감사

이 답변의 개선을위한 enzotibПавел Танков 에게 감사합니다 .


정렬을 사용해서는 안됩니다 -n. 적어도 내 버전에서는 십진수를 올바르게 정렬하지 않습니다. 날짜에서 점을 제거하거나 -printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz, ISO 날짜 등을 사용해야 합니다.
l0b0

@ l0b0 :이 제한은 나에게 알려져 있습니다. 나는 그 세분성 수준을 요구하지 않는 것으로 충분하다고 생각합니다 (즉, 너머로 정렬하는 것은 .당신과 관련 이 없어야합니다) sort -z -n -t. -k1.
Sorpigal

@ l0b0 : 솔루션은 다음과 상관없이 동일한 버그 %TS를 나타냅니다 . 또한 "분수 부분"형식 00.0000000000으로 표시되므로 세분성을 잃게됩니다. 최근 GNU 는 "버전 정렬" sort을 사용하여이 문제를 해결할 수있었습니다.이 -V버전은 예상대로 이러한 유형의 부동 소수점을 처리합니다.
Sorpigal

아니요, 숫자 정렬 대신 "YYYY-MM-DDThh : mm : ss" 에서 문자열 정렬을 수행하기 때문 입니다. 그것은 10000 : 년까지 작업을해야하므로 문자열 정렬은 소수에 대해 상관하지 않는다
l0b0

@ l0b0 : 문자열 정렬 %T@은 0으로 채워지기 때문에 작동합니다.
Sorpigal

4

zsh에서 가장 쉽습니다. Om glob 한정자 를 사용하여 날짜 (가장 오래된 것부터)로 일치를 정렬하고 [1,20]한정자를 사용하여 처음 20 개의 일치 만 유지할 수 있습니다.

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

D도트 파일도 포함 하려면 한정자를 추가하십시오 . 더하다.디렉토리가 아닌 일반 파일 만 일치 시키려면 .

zsh가없는 경우 Perl one-liner가 있습니다 (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

ls -t출력을 tail또는로 결합하십시오 head.

모든 파일 이름에 공백 및 \[*?: 이외의 인쇄 가능한 문자 만 포함 된 경우에만 작동하는 간단한 예 :

 mv $(ls -1tr | head -20) other_folder

1
ls에 -A 옵션을 추가하십시오.ls -1Atr
Arcege

1
-1, 위험하다. 여기에서 예제를 만들어 보겠습니다 touch $'foo\n*'. 해당 파일이 저장된 상태에서 mv "$ (ls)"를 실행하면 어떻게됩니까?
Sorpigal

1
@Sorpigal 진심으로? "특별히 효과가 없다고 말한 예를 생각해 봅시다. 이봐 요, 효과가 없습니다"
Michael Mrozek

1
@Sorpigal 그것은 나쁜 생각이 아니며 99 %의 경우에 작동합니다. 대답은 "일반적인 이름을 가진 파일이 있으면 작동합니다. 파일 이름에 줄 바꿈을 포함하는 미친 사람이라면 그렇지 않습니다." 완전히 맞습니다
Michael Mrozek

1
@ MichaelMrozek : 그것은 나쁜 생각이며 때로는 실패하기 때문에 나쁘다. 때때로 실패하는 것과 그렇지 않은 것을 행할 수있는 옵션이 있다면, 그렇지 않은 옵션과 나쁜 옵션을 선택해야합니다. 대화식으로 좋아하는 것을 스크립트 파일로 수행하고 조언을 제공 할 때 올바르게 수행하십시오.
Sorpigal

3

이것을 위해 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 개가 필터링되어로 이동됩니다 dest_dir. echo명령 행을 테스트 한 경우를 제거하십시오 .


2

아무도 파일 이름에 포함 된 줄 바꿈 문자 (임베디드 포함)를 제공하는 bash 예제를 게시하지 않았으므로 여기에 하나가 있습니다. 가장 오래된 (mdate) 정규 파일 3 개를 이동합니다.

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/*

+1, 내 이전에 유용한 답변 만 제공 (항상 테스트 데이터를 보유하는 것이 좋습니다)
Sorpigal

0

GNU를 사용하는 것이 가장 쉽습니다 find. 매일 Linux DVR에서 사용하여 하루보다 오래된 비디오 감시 시스템의 녹화물을 삭제합니다.

구문은 다음과 같습니다.

find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;

그 기억 find을 정의를 실행의 24 시간이 아니라 하루. 따라서 오후 11시에 마지막으로 수정 된 파일은 오전 1시에 삭제되지 않습니다.

find와 결합 할 수도 cron있으므로 루트로 다음 명령을 실행하여 삭제 일정을 자동으로 예약 할 수 있습니다.

crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF

find매뉴얼 페이지를 참조하여 언제든지 자세한 정보를 얻을 수 있습니다 .

man find

0

다른 답변이 내 질문과 목적에 맞지 않기 때문에이 쉘은 CentOS 7에서 테스트되었습니다.

oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.