How to Use Xargs to Copy Files That Have Spaces and Quotes in Their Names

How can I use xargs to copy files that have spaces and quotes in their names?

You can combine all of that into a single find command:

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

This will handle filenames and directories with spaces in them. You can use -name to get case-sensitive results.

Note: The -- flag passed to cp prevents it from processing files starting with - as options.

Make xargs handle filenames that contain spaces

The xargs command takes white space characters (tabs, spaces, new lines) as delimiters.

You can narrow it down only for the new line characters ('\n') with -d option like this:

ls *.mp3 | xargs -d '\n' mplayer

It works only with GNU xargs.

For MacOS:

ls *.mp3 | tr \\n \\0 | xargs -0 mplayer

The more simplistic and practically useful approach (when don't need to process the filenames further):

mplayer *.mp3

How to grep and copy files with spaces in their names

Use --null options for both grep and xargs:

grep --null --files-with-matches 'word' * | xargs --null cp -t "$target_dir"

The --null options cause the commands to terminate the filenames with a zero byte instead of newlines.

Find and xargs to correctly handle filenames with spaces in their names


find . -name "*.php" -print0 | xargs -0 sed -i 's/string1/string2/g'

You can also do it without xargs at all:

find . -name "*.php" -execdir sed -i 's/string1/string2/g' {} +

how to output file names surrounded with quotes in SINGLE line?

this should work

find $PWD | sed 's/^/"/g' | sed 's/$/"/g' | tr '\n' ' '

EDIT:

This should be more efficient than the previous one.

find $PWD | sed -e 's/^/"/g' -e 's/$/"/g' | tr '\n' ' '

@Timofey's solution would work with a tr in the end, and should be the most efficient.

find $PWD -exec echo -n '"{}" ' \; | tr '\n' ' '

How to properly pass filenames with spaces with $* from xargs to sed via sh?

Finally ended up with this single-line script:

sed -nr 's/.*>(https?:\/\/[^<]+)<.*/\1/p' "$@" | xargs -I% sh -c 'sed -i "" "s@%@`curl -s % | base64`@" "$@"' _ "$@"

which does properly support filenames with or without spaces.

xargs or tail gives error with spaces in directory names

Try -print0 option with -0 option in xargs:

find . -name batch_output.txt -type f -print0 | xargs -0 -n 1 tail -n +2

As per man find:

-print0
This primary always evaluates to true. It prints the pathname of the current file
to standard output, followed by an ASCII NUL character (character code 0).

copy files with double quotes and matching pattern

Put the * outside of the quotes, otherwise it wouldn't get expanded:

cp "test foo.cls"* /dir
  • https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html#Shell-Expansions
  • https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html

Why don't I need to pass \ {}\ to xargs when handling filenames with spaces?

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:

  1. A file name. This is a string containing the path to the program to be run.
  2. A list of strings. By convention we call these "command line arguments".
  3. Another list of strings. By convention we call these "the environment", but at the OS level it's just another string list. However, all programs/libraries conspire to keep this hidden from both the user and the application programmer.

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.

xargs: how to have literal double-quotes in replacement?

GNU Parallel was built specifically to deal with xargs bad handling of special chars:

<source parallel curl -X POST -H "application/json" myURL -d {}

Not only will it quote " correctly, it will quote any string correctly, so it will be interpreted as a single argument by curl.

Added bonus: Your queries will run in parallel - one query per cpu.



Related Topics



Leave a reply



Submit