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

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



답변

의 출력을 구문 분석 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Павел Танков 에게 감사합니다 .


답변

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


답변

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

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

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


답변

이것을 위해 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명령 행을 테스트 한 경우를 제거하십시오 .


답변

아무도 파일 이름에 포함 된 줄 바꿈 문자 (임베디드 포함)를 제공하는 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/*


답변

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


답변

다른 답변이 내 질문과 목적에 맞지 않기 때문에이 쉘은 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"