Question

Such 'identity' function should satisfy the following 2 properties :

identity $(identity a\ b c\ d)
# (Expected output:)
# a b
# c d

And, given the following 'argv_count' function:

argv_count () { echo "argv_count('$@'):$#"; }
argv_count $(identity a\ b c\ d)
# (Expected output:)
# argv_count('a b c d'):2

Additional quotes could be introduced in the tests if need.

A simple candidate such as the following fail to pass the second test:

identity () { for arg in "$@"; do echo "$arg"; done; }

cat is not a correct solution, as it is an identity function relative to stdin|stdout.

Was it helpful?

Solution

No, it is not possible. The reason is that when the shell parses the output of a command substitution (that is, $(somecommand)), it performs word splitting and wildcard expansion but no quote or escape evaluation. This means that if identity's output includes a space, the shell will treat that as a separator between "words" (i.e. arguments to the other program) no matter what quotes/escapes/whatever you add to try to avoid this. Worse, any wildcard-containing words in the output will be expanded into lists of matching files (if there are any). This means that $(identity 'foo * bar') is doubly doomed to failure.

With that said, there are ways to sort of fake it by changing the shell settings. For example, set -f will turn off wildcard expansion, solving that problem -- except that you have to set it in the parent shell before running identity, and then set it back to normal afterwards or lots of other things will break. Similarly, you could change IFS to prevent word splitting from treating spaces as separators -- but again you'd have to change it in the parent shell and set it back afterward, and it'd then cause trouble for whatever replacement separator character you chose. So you can fake it, but it's pretty bad fakery.

EDIT: As Michael Kropat pointed out, using eval is another way to "fake" it, and is more flexible if done carefully.

OTHER TIPS

If you're willing to use eval you can work around the word-splitting on returned values:

$ argv_count a\ b c\ d
argv_count('a b c d'):2
$ identity() { printf ' %q' "$@"; }
$ eval argv_count "$(identity a\ b c\ d)"
argv_count('a b c d'):2
$ eval argv_count "$(eval identity "$(identity a\ b c\ d)")"
argv_count('a b c d'):2

Or with Gordon Davisson's trickier case:

$ argv_count $'foo\t * bar'
argv_count('foo  * bar'):1
$ eval argv_count "$(eval identity "$(identity $'foo\t * bar')")"
argv_count('foo  * bar'):1
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top