가장 오래된 파일 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
나타냅니다.
-z
NULL을 개행 문자가 아닌 줄 종결자로 처리합니다.-n
숫자순으로 정렬
1970년 이후 초 수가 항상 증가하므로 타임스탬프 수가 가장 적은 파일이 필요합니다. 첫 번째 결과 sort
는 가장 낮은 번호의 타임스탬프가 포함된 행이 됩니다. 남은 것은 파일 이름을 추출하는 것뿐입니다.
find
, 파이프라인의 결과는 sort
다음을 통과합니다.프로세스 교체while
마치 표준 입력의 파일인 것처럼 읽혀지는 곳입니다 . 입력을 처리하기 위해 while
차례로 호출됩니다 read
.
read
변수를 공백으로 설정한 맥락에서 IFS
이는 공백이 구분 기호로 부적절하게 해석되지 않음을 의미합니다. 이스케이프 확장을 비활성화하고 줄 끝 구분 기호를 NULL로 만들어 파이프의 출력과 일치시킨다는 것을 read
알 수 있습니다 .-r
-d $'\0'
find
sort
첫 번째 데이터 블록은 타임스탬프와 공백이 앞에 오는 가장 오래된 파일 경로를 나타내며 변수로 읽혀집니다 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/*