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:
- A file name. This is a string containing the path to the program to be run.
- A list of strings. By convention we call these "command line arguments".
- 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
How to Install Nuget from Command Line on Linux
Is There an Invalid Pthread_T Id
Splitting a File in Linux Based on Content
When Should I Use Tcp_Nodelay and When Tcp_Cork
Application Control of Tcp Retransmission on Linux
How to Avoid Transparent_Hugepage/Defrag Warning from Mongodb
"Zero Copy Networking" VS "Kernel Bypass"
Linux Capabilities (Setcap) Seems to Disable Ld_Library_Path
Get Free Disk Space with Df to Just Display Free Space in Kb
Setting Creation or Change Timestamps
"In-Source Builds Are Not Allowed" in Cmake
Truncating a File While It's Being Used (Linux)
Multiple Websites on Nginx & Sites-Available