Send commands from Windows to Linux via Batch Script SSH

8,095

Partial answer. I cannot explain the behavior of the script but I can explain this:

ssh -T [email protected] 'echo hello, world' gives bash: 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 a FOR /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 " with cmd.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:

  1. 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 then dos2unix 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 use dos2unix without a second thought.
  2. Shells in Linux/Unix expect scripts not to have BOM. Modern dos2unix should fix this.

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

Share:
8,095

Related videos on Youtube

Ders
Author by

Ders

Updated on September 18, 2022

Comments

  • Ders
    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

    1. giving ssh the bash commands directly and

    2. alternatively pointing ssh at a .sh shell script to send over.

    Solution 1:

    ssh -T [email protected] 'echo hello, world' gives bash: 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 the if 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 CRLFs. 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
      xenoid over 4 years
      Both ssh <host> "echo Hello, world" and ssh <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.