Unable to pass string parameter (multiline) to remote script using SSH - bash

I’m trying to run a script on a remote machine but I’m facing issue with the parameter that I’m passing to that script. I am passing a variable which is a string containing multiline as input.
The main script is named parent.sh which calls child.sh on remote machine. This child.sh is actually copied to the remote machine and then executed on that machine.
$ cat parent.sh
#!/bin/bash
/usr/bin/scp /home/tom/child.sh harry#remote_server:
printf "Key in parent script: $key\n\n"
/usr/bin/ssh -t harry#remote_server "sudo sh -c '/home/harry/child.sh '$key''"
$ cat child.sh
#!/bin/bash
printf "Key in child: $1"
Input:
key has the following content:
fruit=apple
vegetable=potato
Output:
Key in parent script: fruit=apple
vegetable=potato
Key in child: fruit=apple
Problem is that in the child script's output, i'm only getting the first line but not the second line (vegetable=potato). I tried few combinations of single and double quotes but nothing seems to be working. I would really love to know what blunder am i committing here and how can i rectify it.

This command should work:
/usr/bin/ssh -t harry#remote_server "sudo sh -c '/home/harry/child.sh \"$key\"'"
The key here is that you need the inner command to keep the value of $key quoted when it executes.
Your original command:
"sudo sh -c '/home/harry/child.sh '$key''"
which you probably thought was being read as:
"{sudo} {sh} {-c} {/home/harry/child.sh {$key}}"
is actually read as:
"{sudo} {sh} {-c} {/home/harry/child.sh }$key{}"
which leaves the expansion of $key unquoted and it then gets word-split and the newline becomes just another normal space. You probably have the second "line" available as $2 in the script.

Related

ssh bash with whitespace?

Can anyone explain, please, why this command echoes an empty line instead of "abc"? I'm stuck with this. I know there are multiple ways of reaching the same goal but please also explain why this command does not print "abc" and why it's wrong.
ssh 127.0.0.1 bash -c "echo abc"
You effectively lose a level of quoting when you execute commands via ssh. You would need to write instead:
ssh 127.0.0.1 'bash -c "echo abc"'
Without those outside quotes, the command you're running on the remote system is:
bash -c echo abc
Here you're running the command echo, with $0 set to abc.

Ansible Using shell module, how do I ssh and remain in the same directory? [duplicate]

I already have an ssh agent set up, and I can run commands on an external server in Bash script doing stuff like:
ssh blah_server "ls; pwd;"
Now, what I'd really like to do is run a lot of long commands on an external server. Enclosing all of these in between quotation marks would be quite ugly, and I'd really rather avoid ssh'ing multiple times just to avoid this.
So, is there a way I can do this in one go enclosed in parentheses or something? I'm looking for something along the lines of:
ssh blah_server (
ls some_folder;
./someaction.sh;
pwd;
)
Basically, I'll be happy with any solution as long as it's clean.
Edit
To clarify, I'm talking about this being part of a larger bash script. Other people might need to deal with the script down the line, so I'd like to keep it clean. I don't want to have a bash script with one line that looks like:
ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"
because it is extremely ugly and difficult to read.
How about a Bash Here Document:
ssh otherhost << EOF
ls some_folder;
./someaction.sh 'some params'
pwd
./some_other_action 'other params'
EOF
To avoid the problems mentioned by #Globalz in the comments, you may be able to (depending what you're doing on the remote site) get away with replacing the first line with
ssh otherhost /bin/bash << EOF
Note that you can do variable substitution in the Here document, but you may have to deal with quoting issues. For instance, if you quote the "limit string" (ie. EOF in the above), then you can't do variable substitutions. But without quoting the limit string, variables are substituted. For example, if you have defined $NAME above in your shell script, you could do
ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF
and it would create a file on the destination otherhost with the name of whatever you'd assigned to $NAME. Other rules about shell script quoting also apply, but are too complicated to go into here.
Edit your script locally, then pipe it into ssh, e.g.
cat commands-to-execute-remotely.sh | ssh blah_server
where commands-to-execute-remotely.sh looks like your list above:
ls some_folder
./someaction.sh
pwd;
To match your sample code, you can wrap your commands inside single or double qoutes. For example
ssh blah_server "
ls
pwd
"
I see two ways:
First you make a control socket like this:
ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>
and run your commands
ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>
This way you can write an ssh command without actually reconnecting to the server.
The second would be to dynamically generate the script, scping it and running.
This can also be done as follows.
Put your commands in a script, let's name it commands-inc.sh
#!/bin/bash
ls some_folder
./someaction.sh
pwd
Save the file
Now run it on the remote server.
ssh user#remote 'bash -s' < /path/to/commands-inc.sh
Never failed for me.
Put all the commands on to a script and it can be run like
ssh <remote-user>#<remote-host> "bash -s" <./remote-commands.sh
Not sure if the cleanest for long commands but certainly the easiest:
ssh user#host "cmd1; cmd2; cmd3"
This works well for creating scripts, as you do not have to include other files:
#!/bin/bash
ssh <my_user>#<my_host> "bash -s" << EOF
# here you just type all your commmands, as you can see, i.e.
touch /tmp/test1;
touch /tmp/test2;
touch /tmp/test3;
EOF
# you can use '$(which bash) -s' instead of my "bash -s" as well
# but bash is usually being found in a standard location
# so for easier memorizing it i leave that out
# since i dont fat-finger my $PATH that bad so it cant even find /bin/bash ..
SSH and Run Multiple Commands in Bash.
Separate commands with semicolons within a string, passed to echo, all piped into the ssh command. For example:
echo "df -k;uname -a" | ssh 192.168.79.134
Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda2 18274628 2546476 14799848 15% /
tmpfs 183620 72 183548 1% /dev/shm
/dev/sda1 297485 39074 243051 14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
The posted answers using multiline strings and multiple bash scripts did not work for me.
Long multiline strings are hard to maintain.
Separate bash scripts do not maintain local variables.
Here is a functional way to ssh and run multiple commands while keeping local context.
LOCAL_VARIABLE=test
run_remote() {
echo "$LOCAL_VARIABLE"
ls some_folder;
./someaction.sh 'some params'
./some_other_action 'other params'
}
ssh otherhost "$(set); run_remote"
For anyone stumbling over here like me - I had success with escaping the semicolon and the newline:
First step: the semicolon. This way, we do not break the ssh command:
ssh <host> echo test\;ls
^ backslash!
Listed the remote hosts /home directory (logged in as root), whereas
ssh <host> echo test;ls
^ NO backslash
listed the current working directory.
Next step: breaking up the line:
v another backslash!
ssh <host> echo test\;\
ls
This again listed the remote working directory - improved formatting:
ssh <host>\
echo test\;\
ls
If really nicer than here document or quotes around broken lines - well, not me to decide...
(Using bash, Ubuntu 14.04 LTS.)
The easiest way to configure your system to use single ssh sessions by default with multiplexing.
This can be done by creating a folder for the sockets:
mkdir ~/.ssh/controlmasters
And then adding the following to your .ssh configuration:
Host *
ControlMaster auto
ControlPath ~/.ssh/controlmasters/%r#%h:%p.socket
ControlMaster auto
ControlPersist 10m
Now, you do not need to modify any of your code. This allows multiple calls to ssh and scp without creating multiple sessions, which is useful when there needs to be more interaction between your local and remote machines.
Thanks to #terminus's answer, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ and https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing.
What is the cleanest way to ssh and run multiple commands in Bash?
I recommend using this escaping function. The function takes one argument - a function to escape. Then sshqfunc outputs declare -f of the function and then outputs a string that will call the function with "$#" arguments properly quoted. Then the whole is "%q" quoted and bash -c is added. In case the remote does not have bash, you could change bash to sh.
sshqfunc() { echo "bash -c $(printf "%q" "$(declare -f "$#"); $1 \"\$#\"")"; };
Then define a function with the work you want to do on the remote. The function is defined normally, so it will be properly "clean". You can test such function locally. After defining, properly escaped function is passed to the remote.
work() {
ls
pwd
echo "Some other command"
}
ssh host#something "$(sshqfunc work)"
Passing You can also pass arguments, and they will be passed to your function as positional arguments. The right next argument after the function will be assigned to $0 - usually a placeholder like -- or _ is used to separate arguments from call.
work() {
file=$1
num=$2
ls "$file"
echo "num is $num"
}
ssh host#something "$(sshqfunc work)" -- /this/file 5
But note that arguments should also be properly quoted if there are any magic characters:
ssh host#something "$(sshqfunc work)" -- "$(printf "%q" "$var1" "$var2")"
For simple commands you can use:
ssh <ssh_args> command1 '&&' command2
or
ssh <ssh_args> command1 \&\& command2

Pass along variables via ssh

I'm trying to pass along variables via ssh, and wrote two short testscripts (bash)
This one is to the execute the script on the other side (and it works, at least partially)
I start it with executing: 'mms test alpha one'
#!/bin/bash
sshpass -p (password) ssh hellfire#192.168.0.11 'bash /scripts/mms2 "$#"'
The second script that are executed is:
#!/bin/bash
echo "$#" >/scripts/test1.txt
This script is only for testing if the parameters are are transfered.
So far it creates the text file, but it's empty, so I have no idea if there's wrong with both or only one of the script.
Basically I want to pass a set of variables to the script on the server, these variables can contain spaces.
Anybody have any tips?
I found out by #Gordon Davidsson comment that the $(printf "%q " "$#") could be used to send it as a string, so the remote server didn't interpret the variables as different commands.
My new and working script is:
#!/bin/bash
sshpass -p (password) ssh hellfire#192.168.0.11 "bash /scripts/mms2 $(printf "%q " "$#")"

Pass all args to a command called in a new shell using bash -c

I've simplified my example to the following:
file1.sh:
#!/bin/bash
bash -c "./file2.sh $#"
file2.sh:
#!/bin/bash
echo "first $1"
echo "second $2"
I expect that if I call ./file1.sh a b to get:
first a
second b
but instead I get:
first a
second
In other words, my later arguments after the first one are not getting passed through to the command that I'm executing inside a new bash shell. I've tried many variations of removing and moving around the quotation marks in the file1.sh file, but haven't got this to work.
Why is this happening, and how do I get the behavior I want?
(UPDATE - I realize it seems pointless that I'm calling bash -c in this example, my actual file1.sh is a proxy script for a command that gets called locally to run in a docker container so it's actually docker exec -i mycontainer bash -c '')
Change file1.sh to this with different quoting:
#!/bin/bash
bash -c './file2.sh "$#"' - "$#"
- "$#" is passing hyphen to populate $0 and $# is being passed in to populate all other positional parameters in bash -c command line.
You can also make it:
bash -c './file2.sh "$#"' "$0" "$#"
However there is no real need to use bash -c here and you can just use:
./file2.sh "$#"

Write SSH command over multiple lines

I'm trying to remote login to a shell and execute a bunch of commands on the shell.
But to make it more readable, I'd like to place my code over multiple lines. How should I be doing this?
ssh -o <Option> -x -l <user> <host> " $long_command1; $long_command2; .... "
Thanks!
ssh is in fact just passing a string to the remote host. There this string is given to a shell which is supposed to interpret it (the user's login shell, which is typically something like bash). So whatever you want to execute needs to be interpretable by that remote login shell, that's the whole rule you have to stick to.
You can indeed just use newlines within the command string:
ssh alfe#sweethome "
ls /home/alfe/whatever
ping foreignhost
date
rm foobar
"
You can use the Here Documents feature of bash.
It is like:
ssh <remote-host> bash <<EOF
echo first command
echo second command
EOF
EOF marks the end of the input.
For further info: use man bash and search for Here Documents.
Edit: The only caveat is that using variables can be tricky, you have to escape the $ to protect them to be evaluated on the remote host rather then the local shell. Like \$HOSTNAME. Otherwise works with everything that is run from bash and uses stdin.
You can do like in the following example.
ssh -o <Option> -x -l <user> <host> '
pwd
whoami
ls
echo "$PATH"
'

Resources