Why do we use this strange construction {} \; in linux terminal for exec command?
For example,
find . -type f -name *.jpeg -exec rm {} \;
From the man page of find (emphasis mine):
find . -type f -exec file '{}' \;
Runs `file' on every file in or below the current directory. Notice that the braces are enclosed in single quote marks to protect them from interpretation as shell script punctuation. The semicolon is similarly protected by the use of a backslash, though single quotes could have been used in that case also.
Looking for info I found this post in AskUbuntu which I think is family from StackOverflow where an User ask the same as you.
Link
Hope It is useful.
Related
Is there any way to apply Bash variable substitution on find's output? I know I've seen someone do it on Stack Overflow but I can't seem to find that particular post anymore.
As an example, let's say I want to rename files ending in *.png to *_copy.png. I know I can do this using rename but it's just a thought experiment.
Now I'd like to be able to do something like this:
find . -name "*png" -exec mv "{}" "${{}%.*}_copy.png" \;
Which results in an invalid substitution. Of course, I could first assign the output to a variable and then apply substitution in a sub-shell, but is this really the only way?
find . -name "*.png" -exec bash -c 'var="{}"; mv "{}" "${var%.*}_copy.png"' \;
Or is there any way this can be achieved directly from {}?
Consensus
As Etan Reisner remarked, a better and safer way to handle the output of find would be to pass it as a positional argument:
find . -name "*.png" -exec bash -c 'mv "$0" "${0%.*}_copy.png"' "{}" \;
It took me a while to get the question. Basically you are asking if something like:
echo "${test.png%.png}"
could be used to get the word test.
The answer is no. The string manipulation operators starting with ${ are a subset of the parameter substitution operators. They work only on variables, not with string literals, meaning you need to store the string in a variable before. Like this:
img="test.png"
echo "${img%.png}"
Just for travellers from Google, I would use rename for this particular task. (As the OP already mentioned in his question). The command could look like this:
find -name '*png' -execdir rename 's/\.png/_copy.png/' {} +
This question already has answers here:
How to insert strings containing slashes with sed? [duplicate]
(11 answers)
Closed 5 years ago.
i need a quick command (linux or windows) to replace every \\ with a /, and all tries with sed failed because of the /.
(I already tried find . -name '*.*' -exec sed -i 's/\\///g' {} \;, but i think it failed with the "/".
find . -name '*.*' -type f -exec sed -i 's:\\\\:/:g' {} \;
You need to escape each backslash, and using a colon or comma as separators is generally recommended when making replacements with forward-slash. However, escaping the forward slash works too:
find . -name '*.*' -type f -exec sed -i 's/\\\\/\//g' {} \;
As pointed out in comments the OS module is probably what you really need to look at.
Edit: thanks to #tripleee for reminding me of the -type f line, which limits it to files, rather than including the current directory.
Also, I copied the syntax *.* from the OP but in general it isn't helpful. * alone is usually what you want, since files aren't guaranteed to have a dot in their name. Assuming you were happy to include files not containing a dot, the simplest thing to do here is have no -name at all:
find . -type f -exec sed -i 's:\\\\:/:g' {} \;
This question already has answers here:
Why are the backslash and semicolon required with the find command's -exec option?
(2 answers)
Closed 9 years ago.
This is a relatively simple command, so if a duplicate exists and someone could refer me, I'm sorry and I'll delete/close this question.
Man page for find
find . -type f -exec file '{}' \;
Runs 'file' on every file in or below the current directory. Notice that the braces are enclosed in single quote marks to protect them from interpretation
as shell script punctuation. The semicolon is similarly protected by the use of a backslash, though ';' could have been used in that case also.
I do not understand the notation \;. What in the world is that?
In the find command, the action -exec is followed by a command and that command's arguments. Because there can be any number of arguments, find needs some way of knowing when it ends. The semicolon is what tells find that it has reached the end of the command's arguments.
Left to their own devices, most shells would eat the semicolon. We want that semicolon to be passed to the find command. Therefore, we escape it with a backslash. This tells the shell to treat the semicolon as just one of the arguments to the find command.
MORE: Why not, one may ask, just assume that the exec command's argument just go to the end of the line? Why do we need to signal an end to the exec command's arguments at all? The reason is that find has advanced features. Just for example, consider:
find . -name '*.pdf' -exec echo Yes, we have a pdf: {} \; -o -exec echo No, not a pdf: {} \;
I want to know exactly what {} \; and {} \+ and | xargs ... do. Please clarify these with explanations.
Below 3 commands run and output same result but the first command takes a little time and the format is also little different.
find . -type f -exec file {} \;
find . -type f -exec file {} \+
find . -type f | xargs file
It's because 1st one runs the file command for every file coming from the find command. So, basically it runs as:
file file1.txt
file file2.txt
But latter 2 find with -exec commands run file command once for all files like below:
file file1.txt file2.txt
Then I run the following commands on which first one runs without problem but second one gives error message.
find . -type f -iname '*.cpp' -exec mv {} ./test/ \;
find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ #gives error:find: missing argument to `-exec'
For command with {} \+, it gives me the error message
find: missing argument to `-exec'
why is that? can anyone please explain what am I doing wrong?
The manual page (or the online GNU manual) pretty much explains everything.
find -exec command {} \;
For each result, command {} is executed. All occurences of {} are replaced by the filename. ; is prefixed with a slash to prevent the shell from interpreting it.
find -exec command {} +
Each result is appended to command and executed afterwards. Taking the command length limitations into account, I guess that this command may be executed more times, with the manual page supporting me:
the total number of invocations of the command will be much less than the number of matched files.
Note this quote from the manual page:
The command line is built in much the same way that xargs builds its command lines
That's why no characters are allowed between {} and + except for whitespace. + makes find detect that the arguments should be appended to the command just like xargs.
The solution
Luckily, the GNU implementation of mv can accept the target directory as an argument, with either -t or the longer parameter --target. It's usage will be:
mv -t target file1 file2 ...
Your find command becomes:
find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+
From the manual page:
-exec command ;
Execute command; true if 0 status is returned. All following arguments to find are taken to be arguments to the command until an argument consisting of `;' is encountered. The string `{}' is replaced by the current file name being processed everywhere it occurs in the arguments to the command, not just in arguments where it is alone, as in some versions of find. Both of these constructions might need to be escaped (with a `\') or quoted to protect them from expansion by the shell. See the EXAMPLES section for examples of the use of the -exec option. The specified command is run once for each matched file. The command is executed in the starting directory. There are unavoidable security problems surrounding use of the -exec action; you should use the -execdir option instead.
-exec command {} +
This variant of the -exec action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the total number of invocations of the command will be much less than the number of matched files. The command line is built in much the same way that xargs builds its command lines. Only one instance of `{}' is allowed within the command. The command is executed in the starting directory.
I encountered the same issue on Mac OSX, using a ZSH shell: in this case there is no -t option for mv, so I had to find another solution.
However the following command succeeded:
find .* * -maxdepth 0 -not -path '.git' -not -path '.backup' -exec mv '{}' .backup \;
The secret was to quote the braces. No need for the braces to be at the end of the exec command.
I tested under Ubuntu 14.04 (with BASH and ZSH shells), it works the same.
However, when using the + sign, it seems indeed that it has to be at the end of the exec command.
The standard equivalent of find -iname ... -exec mv -t dest {} + for find implementations that don't support -iname or mv implementations that don't support -t is to use a shell to re-order the arguments:
find . -name '*.[cC][pP][pP]' -type f -exec sh -c '
exec mv "$#" /dest/dir/' sh {} +
By using -name '*.[cC][pP][pP]', we also avoid the reliance on the current locale to decide what's the uppercase version of c or p.
Note that +, contrary to ; is not special in any shell so doesn't need to be quoted (though quoting won't harm, except of course with shells like rc that don't support \ as a quoting operator).
The trailing / in /dest/dir/ is so that mv fails with an error instead of renaming foo.cpp to /dest/dir in the case where only one cpp file was found and /dest/dir didn't exist or wasn't a directory (or symlink to directory).
find . -name "*.mp3" -exec mv --target-directory=/home/d0k/Музика/ {} \+
no, the difference between + and \; should be reversed. + appends the files to the end of the exec command then runs the exec command and \; runs the command for each file.
The problem is find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ should be find . -type f -iname '*.cpp' -exec mv {} ./test/ + no need to escape it or terminate the +
xargs I haven't used in a long time but I think works like +.
I would like to do something like:
find . -iname "*Advanced*Linux*Program*" -exec kpdf {} & \;
Possible? Some other comparable method available?
Firstly, it won't work as you've typed, because the shell will interpret it as
find . -iname "*Advanced*Linux*Program*" -exec kpdf {} &
\;
which is an invalid find run in the background, followed by a command that doesn't exist.
Even escaping it doesn't work, since find -exec actually execs the argument list given, instead of giving it to a shell (which is what actually handles & for backgrounding).
Once you know that that's the problem, all you have to do is start a shell to give these commands to:
find . -iname "*Advanced*Linux*Program*" -exec sh -c '"$0" "$#" &' kpdf {} \;
On the other hand, given what you're trying to do, I would suggest one of
find ... -exec kfmclient exec {} \; # KDE
find ... -exec gnome-open {} \; # Gnome
find ... -exec xdg-open {} \; # any modern desktop
which will open the file in the default program as associated by your desktop environment.
If your goal is just not having to close one pdf in order to see the next one as opposed to display each pdf in its own separate instance, you might try
find . -iname "*Advanced*Linux*Program*" -exec kpdf {} \+ &
With the plussed variant, -exec builds the command line like xargs would so all the files found would be handed to the same instance of kpdf. The & in the end then affects the whole find. With very large numbers of files found it might still open them in batches because command lines grow too long, but with respect to ressource consumption on your system this may even be a good thing. ;)
kpdf has to be able to take a list of files on the command line for this to work, as I don't use it myself I don't know this.