Reputation: 3
I have the following problem:
I want to use a xargs-find construction for finding files. The difference is, that I do not want to use find as command1 but as command2 in:
command1 | xargs command2
The problem occurs if the file names have spaces in their names
For example:
If I am trying:
echo 01.Here Comes The Night Time II.flac | xargs -pi find ~/Multimedia/Musik/flac/ -name "\""{}"\""
find ~/Multimedia/Musik/flac/ -name "01.Here Comes The Night Time II.flac" ?...yes
Nothing will be found. Also with the -0 option of xargs it is not working.
If I copy and paste the interactive printed request from xargs, the file will be found:
find ~/Multimedia/Musik/flac/ -name "01.Here Comes The Night Time II.flac"
~/Multimedia/Musik/flac/Arcade Fire/Reflektor (CD 2)/01.Here Comes The Night Time II.flac
Is there something wrong in the way I "feed" the pipe, or in the way I include the " in the find command (which I figured out by trial and error), or something else?
Upvotes: 0
Views: 579
Reputation: 85767
You seem to be confused about the way programs are started internally and how commands are interpreted by the shell.
In unix, starting a program involves three parameters:
When you type a command into the shell, many things happen, but in the simplest case it's just a bunch of space-separated words:
$ foo bar baz
($
represents the shell prompt, not something you type.)
The shell splits this line into three words (foo
, bar
, baz
) and interprets the first one as the name of a program (to be looked up in the directories listed in the PATH
variable). Let's assume PATH
lists /usr/bin
and there is indeed a /usr/bin/foo
program.
Now the shell starts the program as follows (pseudo-code):
exec("/usr/bin/foo", ["foo", "bar", "baz"], [...])
I.e. we run the executable in /usr/bin/foo
, passing a list of three strings as arguments.
([...]
represents the environment, which we're going to ignore from now on.)
What happens if you do this instead?
$ foo "bar baz"
The quotes affect the way the shell splits the line into words. In particular, " "
(a space) in quotes does not act as a separator but is taken literally. This gives us a two-element list (foo
, bar baz
). Note that the quotes are not part of the words themselves.
Internally this translates to the following invocation:
exec("/usr/bin/foo", ["foo", "bar baz"], [...])
Again, the second argument simply contains a space. There are no embedded quotes.
So what happens with a command like
$ xargs -pi find ~/Multimedia/Musik/flac/ -name "\""{}"\""
?
This will again be parsed into a list of words by the shell. ~
is replaced by the name of your home directory. "\""
is just a convoluted way of writing \"
or '"'
(i.e. a literal "
character). The list we end up with is xargs
, -pi
, find
, /home/madZeo/Multimedia/Musik/flac/
, -name
, "{}"
. This translates to the following invocation:
exec("/usr/bin/xargs", ["xargs", "-pi", "find", "/home/madZeo/Multimedia/Musik/flac/", "-name", "\"{}\""], [...])
Note how the last argument is the 4-character string "{}"
.
xargs
treats its first argument (-pi
) as an option specification. In particular, -i
tells it to replace {}
in the argument list by the current value read from standard input.
xargs
then reads a line from its standard input, which (because of your echo
pipe) gives 01.Here Comes The Night Time II.flac
.
This gets substituted in in place of {}
, yielding the list find
, /home/madZeo/Multimedia/Musik/flac/
, -name
, "01.Here Comes The Night Time II.flac"
. xargs
then invokes find
like this:
exec("/usr/bin/find", ["find", "/home/madZeo/Multimedia/Musik/flac/", "-name", "\"01.Here Comes The Night Time II.flac\""], [...])
This tells find
to look for a file whose name literally starts with "
(a quote character). No such file exists, so this fails.
The fix is to write the command like this:
$ xargs -pi find ~/Multimedia/Musik/flac/ -name {}
This ultimately ends up running
exec("/usr/bin/find", ["find", "/home/madZeo/Multimedia/Musik/flac/", "-name", "01.Here Comes The Night Time II.flac"], [...])
, which is what you want.
The issue is that xargs
runs its subcommand (find
in this case) directly. It does not construct a new command line that gets re-parsed by the shell. It does not split its incoming argument on spaces, it does not interpret quotes, it doesn't care about "special" characters like $
or *
or \
. It simply takes the list of words it was given, replaces any occurrence of the substring {}
by the current input, then executes it.
If you naively take this final command and paste it into your shell, it will undergo word splitting, quote removal, etc., leading to a different outcome.
Upvotes: 2