What does '{} \;' mean in the 'find' command context?

Solution 1:

{} 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 {} +

Solution 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.

Solution 3:

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

Solution 4:

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.