When you leave a variable substitution $var
or a command substitution $(cmd)
unquoted, the result undergoes the following transformations:
- Split the resulting string into words. The split happens at whitespace (sequences of space, tabs and newlines); this can be configured by setting
IFS
(see 1, 2).
- Each of the resulting word is treated as a glob pattern, and if it matches some files, the word is replaced by the list of file names.
Note that the result is not a string but a list of strings. Furthermore, note that quote characters like "
are not involved here; they are part of the shell source syntax, not of string expansion.
A general rule of shell programming is to always put double quotes around variable and command substitutions, unless you know why you have to leave them off. So in test.sh
, write echo "Argument 1: $1"
. To pass arguments to test.sh
, you're in trouble: you need to pass a list of words from args.sh
to test.sh
, but your chosen method involves a command substitution and that only provides the way for a simple string.
If you can guarantee that the arguments to be passed do not contain any newlines, and it is acceptable to change the invocation process slightly, you can set IFS
to contain only a newline. Make sure that args.sh
outputs exactly one file name per line with no stray quotes.
IFS='
'
test.sh $(args.sh)
unset IFS
If the arguments may contain arbitrary characters (presumably except null bytes, which you cannot pass as arguments), you'll need to perform some encoding. Any encoding will do; of course, it won't be the same as passing the arguments directly: that's not possible. For example, in args.sh
(replace \t
by an actual tab character if your shell doesn't support it):
for x; do
printf '%s_\n' "$x" |
sed -e 's/q/qq/g' -e 's/ /qs/g' -e 's/\t/qt/g' -e 's/$/qn/'
done
and in test.sh
:
for arg; do
decoded=$(printf '%s\n' "$arg" |
sed -e 's/qs/ /g' -e 's/qt/\t/g' -e 's/qn/\n/g' -e 's/qq/q/g')
decoded=${decoded%_qn}
# process "$decoded"
done
You may prefer to change test.sh
to accept a list of strings as input. If the strings don't contain newlines, invoke args.sh | test.sh
and use this in args.sh
(explanation):
while IFS= read -r line; do
# process "$line"
done
Another method that prevents the need for quoting altogether is to invoke the second script from the first.
…
args.sh "$@"