My problem is that I want to pass a string containing arbitrarily complex bash to bash -c "<command>"
, but I've found one case that doesn't behave as expected.
bash -c "s=\"hi\"; for _ in 1 2 3; do echo $s; s=\"$s hi\"; done"
I'd expect the command to output a little right angled triangle of hi's, like so:
hi
hi hi
hi hi hi
But instead the command outputs:
Which presumably means $s
is unset. My suspicion is that it's because of how bash is parsed, in that it's parsed line by line.
How could I make this one line bash -c
command work? Is this a downside / something to be wary of with this approach of simply passing a potentially quite complex bash script in one line to bash -c
?
Thanks very much in advance :) Luke
CodePudding user response:
Why using another bash
shell ?
You simply need:
s="hi"
for _ in 1 2 3; do
echo "$s"
s="$s hi"
done
Or using here-doc with single quote to prevent shell expansion:
bash<<'EOF'
s="hi"
for _ in 1 2 3; do
echo "$s"
s="$s hi"
done
EOF
Or reusing your command with proper single quotes and no escaping:
bash -c 's="hi"; for _ in 1 2 3; do echo $s; s="$s hi"; done'
Learn how to quote properly in shell, it's very important :
"Double quote" every literal that contains spaces/metacharacters and every expansion:
"$var"
,"$(command "$var")"
,"${array[@]}"
,"a & b"
. Use'single quotes'
for code or literal$'s: 'Costs $5 US'
,ssh host 'echo "$HOSTNAME"'
. See
http://mywiki.wooledge.org/Quotes
http://mywiki.wooledge.org/Arguments
http://wiki.bash-hackers.org/syntax/words
when-is-double-quoting-necessary
Output
hi
hi hi
hi hi hi
CodePudding user response:
The immediate problem is that the $
characters aren't escaped, so $s
is getting expanded (to the empty string) before the command is passed to bash -c
. You can see this by replacing bash -c
with echo
:
$ printf '%s\n' "s=\"hi\"; for _ in 1 2 3; do echo $s; s=\"$s hi\"; done"
s="hi"; for _ in 1 2 3; do echo ; s=" hi"; done
You can see that the echo $s;
part turned into just echo ;
, and s=\"$s hi\"
into s=" hi"
.
You can either escape the $
characters, or switch to single-quoting the entire command instead of double-quoting it (and remove the other escapes).
More generally, anytime you have a situation like this where a string/command/whatever is passing through multiple levels of parsing (here, it's first parsed by your interactive shell, then again by the bash -c
shell), it's critical to understand what's being expanded/applied by each level vs what's passed through to the next level. You often need multiple levels of quoting and/or escaping to control exactly when in the parsing process a particular thing gets acted on (vs just passed through to the next level). Above, I used printf
to see what had happened after just the first level of parsing, and doing tests like this can help a lot with understanding what's going on. (Note: I prefer printf '%s\n'
over echo
, because some versions of echo
will try to interpret escape sequences themselves, which can add confusion.)