ssh usrname@ip 'bash -c "for i in 1 2 3; do date;sleep 1;done" '
works well, and here is the output of the said command:
Sun Jun 1 11:02:12 CST 2022
Sun Jun 1 11:02:13 CST 2022
Sun Jun 1 11:02:14 CST 2022
Whereas the command below encounters error:
ssh usrname@ip 'bash -c "for i in $(seq 1 3); do date;sleep 1;done" '
Here is the error message:
bash: -c: line 1: syntax error near unexpected token `2'
bash: -c: line 1: `2'
What confuses is that for i in $(seq 1 3); do date;sleep 1;done
works well in local bash or remote bash.
Tips:You can try ssh usrname@ip 'bash -c "for i in 1 2 3; do date;sleep 1;done" '
in your local computer which has sshd
service by replacing the ip
to your computer.
CodePudding user response:
You'll get the exact same error running the command locally (i.e. without ssh
):
$ bash -c "for i in $(seq 1 3); do date;sleep 1;done"
bash: -c: line 1: syntax error near unexpected token `2'
bash: -c: line 1: `2'
The reason it happens has to do with the order in which things get expanded, and when word-splitting does (or doesn't) happen. Specifically, it's because the $(seq 1 3)
part gets expanded before it's passed to bash
as part of the command to be executed But because it's in double-quotes, it's not subject to word-splitting, so the newlines between the numbers are left intact rather than being converted to breaks between words. You can see this with by turning on tracing with set -x
:
$ set -x
$ bash -c "for i in $(seq 1 3); do date;sleep 1;done"
seq 1 3
bash -c 'for i in 1
2
3; do date;sleep 1;done'
bash: -c: line 1: syntax error near unexpected token `2'
bash: -c: line 1: `2'
...so it runs seq 1 3
first. That prints "1", "2", and "3" with newlines between them, so that's how they get included in the argument to bash -c
. bash then treats the newlines as command delimiters, and you can't have a command delimiter in the middle of the list of things for for
to iterate over. So bash
sees a 2
where it expected a done
, and panics.
The reason it normally works is because you usually put $(seq 1 3)
in a context where it's not double-quoted, so the newlines just turn into word breaks. Compare:
$ echo $(seq 1 3) # No double-quotes, newlines convert to word breaks
1 2 3
$ echo "$(seq 1 3)" # With double-quotes, newlines are retained
1
2
3
You can fix this by escaping the $
to delay interpretation of the $( )
substitution until it's passed to bash -c
(which is after the double-quotes have been applied and removed):
$ bash -c "for i in \$(seq 1 3); do date;sleep 1;done"
Sat Jun 11 23:55:54 PDT 2022
Sat Jun 11 23:55:55 PDT 2022
Sat Jun 11 23:55:56 PDT 2022
(And that should work over ssh
as well, provided the whole thing is in single-quotes so the local shell doesn't mess with it.)