3

This command correctly finds all files in the directory that are older than 1 day:

$ find /Users/[me]/test -type f ! -newermt -1day | xargs -0 -n1
/Users/[me]/test/1.txt
/Users/[me]/test/2.txt

However, when I change the 'xargs' parameter to delete them it throws an error:

$ find /Users/[me]/test -type f ! -newermt -1day | xargs -0 rm
rm: /Users/[me]/test/2.txt
/Users/[me]/test/1.txt
: No such file or directory

Anyone know what the problem is and how to fix it?

Or maybe there's a better way of doing this? (I'm not very familiar with the macos command line, as you can probably tell!)

New contributor
mattsg is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
3
  • 5
    I think if you're going to use the -0 option w/ xargs, you must pair that with the print0 option in find; i.e. find /Users/[me]/test -type f ! -newermt -1day print0 | xargs -0 rm. But this may depend on which version of find & xargs you use; i.e. the native FreeBSD-derived macOS version, OR the GNU version that you'd have if you've installed (for example) MacPorts findutils. If you have a lot of files to delete, xargs is more efficient than using the delete option in find.
    – Seamus
    Commented 2 days ago
  • @Seamus macOS find seems to support -print0 but if you have GNU find, you might as well use -exec rm {} + which should be at least as efficient as xargs.
    – terdon
    Commented 2 days ago
  • 1
    @terdon - I don't know if it's a recent addition, but GNU find also supports -delete.
    – Jim Davis
    Commented 6 hours ago

3 Answers 3

16

find can delete directly.

find /Users/[me]/test -type f ! -newermt -1day -delete
3
  • 2
    It can also list directly, so you can run find /Users/[me]/test -type f ! -newermt -1day -ls to make sure it's going to act on the correct things.
    – Mark
    Commented yesterday
  • 2
    If your version of find lacks a -delete primary (which is not part of the POSIX specification), you can also use -exec rm {} +. (Which it shouldn't; I forgot this was not unix.stackexchange.com, and the BSD version of find that ships with macOS does support -delete.)
    – chepner
    Commented yesterday
  • Yes, as @Mark says, it is smart to run a "just list them" version before running the "delete them" version, so you can verify that your intentions are being manifest accurately. :) Commented yesterday
14

While there are alternative flags to find such as -delete and -print0, to answer the question of what the problem is with xargs and how to fix that problem…

xargs -0 expects the null character to separate arguments.
find outputs results separated by a new line.

Therefore xargs see the result of find as

/Users/[me]/test/2.txt\n/Users/[me]/test/1.txt
#                     ^^ new line

which is one argument.

rm tries to remove the one file with that name (containing a new line), which you can think of as executing

rm "/Users/[me]/test/2.txt\n/Users/[me]/test/1.txt"

(where \n would be a new line) which it cannot find.

Remove -0 from xargs to split on new line so it executes

rm "/Users/[me]/test/2.txt" "/Users/[me]/test/1.txt"

Or, to make this more robust and able to handle arbitrary file names, including those that might have a \n in the file name itself, use

find /Users/[me]/test -type f ! -newermt -1day -print0 | xargs -0 -rm
3
  • 4
    Alternatively you may add -print0 to find arguments. It will allow to actually delete files with new line in name.
    – talex
    Commented 2 days ago
  • 1
    While removing the -0 works in this case, it does not work in the general case. Try this sequence and see why: touch foo$'\n'bar; find . -name 'foo*bar' | xargs stat Adding -print0 to find and -0 to xargs ala talex will get the desired result. Commented 2 days ago
  • This answer was to explain the behaviour of xargs. Alternative answers exist as I acknowledge in the answer that modify the invocation of find instead, feel free to upvote those as I have done. Thanks for your contribution!
    – grg
    Commented 2 days ago
5

Source of the problem explained in @grg answer. Here is alternative solution:

You need to add -print0 to find arguments.

find /Users/[me]/test -type f ! -newermt -1day -print0 | xargs -0 rm
New contributor
talex is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
2
  • Strong agree. The benefit of the find -print0 | xargs -0 foo is that even if the filename has a newline in it (people and programs do crazy things sometimes--accidentally or maliciously) the foo program will always get the correct argument. Commented 2 days ago
  • 1
    Yep, but there's no point in using xargs if you aren't even parallelizing it, so just have find do the work, either using -delete or using find ... -exec rm {} \;
    – terdon
    Commented 2 days ago

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .