Using find command In first command, the find command itself deletes the file, using -delete option. In the next 2 commands, the find command passes its output to xargs command which constructs separate rm command for those files. Here is a simple command to find and delete all files in your folder except . zip files.
We mention rm {} to delete all files older than 7 days, in /data/ folder. If you want to delete only folders, add type -d option to find folders, and use rmdir command. If you want to delete both files & folders, use rm -r command.
The problems with the existing answers:
rm
directly on an unquoted command substitution (rm `...`
), there's an added risk of unintended globbing.rm
to directories will fail).wnoise's answer addresses these issues, but the solution is GNU-specific (and quite complex).
Here's a pragmatic, POSIX-compliant solution that comes with only one caveat: it cannot handle filenames with embedded newlines - but I don't consider that a real-world concern for most people.
For the record, here's the explanation for why it's generally not a good idea to parse ls
output: http://mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
Note: This command operates in the current directory; to target a directory explicitly, use a subshell ((...)
) with cd
:(cd /path/to && ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {})
The same applies analogously to the commands below.
The above is inefficient, because xargs
has to invoke rm
separately for each filename.
However, your platform's specific xargs
implementation may allow you to solve this problem:
A solution that works with GNU xargs
is to use -d '\n'
, which makes xargs
consider each input line a separate argument, yet passes as many arguments as will fit on a command line at once:
ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
Note: Option -r
(--no-run-if-empty
) ensures that rm
is not invoked if there's no input.
A solution that works with both GNU xargs
and BSD xargs
(including on macOS) - though technically still not POSIX-compliant - is to use -0
to handle NUL
-separated input, after first translating newlines to NUL
(0x0
) chars., which also passes (typically) all filenames at once:
ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --
Explanation:
ls -tp
prints the names of filesystem items sorted by how recently they were modified , in descending order (most recently modified items first) (-t
), with directories printed with a trailing /
to mark them as such (-p
).
ls -tp
always outputs file / directory names only, not full paths, that necessitates the subshell approach mentioned above for targeting a directory other than the current one ((cd /path/to && ls -tp ...)
).grep -v '/$'
then weeds out directories from the resulting listing, by omitting (-v
) lines that have a trailing /
(/$
).
tail -n +6
skips the first 5 entries in the listing, in effect returning all but the 5 most recently modified files, if any.
Note that in order to exclude N
files, N+1
must be passed to tail -n +
.
xargs -I {} rm -- {}
(and its variations) then invokes on rm
on all these files; if there are no matches at all, xargs
won't do anything.
xargs -I {} rm -- {}
defines placeholder {}
that represents each input line as a whole, so rm
is then invoked once for each input line, but with filenames with embedded spaces handled correctly.--
in all cases ensures that any filenames that happen to start with -
aren't mistaken for options by rm
.A variation on the original problem, in case the matching files need to be processed individually or collected in a shell array:
# 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
Remove all but 5 (or whatever number) of the most recent files in a directory.
rm `ls -t | awk 'NR>5'`
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm
This version supports names with spaces:
(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm
Simpler variant of thelsdj's answer:
ls -tr | head -n -5 | xargs --no-run-if-empty rm
ls -tr displays all the files, oldest first (-t newest first, -r reverse).
head -n -5 displays all but the 5 last lines (ie the 5 newest files).
xargs rm calls rm for each selected file.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With