Home > Enterprise >  About the error encountered when noninteractively running comand by ssh
About the error encountered when noninteractively running comand by ssh

Time:06-12

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.)

  • Related