Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xargs and find, rm complaining about \n (newline) in filename

Tags:

linux

bash

xargs

I am trying to delete the oldest file in a tree with a script in Debian.

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | xargs -0 ls -t | tail -1 | xargs -0 rm

But I am getting an error:

rm: cannot remove `/home/backups/tree/structure/file.2011-12-08_03-01-01.sql.gz\n': No such file or directory

Any ideas what I am doing wrong (or is there an easier/better way?), I have tried to RTFM, but am lost.

like image 222
user1076412 Avatar asked Dec 15 '11 11:12

user1076412


People also ask

How do you use rm with xargs?

If you want to use xargs command to delete these files just pipe it to xargs command with rm function as its argument. In the above case, xargs command will construct separate rm statements for each file name passed to it by the result of find command. That's it.

What is xargs command in bash?

The xargs command builds and executes commands provided through the standard input. It takes the input and converts it into a command argument for another command. This feature is particularly useful in file management, where xargs is used in combination with rm , cp , mkdir , and other similar commands.

What is xargs WC?

wc combines the total lines of all files that were passed to is as arguments. xargs collects lines from input and puts them all at once as one set of multiple arguments to wc so you get the total of all those files.


2 Answers

The ls appends a newline and the last xargs -0 says the newline is part of the file name. Run the last xargs with -d '\n' instead of -0.

BTW, due to the way xargs works, your whole pipe is a bug waiting to happen. Consider a really long file name list produced by the find, so that the xargs -0 ls runs ls multiple times with subsets of the filenames. Only the oldest of the last ls invocation will make it past the tail -1. If the oldest file is actually, say, the very first filename output by find, you are deleting a younger file.

like image 120
Jens Avatar answered Nov 15 '22 07:11

Jens


Any solution involving ls is absolutely wrong.

The correct way to do this is to use find to fetch the set of files, sort to order them chronologically, filter out all but the first, then rm to delete. @Ken had this mostly right, missing only a few details.

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\0' |\
    sort -z -n | \
    { IFS= read -d '' file ; [ -n "$file" ] && echo rm -f "$(cut -d' ' -f2- <<<"$file")" ; }

Remove the echo above to actually perform the deletion.

The above code works even for files which have spaces, newlines or other unusual values in the file names. It will also do nothing harmful when there are no results.

If you don't care about breaking on newlines in filenames this gets a bit easier

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\n' |\
    sort -n |\
    head -n 1 |\
    cut -d' ' -f2- |\
    xargs echo rm

The difference is that we can rely on head and can use cut on a pipe instead of doing anything crazy.

like image 20
sorpigal Avatar answered Nov 15 '22 06:11

sorpigal