7

I want to delete files, which have size = 0. So I tried:

find ./ -size 0 | xargs rm

But it has a problem with files, which names begin with space. I found this:

find ./ -size 0 -exec rm -i {} \;

It works. I think my way with xargs is too sophisticated for this.

What does {} \; means?

Could you explain me? My English is not very good, so please write simply.

3
  • 1
    by the way: There's -print0 in find and -0 in xargs. Using them is very much imperative when doing stuff like this. Because if it finds "/ bla"... 😅
    – Daniel B
    May 3 '16 at 21:15
  • @DanielB -print0 and xargs -0 are both non standard and often useless, find alone being able to achieve the same goal without these hacks.
    – jlliagre
    May 3 '16 at 22:16
8

{} has absolutely no meaning to bash, so is passed unmodified as an argument to the command executed, here find.

On the other hand, ; has a specific meaning to bash. It is normally used to separate sequential commands when they are on the same command line. Here the backslash in \; is precisely used to prevent the semicolon to be interpreted as a command separator by bash and then allow it to be passed as a parameter to the underlying command, find. Quoting the semicolon, i.e. ";" or ';', could have been an alternate way to have it stayed unprocessed.

The command:

find ./ -size 0 -exec rm -i {} \;

means: find in the current directory (note that the / is useless here, . cannot be but a directory anyway) anything that has a size of 0 and for each object found, run the command rm -i name, i.e. interactively prompt for each file if you want to remove it. {} is replaced by each file name found in the executed command. One nice feature is that this file name is strictly a single argument, whatever the file name (even containing embedded spaces, tabs, line feeds, and whatever characters). This wouldn't be the case with xargs, unless non portable hacks are used. The final ; is there to end the -exec clause. The reason why its end needs to be delimited is that other find options might follow the -exec one, although it is rarely done. e.g.:

find . -name "*.js" -exec ls -l {} \; -name "special*" -exec wc -l {} \;

One issue with this command is that it won't ignore non plain files, so might prompt the user to delete sockets, block and character devices, pipes, and directories. It will always fail with the latter even if you answer yes.

Another issue, although not really critical here, is that rm will be called for each file that has a zero size. If you substitute the -exec ending from /; to +, find will optimize the sub-process creation by only calling rm the minimal possible number of times, often just once.

Here is then how I would modify this command:

find . -type f -size 0 -exec rm -i {} +
10
  • {} is parameter substitution
    – clhy
    May 6 '16 at 0:18
  • 1
    @The_IT_Guy_You_Don't_Like It is not. More precisely, parameter substitution requires a $ character before { and requires at least a variable name to be present between the curly braces. {} alone has no meaning for the shell and is left unchanged by it.
    – jlliagre
    May 6 '16 at 7:18
  • Why does this not work without the +? What's it doing here? Jan 8 '19 at 11:17
  • 2
    @CGTheLegend It doesn't work without the + because the -exec expression takes a variable number of arguments so needs to be terminated by something to be correctly parsed, and further expressions to be properly found. The terminator can be either + or ;.
    – jlliagre
    Jan 8 '19 at 11:35
  • excellent. The reason why its end needs to be delimited is that other find clauses might follow the -exec one, although it is rarely done. could you elaborate on this. This will clarify things further down.
    – samshers
    Aug 29 '20 at 5:03
2

When using find -exec, {} is expanded to each result found.

For example, if you have a directory example containing 3 files a.txt, b.txt and c.txt, find example/ -exec rm -i {} \; will expand to:

find example/ -exec rm -i example/a.txt \;
find example/ -exec rm -i example/b.txt \;
find example/ -exec rm -i example/c.txt \;

The \; at the end is simple an escaped ; to indicate the end of the exec pattern. Otherwise, it would be interpreted by shell itself.

1
  • 2
    The command wouldn't really expand to the three commands you wrote which would result in multiple prompts for the same file and leads to "file not found" errors if you accept to remove them, but simply to: rm -i "example/a.txt" ; rm -i "example/b.txt" ; rm -i "example/c.txt"
    – jlliagre
    May 3 '16 at 22:10
1

In conjunction with the find command's exec option, the {} part is replaced by the name of the files found when the command is executed. The \; is important too, because that is what defines the end of the command being executed

For instance

find ~ -name \*.bak -exec -rm -f {} \;

would delete all files ending in .bak anywhere in the user's home directory or in folders contained inside it. By executing rm -f on each file found.

xargs takes standard input lines, usually from a pipe, and forms the tailing part of the arguments from it when it executed the command you give it

1

To take each case in turn:-

xargs

What this program does is to add each line from input as a parameter to whatever command is passed as its own parameter , then run the resultant command, so if find lists three files a, b and c in the current directory it would give as output:

./a
./b
./c

If this is piped to xargs rm, then this command will be executed:

rm ./a ./b ./c

However, if find also finds a file called " d e ", then the executed command becomes:

rm ./a ./b ./c ./ d e 

To handle this, find provides an option -print0, which adds a null character (\0) after each file, instead of the usual new-line. To tell xargs that the input is in this form, add the parameter -0, so the command:

find . ... -print0|xargs -0 rm

will build and execute the command:

rm "./a" "./b" ."/c" "./ d e "

This will work on all file names, including names containing blanks, tabs and new-lines.

find -exec

When this option is called, then what follows (up to \;) is executed for each file found. This is not terribly useful unless there is some means to refer to the currently found file and that is what {} does. It can be called more than once in the run string, eg to copy a selection of files to a back-up directory:

find . ... -exec cp "{}" "/BackUp/{}" \;

This in the find example above would execute in turn:

cp "./a" "/BackUp/./a"
cp "./b" "/BackUp/./b"
cp "./c" "/BackUp/./c"

Note that, outside their use in the find command, { and } have special meanings in bash (multiple commands and expanding lists), but they involve other characters between the brackets: the string {} is copied literally, so does not need escaping.

2
  • +1, excellent - find . ... -exec cp "{}" "/BackUp/{}" \;
    – samshers
    Aug 29 '20 at 13:01
  • { and } have special meanings in bash (multiple commands and expanding lists) - any examples
    – samshers
    Aug 29 '20 at 13:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.