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

LinuxMacosUnixCommand LineXargs

Linux Problem Overview


I'm trying to copy a bunch of files below a directory and a number of the files have spaces and single-quotes in their names. When I try to string together find and grep with xargs, I get the following error:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

Any suggestions for a more robust usage of xargs?

This is on Mac OS X 10.5.3 (Leopard) with BSD xargs.

Linux Solutions


Solution 1 - Linux

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.

Solution 2 - Linux

find . -print0 | grep --null 'FooBar' | xargs -0 ...

I don't know about whether grep supports --null, nor whether xargs supports -0, on Leopard, but on GNU it's all good.

Solution 3 - Linux

The easiest way to do what the original poster wants is to change the delimiter from any whitespace to just the end-of-line character like this:

find whatever ... | xargs -d "\n" cp -t /var/tmp

Solution 4 - Linux

This is more efficient as it does not run "cp" multiple times:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

Solution 5 - Linux

I ran into the same problem. Here's how I solved it:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

I used sed to substitute each line of input with the same line, but surrounded by double quotes. From the sed man page, "...An ampersand (``&'') appearing in the replacement is replaced by the string matching the RE..." -- in this case, .*, the entire line.

This solves the xargs: unterminated quote error.

Solution 6 - Linux

This method works on Mac OS X v10.7.5 (Lion):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

I also tested the exact syntax you posted. That also worked fine on 10.7.5.

Solution 7 - Linux

Just don't use xargs. It is a neat program but it doesn't go well with find when faced with non trivial cases.

Here is a portable (POSIX) solution, i.e. one that doesn't require find, xargs or cp GNU specific extensions:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

Note the ending + instead of the more usual ;.

This solution:

  • correctly handles files and directories with embedded spaces, newlines or whatever exotic characters.

  • works on any Unix and Linux system, even those not providing the GNU toolkit.

  • doesn't use xargs which is a nice and useful program, but requires too much tweaking and non standard features to properly handle find output.

  • is also more efficient (read faster) than the accepted and most if not all of the other answers.

Note also that despite what is stated in some other replies or comments quoting {} is useless (unless you are using the exotic fishshell).

Solution 8 - Linux

Look into using the --null commandline option for xargs with the -print0 option in find.

Solution 9 - Linux

For those who relies on commands, other than find, eg ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar

Solution 10 - Linux

find | perl -lne 'print quotemeta' | xargs ls -d

I believe that this will work reliably for any character except line-feed (and I suspect that if you've got line-feeds in your filenames, then you've got worse problems than this). It doesn't require GNU findutils, just Perl, so it should work pretty-much anywhere.

Solution 11 - Linux

I have found that the following syntax works well for me.

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200

In this example, I am looking for the largest 200 files over 1,000,000 bytes in the filesystem mounted at "/usr/pcapps".

The Perl line-liner between "find" and "xargs" escapes/quotes each blank so "xargs" passes any filename with embedded blanks to "ls" as a single argument.

Solution 12 - Linux

With Bash (not POSIX) you can use process substitution to get the current line inside a variable. This enables you to use quotes to escape special characters:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)

Solution 13 - Linux

Frame challenge — you're asking how to use xargs. The answer is: you don't use xargs, because you don't need it.

The comment by user80168 describes a way to do this directly with cp, without calling cp for every file:

find . -name '*FooBar*' -exec cp -t /tmp -- {} +

This works because:

  • the cp -t flag allows to give the target directory near the beginning of cp, rather than near the end. From man cp:

> -t, --target-directory=DIRECTORY > copy all SOURCE arguments into DIRECTORY

  • The -- flag tells cp to interpret everything after as a filename, not a flag, so files starting with - or -- do not confuse cp; you still need this because the -/-- characters are interpreted by cp, whereas any other special characters are interpreted by the shell.

  • The find -exec command {} + variant essentially does the same as xargs. From man find:

> -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 invoca‐ > 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, and (when find is being invoked > from a shell) it should be quoted (for example, '{}') to protect > it from interpretation by shells. The command is executed in > the starting directory. If any invocation returns a non-zero > value as exit status, then find returns a non-zero exit status. > If find encounters an error, this can sometimes cause an immedi‐ > ate exit, so some pending commands may not be run at all. This > variant of -exec always returns true.

By using this in find directly, this avoids the need of a pipe or a shell invocation, such that you don't need to worry about any nasty characters in filenames.

Solution 14 - Linux

Be aware that most of the options discussed in other answers are not standard on platforms that do not use the GNU utilities (Solaris, AIX, HP-UX, for instance). See the POSIX specification for 'standard' xargs behaviour.

I also find the behaviour of xargs whereby it runs the command at least once, even with no input, to be a nuisance.

I wrote my own private version of xargs (xargl) to deal with the problems of spaces in names (only newlines separate - though the 'find ... -print0' and 'xargs -0' combination is pretty neat given that file names cannot contain ASCII NUL '\0' characters. My xargl isn't as complete as it would need to be to be worth publishing - especially since GNU has facilities that are at least as good.

Solution 15 - Linux

For me, I was trying to do something a little different. I wanted to copy my .txt files into my tmp folder. The .txt filenames contain spaces and apostrophe characters. This worked on my Mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/

Solution 16 - Linux

bill_starr's Perl version won't work well for embedded newlines (only copes with spaces). For those on e.g. Solaris where you don't have the GNU tools, a more complete version might be (using sed)...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

adjust the find and grep arguments or other commands as you require, but the sed will fix your embedded newlines/spaces/tabs.

Solution 17 - Linux

I used Bill Star's answer slightly modified on Solaris:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

This will put quotes around each line. I didn't use the '-l' option although it probably would help.

The file list I was going though might have '-', but not newlines. I haven't used the output file with any other commands as I want to review what was found before I just start massively deleting them via xargs.

Solution 18 - Linux

If find and xarg versions on your system doesn't support -print0 and -0 switches (for example AIX find and xargs) you can use this terribly looking code:

 find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest

Here sed will take care of escaping the spaces and quotes for xargs.

Tested on AIX 5.3

Solution 19 - Linux

I played with this a little, started contemplating modifying xargs, and realised that for the kind of use case we're talking about here, a simple reimplementation in Python is a better idea.

For one thing, having ~80 lines of code for the whole thing means it is easy to figure out what is going on, and if different behaviour is required, you can just hack it into a new script in less time than it takes to get a reply on somewhere like Stack Overflow.

See https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs and https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py.

With yargs as written (and Python 3 installed) you can type:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

to do the copying 203 files at a time. (Here 203 is just a placeholder, of course, and using a strange number like 203 makes it clear that this number has no other significance.)

If you really want something faster and without the need for Python, take zargs and yargs as prototypes and rewrite in C++ or C.

Solution 20 - Linux

I created a small portable wrapper script called "xargsL" around "xargs" which addresses most of the problems.

Contrary to xargs, xargsL accepts one pathname per line. The pathnames may contain any character except (obviously) newline or NUL bytes.

No quoting is allowed or supported in the file list - your file names may contain all sorts of whitespace, backslashes, backticks, shell wildcard characters and the like - xargsL will process them as literal characters, no harm done.

As an added bonus feature, xargsL will not run the command once if there is no input!

Note the difference:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Any arguments given to xargsL will be passed through to xargs.

Here is the "xargsL" POSIX shell script:

> #! /bin/sh > # Line-based version of "xargs" (one pathname per line which may contain any > # amount of whitespace except for newlines) with the added bonus feature that > # it will not execute the command if the input file is empty. > # > # Version 2018.76.3 > # > # Copyright (c) 2018 Guenther Brunthaler. All rights reserved. > # > # This script is free software. > # Distribution is permitted under the terms of the GPLv3. >
> set -e > trap 'test $? = 0 || echo "$0 failed!" >& 2' 0 >
> if IFS= read -r first > then > { > printf '%s\n' "$first" > cat > } | sed 's/./\&/g' | xargs ${1+"$@"} > fi

Put the script into some directory in your $PATH and don't forget to

$ chmod +x xargsL

the script there to make it executable.

Solution 21 - Linux

You might need to grep Foobar directory like:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

Solution 22 - Linux

If you are using Bash, you can convert stdout to an array of lines by mapfile:

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

The benefits are:

  • It's built-in, so it's faster.

  • Execute the command with all file names in one time, so it's faster.

  • You can append other arguments to the file names. For cp, you can also:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    however, some commands don't have such feature.

The disadvantages:

  • Maybe not scale well if there are too many file names. (The limit? I don't know, but I had tested with 10 MB list file which includes 10000+ file names with no problem, under Debian)

Well... who knows if Bash is available on OS X?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDrew StephensView Question on Stackoverflow
Solution 1 - LinuxgodbykView Answer on Stackoverflow
Solution 2 - LinuxChris Jester-YoungView Answer on Stackoverflow
Solution 3 - Linuxuser87601View Answer on Stackoverflow
Solution 4 - LinuxTometzkyView Answer on Stackoverflow
Solution 5 - LinuxoyouareatubeoView Answer on Stackoverflow
Solution 6 - LinuxfrediyView Answer on Stackoverflow
Solution 7 - LinuxjlliagreView Answer on Stackoverflow
Solution 8 - LinuxShannon NelsonView Answer on Stackoverflow
Solution 9 - LinuxAleksandr GuidrevitchView Answer on Stackoverflow
Solution 10 - LinuxmavitView Answer on Stackoverflow
Solution 11 - Linuxbill_starrView Answer on Stackoverflow
Solution 12 - LinuxStackedCrookedView Answer on Stackoverflow
Solution 13 - LinuxgerritView Answer on Stackoverflow
Solution 14 - LinuxJonathan LefflerView Answer on Stackoverflow
Solution 15 - LinuxMoisesView Answer on Stackoverflow
Solution 16 - Linuxrjb1View Answer on Stackoverflow
Solution 17 - LinuxCarl Yamamoto-FurstView Answer on Stackoverflow
Solution 18 - LinuxJan PtáčníkView Answer on Stackoverflow
Solution 19 - LinuxJohn AllsupView Answer on Stackoverflow
Solution 20 - LinuxGuenther BrunthalerView Answer on Stackoverflow
Solution 21 - LinuxfredView Answer on Stackoverflow
Solution 22 - LinuxXiè JìléiView Answer on Stackoverflow