Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I want to pipe file names to other programs, but they all choke when the names contain spaces.

Let's say I have a file called.

foo bar

How can I get find to return the correct name?

Obviously I want:

foo\ bar

or:

"foo bar"

EDIT: I don't want to go through xargs, I want to get a correctly formatted string out of find so that I can pipe the string of file names directly to another program.

share|improve this question
4  
what are you piping it to? are you aware of the -exec flag with find? you could potentially alleviate this error and make your command more efficient by doing -exec instead of piping it to other commands. Just my $.02 –  h3rrmiller Jul 1 '13 at 15:56
5  
@bug: find formats the file names just fine; they are written one name per line. (Of course, this is ambiguous if a filename contains a newline character.) So the problem is the receiving end "choking" when it gets a space, which means you have to tell us what the receiving end is if you want a meaningful answer. –  rici Jul 1 '13 at 16:40
2  
What you call "properly formatted" is really "escaped for consumption by the shell". Most utilities which can read a bunch of file names would choke on a shell-escaped name, but it would in fact make sense for (say) find to offer an option to output file names in a format suitable for the shell. In general, though, the -print0 GNU find extension works fine for many other scenarios (too), and you should learn to use it in any event. –  tripleee Jul 1 '13 at 16:55
1  
@bug: By the way, ls $(command...) does not feed the list through stdin. It puts the output of $(command...) directly into the command line. In that case, it is the shell which is reading from the c, and it will use the current value of $IFS to decide how to wordsplit the output. In general, you're better off using xargs. You won't notice a performance hit. –  rici Jul 1 '13 at 18:06
2  
find -printf '"%p"\n' will add double quotes around each found name, but will not properly quote any double quotes in a file name. If your file names do not have any embedded double quotes, you can ignore the problem: or pipe through sed 's/"/&&/g;s/^""/"/;s/""$/"/'. If your file names end up being handled by the shell, you should probably use single quotes instead of double quotes, though (otherwise sweet$HOME will become something like sheet/home/you). And this is still not very robust against file names with newlines in them. How do you want to handle those? –  tripleee Jul 2 '13 at 9:23

3 Answers 3

Using -print0 is one option, but not all programs support using nullbyte-delimited data streams, so you'll have to use xargs with the -0 option for some things, as Gnouc's answer noted.

An alternative would be to use find's -exec or -execdir options. The first of the following will feed the filenames to somecommand one at a time, while the second will expand to a list of files:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

You may find that you are better off using globbing in many cases. If you have a modern shell (bash 4+, zsh, ksh), you can get recursive globbing with globstar (**). In bash, you have to set this:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

I have a line saying shopt -s globstar extglob in my .bashrc, so this is always enabled for me (and so are extended globs, which are also useful).

If you don't want recursiveness, obviously just use ./*.txt instead, to use every *.txt in the working directory. find has some very useful fine-grained searching capabilities, and is mandatory for tens of thousands of files (at which point you'll run into the shell's maximum number of arguments), but for day-to-day usage it is often unnecessary.

share|improve this answer

You must combine find and xargs:

find . -type f -print0 | xargs -0 <command>

-0 option tells xargs to use the ASCII NUL character instead of space to end (separate) the filenames.

example:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
share|improve this answer
    
Doesn't work. When I run ls $(find . -maxdepth 1 -type f -print0 | xargs -0) I get ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory –  bug Jul 1 '13 at 16:01
1  
Have you tried it the way Gnouc actually wrote it? If you insist on doing it your way, try enclosing the $(..) in double-quotes "$(..)" –  evilsoup Jul 1 '13 at 16:03
2  
@bug: your command is wrong. Try exactly I worte and read the manpage of find and xargs. –  Gnouc Jul 1 '13 at 16:12
    
I see, then again I want to get a formatted string which I could pipe directly. –  bug Jul 1 '13 at 16:18
    
@bug: Just use xargs -0 <your program> –  Gnouc Jul 1 '13 at 16:41

Personally, I'd use the -exec find action to solve this sort of problem. Or, if necessary, xargs, which allows for parallel execution.

However, there is a way to get find to produce a bash-readable list of filenames. Unsurprisingly, it uses -exec and bash, in particular an extension to the printf command:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

However, while that will print out correctly shell-escaped words, it will not be usable with $(...), because $(...) does not interpret quotes or escapes. (The resut of $(...) is subject to word splitting and pathname expansion, unless surrounded by quotes.) So the following will not do what you want:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

What you would have to do is:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Note that I have made no real attempt to test the above monstrosity.)

But then you might as well do:

find ... -exec ls {} +
share|improve this answer
    
I don't think the ls scenario adequately captures the OP's use case, but this is only speculation, since we have not been shown what (s)he is actually trying to accomplish. This solution actually works very nicely; I get the output I (vaguely) expected for all the funny file names I tried, including touch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')" –  tripleee Jul 3 '13 at 11:17
    
@triplee: I have no idea what OP wants to do either. The only real advantage of constructing the quoted string to pass to eval is that you don't have to pass it to eval yet; you could save it in a parameter and use it later, perhaps several times with different commands. However, OP gives no indication that that is the use case (and if it were, it might be better to put the filenames into an array, although that's tricky too.) –  rici Jul 3 '13 at 14:33

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.