bash에서 가장 최근의 X 파일을 제외한 모든 파일 삭제
bash가있는 표준 UNIX 환경에서 디렉토리에서 가장 최근의 X 파일을 제외한 모든 파일을 삭제하는 명령을 실행하는 간단한 방법이 있습니까?
좀 더 구체적인 예를 제공하기 위해 매 시간마다 디렉토리에 파일 (예 : 로그 파일 또는 지워진 백업)을 기록하는 일부 cron 작업을 상상해보십시오. 5보다 작을 때까지 해당 디렉토리에서 가장 오래된 파일을 제거하는 다른 크론 작업을 실행하는 방법을 원합니다.
그리고 분명히하기 위해 파일이 하나만 존재하므로 절대 삭제해서는 안됩니다.
기존 답변의 문제점 :
- 공백이나 줄 바꿈이 포함 된 파일 이름을 처리 할 수 없습니다.
rm
인용되지 않은 명령 대체 (rm `...`
)에서 직접 호출하는 솔루션의 경우 의도하지 않은 글로브가 발생할 위험이 추가됩니다.
- 파일과 디렉토리를 구별 할 수 없음 (즉, 디렉토리가 가장 최근에 수정 된 5 개의 파일 시스템 항목 중 하나 인 경우 5 개 미만의 파일을 효과적으로 보유
rm
하므로 디렉토리에 적용 하는 데 실패 함).
wnoise의 답변 은 이러한 문제를 해결하지만 해결책은 GNU에 따라 다릅니다 (매우 복잡합니다).
여기 에는 한 가지 경고 만 포함 된 실용적이고 POSIX 호환 솔루션 이 있습니다. 포함 된 줄 바꿈으로 파일 이름을 처리 할 수는 없지만 대부분의 사람들이 실제로 우려하는 것은 아닙니다.
기록을 위해 다음은 일반적으로 ls
출력 을 구문 분석하는 것이 좋지 않은 이유에 대한 설명입니다 . http://mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
각 파일 이름 마다 한 번씩 호출해야 하므로 위의 내용은 비효율적 입니다. 플랫폼 에서이 문제를 해결할 수 있습니다.xargs
rm
xargs
당신이있는 경우 GNU xargs
사용 -d '\n'
차종은, xargs
각각의 입력 라인 별도의 인수 고려를 아직 명령 줄에 맞는 많은 인수로 전달 번 :
ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
-r
( --no-run-if-empty
)는 rm
입력이 없으면 호출되지 않도록합니다.
당신이있는 경우 BSD xargs
(에 포함 OS X ), 당신이 사용할 수있는 -0
처리 NUL
로 처음 번역의 뉴 라인 후, 단락 지어진 입력 NUL
( 0x0
) 문자를, 어떤도 (일반적으로) 통과하는 모든 파일 이름. 한 번에 (것이다 GNU와도 작업 xargs
) :
ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --
설명:
ls -tp
파일 시스템 항목의 이름을 최근에 수정 한 파일 순서대로 내림차순 (가장 최근에 수정 한 항목) (-t
)으로 인쇄하고, 디렉토리를 그 뒤에/
표시 하는 후행 ( )으로 인쇄 합니다-p
.grep -v '/$'
그런 다음-v
후행/
(/$
) 이있는 ( ) 행을 생략하여 결과 목록에서 디렉토리를 제거합니다 .- 주의 사항 : 디렉토리를 가리키는 심볼릭 링크 는 기술적으로 그 자체가 디렉토리가 아니므 로 이러한 심볼릭 링크는 제외 되지 않습니다 .
tail -n +6
처음 건너 뜁니다 (5 개) 모두를 반환 효과, 목록의 항목 만 5 개 가장 최근에 수정 된 파일 (있는 경우). 파일
을 제외 시키 려면로 전달되어야합니다 .N
N+1
tail -n +
xargs -I {} rm -- {}
(그리고 그 변형)rm
모든 파일에서 호출 합니다; 일치xargs
하는 항목이 없으면 아무 것도하지 않습니다.xargs -I {} rm -- {}
{}
각 입력 줄 을 전체로 나타내는 자리 표시자를 정의 하므로rm
각 입력 줄마다 한 번 호출되지만 포함 된 공백이있는 파일 이름을 사용하여 올바르게 처리됩니다.--
모든 경우에 시작하는 모든 파일 이름이 옵션 으로-
오인되지 않도록 합니다 .rm
일치하는 파일을 개별적 으로 처리 하거나 셸 배열로 수집 해야하는 경우 원래 문제 의 변형 :
# One by one, in a shell loop (POSIX-compliant):
ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done
# One by one, but using a Bash process substitution (<(...),
# so that the variables inside the `while` loop remain in scope:
while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)
# Collecting the matches in a Bash *array*:
IFS=$'\n' read -d '' -ra files < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s\n' "${files[@]}" # print array elements
디렉토리에서 가장 최근 파일 5 개 (또는 숫자)를 제외한 모든 파일을 제거하십시오.
rm `ls -t | awk 'NR>5'`
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm
이 버전은 공백이있는 이름을 지원합니다.
(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm
thelsdj의 답변의 간단한 변형 :
ls -tr | head -n -5 | xargs --no-run-if-empty rm
ls -tr은 가장 오래된 것부터 모든 파일을 표시합니다 (-t newest, -r reverse).
head -n -5는 5 개의 마지막 행을 제외한 모든 행을 표시합니다 (예 : 5 개의 최신 파일).
xargs rm은 선택된 각 파일에 대해 rm을 호출합니다.
find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f
Requires GNU find for -printf, and GNU sort for -z, and GNU awk for "\0", and GNU xargs for -0, but handles files with embedded newlines or spaces.
All these answers fail when there are directories in the current directory. Here's something that works:
find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm
This:
works when there are directories in the current directory
tries to remove each file even if the previous one couldn't be removed (due to permissions, etc.)
fails safe when the number of files in the current directory is excessive and
xargs
would normally screw you over (the-x
)doesn't cater for spaces in filenames (perhaps you're using the wrong OS?)
ls -tQ | tail -n+4 | xargs rm
List filenames by modification time, quoting each filename. Exclude first 3 (3 most recent). Remove remaining.
EDIT after helpful comment from mklement0 (thanks!): corrected -n+3 argument, and note this will not work as expected if filenames contain newlines and/or the directory contains subdirectories.
Ignoring newlines is ignoring security and good coding. wnoise had the only good answer. Here is a variation on his that puts the filenames in an array $x
while IFS= read -rd ''; do
x+=("${REPLY#* }");
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )
If the filenames don't have spaces, this will work:
ls -C1 -t| awk 'NR>5'|xargs rm
If the filenames do have spaces, something like
ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh
Basic logic:
- get a listing of the files in time order, one column
- get all but the first 5 (n=5 for this example)
- first version: send those to rm
- second version: gen a script that will remove them properly
With zsh
Assuming you don't care about present directories and you will not have more than 999 files (choose a bigger number if you want, or create a while loop).
[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])
In *(.om[6,999])
, the .
means files, the o
means sort order up, the m
means by date of modification (put a
for access time or c
for inode change), the [6,999]
chooses a range of file, so doesn't rm the 5 first.
I realize this is an old thread, but maybe someone will benefit from this. This command will find files in the current directory :
for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done
This is a little more robust than some of the previous answers as it allows to limit your search domain to files matching expressions. First, find files matching whatever conditions you want. Print those files with the timestamps next to them.
find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'
Next, sort them by the timestamps:
sort -r -z -n
Then, knock off the 4 most recent files from the list:
tail -n+5
Grab the 2nd column (the filename, not the timestamp):
awk '{ print $2; }'
And then wrap that whole thing up into a for statement:
for F in $(); do rm $F; done
This may be a more verbose command, but I had much better luck being able to target conditional files and execute more complex commands against them.
found interesting cmd in Sed-Onliners - Delete last 3 lines - fnd it perfect for another way to skin the cat (okay not) but idea:
#!/bin/bash
# sed cmd chng #2 to value file wish to retain
cd /opt/depot
ls -1 MyMintFiles*.zip > BigList
sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList
for i in `cat DeList`
do
echo "Deleted $i"
rm -f $i
#echo "File(s) gonzo "
#read junk
done
exit 0
Removes all but the 10 latest (most recents) files
ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm
If less than 10 files no file is removed and you will have : error head: illegal line count -- 0
I needed an elegant solution for the busybox (router), all xargs or array solutions were useless to me - no such command available there. find and mtime is not the proper answer as we are talking about 10 items and not necessarily 10 days. Espo's answer was the shortest and cleanest and likely the most unversal one.
Error with spaces and when no files are to be deleted are both simply solved the standard way:
rm "$(ls -td *.tar | awk 'NR>7')" 2>&-
Bit more educational version: We can do it all if we use awk differently. Normally, I use this method to pass (return) variables from the awk to the sh. As we read all the time that can not be done, I beg to differ: here is the method.
Example for .tar files with no problem regarding the spaces in the filename. To test, replace "rm" with the "ls".
eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')
Explanation:
ls -td *.tar
lists all .tar files sorted by the time. To apply to all the files in the current folder, remove the "d *.tar" part
awk 'NR>7...
skips the first 7 lines
print "rm \"" $0 "\""
constructs a line: rm "file name"
eval
executes it
Since we are using rm
, I would not use the above command in a script! Wiser usage is:
(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))
In the case of using ls -t
command will not do any harm on such silly examples as: touch 'foo " bar'
and touch 'hello * world'
. Not that we ever create files with such names in real life!
Sidenote. If we wanted to pass a variable to the sh this way, we would simply modify the print (simple form, no spaces tolerated):
print "VarName="$1
to set the variable VarName
to the value of $1
. Multiple variables can be created in one go. This VarName
becomes a normal sh variable and can be normally used in a script or shell afterwards. So, to create variables with awk and give them back to the shell:
eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\"" }'); echo "$VarName"
leaveCount=5
fileCount=$(ls -1 *.log | wc -l)
tailCount=$((fileCount - leaveCount))
# avoid negative tail argument
[[ $tailCount < 0 ]] && tailCount=0
ls -t *.log | tail -$tailCount | xargs rm -f
I made this into a bash shell script. Usage: keep NUM DIR
where NUM is the number of files to keep and DIR is the directory to scrub.
#!/bin/bash
# Keep last N files by date.
# Usage: keep NUMBER DIRECTORY
echo ""
if [ $# -lt 2 ]; then
echo "Usage: $0 NUMFILES DIR"
echo "Keep last N newest files."
exit 1
fi
if [ ! -e $2 ]; then
echo "ERROR: directory '$1' does not exist"
exit 1
fi
if [ ! -d $2 ]; then
echo "ERROR: '$1' is not a directory"
exit 1
fi
pushd $2 > /dev/null
ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {}
popd > /dev/null
echo "Done. Kept $1 most recent files in $2."
ls $2|wc -l
참고URL : https://stackoverflow.com/questions/25785/delete-all-but-the-most-recent-x-files-in-bash
'IT' 카테고리의 다른 글
Android : LocationManager 및 Google Play 서비스 (0) | 2020.06.25 |
---|---|
백그라운드 작업자에게 인수를 보내시겠습니까? (0) | 2020.06.25 |
CKEditor는 div에서 클래스를 자동으로 제거합니다. (0) | 2020.06.25 |
Chrome 용 Chrome 앱에서 주소 표시 줄을 어떻게 숨기나요? (0) | 2020.06.25 |
URL에서 JSON 문자열을 얻는 방법은 무엇입니까? (0) | 2020.06.25 |