Send commands from Windows to Linux via Batch Script SSH
Partial answer. I cannot explain the behavior of the script but I can explain this:
ssh -T [email protected] 'echo hello, world'
givesbash: echo hello, world: command not found
The command seems to parse as one token instead of seeing
echo
as the command and the rest as the argument.
The explanation is in this answer on SO. It reads:
Single quotes are not used at all by the
cmd.exe
command processor except to enclose the command to run within aFOR /F
statement
This means your ssh
got the following arguments: -T
, [email protected]
, 'echo hello,
, world'
. Single-quotes got to the tool literally.
An argument to ssh
, that is not recognized as an option, option-argument or [user@]hostname
, is considered to be a command or a part of it. Following arguments (if any) are also considered parts of the command. If there are two or more parts then ssh
will build a command for a remote shell by concatenating them, injecting single spaces in between.
In your case the two parts ('echo hello,
, world'
) gave this code to run in a remote shell:
'echo hello, world'
Solution for uncomplicated commands: use double-quotes, they will be interpreted by cmd.exe
. This code:
ssh -T [email protected] "echo hello, world"
will pass the following arguments to ssh
: -T
, [email protected]
, echo hello, world
. This is what you intended in the first place. The remote shell will get:
echo hello, world
Note when you said "seeing echo
as the command and the rest as the argument", it's not exactly what happens. The rest is arguments, plural. On the remote side echo
will get two arguments: hello,
and world
. It will print them as hello, world
because injecting a single space is what the tool does in such circumstances. If you need multiple spaces between the two then this is what you should do:
ssh -T [email protected] "echo 'hello, world'"
Here the double-quotes make cmd.exe
not split the string, so ssh
gets it with these spaces. Then on the remote side the single-quotes makes the remote shell not split the (shorter) string, so echo
gets it with these spaces. Omitting any pair of quotes will result in hello, world
. Which mechanism is responsible for replacing multiple spaces with just one depends on which quotes you omit.
More complicated commands often need to be modified (especially if they include double-quotes) to prevent the local interpreter from interfering. The linked answer states:
Here are some important notes about using double quotes
"
withcmd.exe
.
Double quotes are a simple state machine. The first
"
turns quoting on, the next turns it off.All special characters are treated as literal values when quoted, except for the
%
and<newLine>
characters always, and the!
character if delayed expansion is enabled, and the^
character if delayed expansion is enabled and!
also appears somewhere within the line.If quoting is off, then you can escape a
"
as^"
to prevent it from turning quoting on. But once quoting is on, you cannot escape the closing"
. The very next"
will always turn quoting off.
This inconvenience also happens in (local) Bash or any other shell in Linux or wherever. The quoting rules are different but still you need to understand which parts of syntax will be interpreted on the local side, which on the remote side. In Bash you can use single-quotes or here documents, this often helps.
A firm way to prevent the local interpreter from altering code intended for the remote interpreter is to make the code not appear as code on the local side. This is what you were trying to do by reading code from a file. I don't know Windows or cmd.exe
well enough to tell why the approach didn't work; all I know is it works from Linux to Linux, so most likely the culprit is not solely in your remote Linux.
Since you have the code in a file, an alternative approach is to copy the file itself to the remote side. Then you can invoke a command like this in your local cmd.exe
:
ssh [email protected] ./script.sh
This needs the script to be executable on the remote side. Also a proper shebang in the script is advised. You can circumvent these limitations by:
ssh [email protected] "/bin/sh ./script.sh"
(or /bin/bash
or whatever the script is designed for). Note the double-quotes here are not strictly necessary. I just tend to quote whole multi-word commands invoked with ssh
.
Copying a script may be done via scp
, FTP, Samba or some other method. There are quirks though:
-
Shells in Linux/Unix expect scripts to use Unix line endings: LF. In Windows text files use CR+LF. Most protocols copy files as they are, without conversion. FTP being an exception (useful if you know how it works, error-prone if you don't; compare this answer of mine). Possible approaches:
- In Windows use a text editor that allows you to save files with Unix line endings. Copy files to Linux without conversion.
- In Windows use Windows line endings. Copy files to Linux via FTP in text mode.
- Create in whatever way, copy in whatever way. Finally in Linux use
dos2unix
. If by any means the file is already in Unix format thendos2unix
will only replace the file with it exact copy; it will be almost like a no-op. There are few downsides in general but in your case they shouldn't matter. So you can usedos2unix
without a second thought.
Shells in Linux/Unix expect scripts not to have BOM. Modern
dos2unix
should fix this.POSIX requires a trailing newline character in every line including the last one. If the last line of your script (text file in general) is not terminated by LF, some tools may ignore it as if the line didn't exist. Common shells in Linux seem to accept incomplete lines but other tools may not.
Related videos on Youtube
Ders
Updated on September 18, 2022Comments
-
Ders almost 2 years
I want to have a simple way of running commands on a remote Linux machine from local Windows machines. To do this I will use batch scripts that send commands over SSH. I have having trouble with both
giving
ssh
the bash commands directly andalternatively pointing
ssh
at a.sh
shell script to send over.
Solution 1:
ssh -T [email protected] 'echo hello, world'
givesbash: echo hello, world: command not found
The command seems to parse as one token instead of seeing
echo
as the command and the rest as the argument.Solution 2:
:: script.bat @echo off ssh -T [email protected] < script.sh pause
.
# script.sh echo pt1 echo pt2 if [ -n "str" ]; then echo pt3 fi echo pt4
This attempt appears to SSH, complete the first 2 commands, and then hang on the
if
statement indefinitely. It never moves past theif
conditional if it even makes it that far.Summary of Solutions:
Kamil has a very in-depth explanation, including how
echo
works. It is very helpful to read.I was already suspicious of the
CRLF
s. After the feedback, for the remainder of the debugging I used;
-delimited one-liners.@xenoid has inadvertently pointed out and @Kamil has noticed that my quoting in 1 is incorrect. I was using single quotes, but that is strict quoting where everything except for
'
is a literal. Not using quotes or using double quotes provides the expected behavior.I still can't get 2 to work. @Kamil has an excellent solution to just put the shell script on the Linux machine. I will be using this.
-
xenoid over 4 yearsBoth
ssh <host> "echo Hello, world"
andssh <host> echo "Hello, world"
work for me, from Linux to Linux. Your script.sh method also works, if the script uses Unix lineneds, with CRLF it fails.