Per zanco's answer, you're not providing a remote command to ssh
, given how the shell parses the command line. To solve this problem, change the syntax of your ssh
command invocation so that the remote command is comprised of a syntactically correct, multi-line string.
There are a variety of syntaxes that can be used. For example, since commands can be piped into bash
and sh
, and probably other shells too, the simplest solution is to just combine ssh
shell invocation with heredocs:
ssh user@server /bin/bash <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
Note that executing the above without /bin/bash
will result in the warning Pseudo-terminal will not be allocated because stdin is not a terminal
. Also note that EOT
is surrounded by single-quotes, so that bash
recognizes the heredoc as a nowdoc, turning off local variable interpolation so that the command text will be passed as-is to ssh
.
If you are a fan of pipes, you can rewrite the above as follows:
cat <<'EOT' | ssh user@server /bin/bash
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
The same caveat about /bin/bash
applies to the above.
Another valid approach is to pass the multi-line remote command as a single string, using multiple layers of bash
variable interpolation as follows:
ssh user@server "$( cat <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
)"
The solution above fixes this problem in the following manner:
ssh user@server
is parsed by bash, and is interpreted to be the ssh
command, followed by an argument user@server
to be passed to the ssh
command
"
begins an interpolated string, which when completed, will comprise an argument to be passed to the ssh
command, which in this case will be interpreted by ssh
to be the remote command to execute as user@server
$(
begins a command to be executed, with the output being captured by the surrounding interpolated string
cat
is a command to output the contents of whatever file follows. The output of cat
will be passed back into the capturing interpolated string
<<
begins a bash heredoc
'EOT'
specifies that the name of the heredoc is EOT. The single quotes '
surrounding EOT specifies that the heredoc should be parsed as a nowdoc, which is a special form of heredoc in which the contents do not get interpolated by bash, but rather passed on in literal format
Any content that is encountered between <<'EOT'
and <newline>EOT<newline>
will be appended to the nowdoc output
EOT
terminates the nowdoc, resulting in a nowdoc temporary file being created and passed back to the calling cat
command. cat
outputs the nowdoc and passes the output back to the capturing interpolated string
)
concludes the command to be executed
"
concludes the capturing interpolated string. The contents of the interpolated string will be passed back to ssh
as a single command line argument, which ssh
will interpret as the remote command to execute as user@server
If you need to avoid using external tools like cat
, and don't mind having two statements instead of one, use the read
built-in with a heredoc to generate the SSH command:
IFS='' read -r -d '' SSH_COMMAND <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
ssh user@server "${SSH_COMMAND}"