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.

Suppose I have a script like this:

#!/bin/bash
printf '%q\n' "b c"

Executing the script prints:

b\ c

on the commandline.

Now, being in a directory which contains a file named b c I want to pass the output of my script to a command like ls:

$ ls $(./myscript)

The problem here is that b c is split to b\ and c, i.e. two arguments, and ls of course cannot find them. Is there some way to circumvent this? I thought escaping the space in the output would be enough.

share|improve this question
1  
Your example file names were inconsistent (a b, b c, b\ c). I think I've corrected them, please review that my edit still corresponds to your problem. –  Gilles Feb 28 '13 at 14:28

2 Answers 2

up vote 3 down vote accepted

Use quotes:

$ ls "$(./myscript)"
share|improve this answer
1  
This doesn't work either. –  helpermethod Feb 28 '13 at 14:17
1  
@helpermethod It does, and it is the right answer, if you have a single file and print just its name without quotes. –  Gilles Feb 28 '13 at 14:29
    
@Gilles Correct. The problem was the %q flag of the printf builtin. Changing that to %s solved the problem. –  helpermethod Feb 28 '13 at 14:38
    
@Gilles What about printing multiple files? –  helpermethod Feb 28 '13 at 14:50
    
Unfortunately the argument passing / word splitting mechanism of the shell does not make this possible in any way that is both simple and correct, AFAIK. You would have to invoke ls inside your script where you still have the separate arguments. If you don't want to hardcode ls and want it to be different commands, you could have the script take in a command as an argument but the script would still have to be the one to run it. –  jw013 Feb 28 '13 at 16:06

Command substitution

If you need to pass the output from a command as an argument to another command, use command substitution. Similarly to what happens with variable substitutions, the result of command substitution undergoes word splitting (splitting into separate words at each character in $IFS, whitespace by default) and wildcard expansion (globbing of file names). So , always use double quotes around command substitutions unless you want splitting and globbing to happen.

ls -- "$(./myscript)"

-- tells ls to stop processing options, this is necessary in case the file name printed by the script begins with -.

You have to print the literal file names. Don't add extra quoting from within the script.

There's actually one edge case where the argument is mangled: command substitution strips off any final newline from the command output. So whether the output is foo↲bar or foo↲bar↲ or foo↲bar↲↲↲ (I use to represent a newline character), the argument of the ls command will be just foo↲bar. This hardly ever happens in practice, because although newlines are allowed in file names, nobody uses them. Nonetheless it could be a security concern, because someone trying to attack your system might put newlines in a file name.

To handle final newlines, arrange for the command to output an extra non-newline character, and strip it off from the result of the command substitution.

output=$(./myscript; echo a)
output="${output%a}"
ls -- "$output"

Passing multiple file names to a command

The one character that cannot appear in a file name or a command line argument is the null byte. The safe way of outputting multiple file names or other arguments to be passed to a command is to separate them with null bytes and call xargs -0. This works on Linux, *BSD and OSX but is an extension to POSIX that other unix variants may lack. If the resulting command line is too long, xargs splits it and invokes the command multiple times.

… | xargs -0 ls

Without -0, xargs expects an input format that no other common command produces. It is safe for use if the input contains no whitespace nor any of \"'.

A way to quote input for xargs is to put a backslash before at least the following characters: newline, tab, space and \"'.

for x in …; do
  printf '%s\n' "$x" | sed -e 's/[  '\''\"]/\\&/g' -e 's/$/\\$/' -e '$ s/\\$//'
done | xargs ls

Put a space and a tab instead of the two space characters inside brackets in the sed command.

Further notes on unprotected substitutions and quoting

In an unprotected variable or command susbtitution, for each word to which globbing happens, the four characters \[*? are expanded. However, only the characters [*? trigger globbing: a backslash without other globbing characters remains intact. Thus, if the current directory contains three files called foo, goo and bar, then:

  • ls $(echo '?oo bar') calls ls with the arguments foo, goo and bar
  • ls $(echo '?\oo b\ar') calls ls with the arguments foo, goo and b\ar
share|improve this answer

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.