Run 'export' command Over SSH - bash

When I run the following from my bash shell:
bash -c '(export abc=123 && echo $abc)'
The output is "123". But when I run it over ssh:
ssh remote-host "bash -c '(export abc=123 && echo $abc)'"
There is no output. Why is this? Is there a way around this? That is, is there a way to set an environment variable for a command I run over ssh?
Note: When I replace echo $abc with something standard like echo $USER the ssh command prints out the username on the remote machine as expected since it is already set.
I am running RHEL 5 Linux with OpenSSH 4.3

That is because when using
ssh remote-host "bash -c '(export abc=123 && echo $abc)'"
the variable gets expanded by the local shell (as it is the case with $USER) before ssh executes. Escape the $ by using \$ and it should do fine
ssh remote-host "bash -c '(export abc=123 && echo \$abc)'"
On a side note:
You don't need to export just for this.
You don't need to wrap it in ()
Like so:
ssh remote-host "bash -c 'abc=123 && echo \$abc'"
Heck, you can even leave out the bash -c ... stuff, as the ssh manpage states:
If command is specified, it is executed on the remote host instead of a login shell.
But these may be specific to your task ;)

Related

how can i expect ssh and run shell command?

i want to mkdir at remote machine, but i dont know if the dir exists, how can i do this?
i use spawn ssh username#ip bash -c [ -d $dest_file ] && echo ok || mkdir -p $dest_file
and returns
while executing
"-d $dest_file "
invoked from within
"[ -d $dest_file ] && echo ok || mkdir -p $dest_file"
(file "mkdir.exp" line 22)
i cant use ssh-key because my ip is dynamic
First, you probably don't need bash -c because ssh is already executing the command with your remote shell.
Secondly, you're not sufficiently quoting your ssh arguments. You're writing an expect script, which uses the tcl programming language, and [ is a special character that will attempt to evaluate its contents as a tcl command and return the output (read more here). For this to work properly, you would need to escape the opening [ to get tcl to interpret it literally:
spawn ssh localhost \[ -d $dest_file ] && echo ok || mkdir -p $dest_file
This seems to work correctly on my system, but as I indicate in a comment it would be much easier to drop all the conditionals and just run:
spawn ssh localhost mkdir -p $dest_file
This accomplishes the same thing and doesn't run afoul of any quoting issues.

First echo missing when using bash -c over SSH

While debugging a script that runs various commands remotely, I noticed some problems getting output from echo.
I realize that the bash -c isn't necessary here, but it still has me wondering.
In my shell:
> bash -c "echo hello && echo hi"
hello
hi
But, if I bring SSH into the picture:
> ssh ${myhost} bash -c "echo hello && echo hi"
hi
Yet, date outputs, even though that first echo didn't:
> ssh ${myhost} bash -c "date && echo hi"
Thu Jun 3 21:15:26 UTC 2021
hi
What's going on here?
When you run a command via ssh like this, it's parsed twice: first on the local computer (before it's passed to the ssh command as arguments), then again on the remote computer before it's actually executed. Each time it's parsed, the shell will apply and remove quotes and escapes. That means the double-quotes you have around the command get applied and removed by the local shell, before the command is sent to the remote shell. So what looks like this command:
bash -c "echo hello && echo hi"
Turns into this by the time the remote shell sees it:
bash -c echo hello && echo hi
...which is two separate commands, bash -c echo hello and echo hi. The second one, echo hi, works as you expect, but the first may not.
With bash -c, the argument immediately after that is taken as the command string to execute, and any further arguments are assigned to $0, $1, etc as it runs. So bash -c echo hello just runs echo with $0 set to "hello". So it prints a blank line.
If you want the command to be executed as you expect, you need two layers of quotes and/or escapes, one to be applied and removed by the local shell and another to be applied and removed by the remote shell. Any of these will work:
# Single-quotes for local shell, double for remote
ssh ${myhost} 'bash -c "echo hello && echo hi"'
# Double-quotes for local shell, single for remote
ssh ${myhost} "bash -c 'echo hello && echo hi'"
# Double-quotes for local shell, escaped doubles for remote
ssh ${myhost} "bash -c \"echo hello && echo hi\""
# Single-quotes for local shell, escaped characters for remote
ssh ${myhost} 'bash -c echo\ hello\ \&\&\ echo\ hi'
...and many more possibilities. Note that if the command string contains anything like variable references or command substitutions, you need to pay attention to whether you want them to expand on the local or remote computer, and make sure the quoting/escaping method you use accomplishes that.
BTW, in this case since you're running a command with bash -c, there's actually a third layer of parsing done by the shell invoked by bash -c. If that command has anything that needed quoting/escaping, keeping the levels straight will be even more complex.
The command that arrives to the server is : bash -c echo hello && echo hi
ie without quote
and if you run this cde locally, it produces the same output
If you want the good result
ssh mm 'bash -c "echo hello && echo hi"'
Could also try using ";" as a separator
ssh ${myserver} "echo hello; echo hi"
testuser#mymac ~ % ssh ${myserver} "echo hello; echo hi"
hello
hi
testuser#mymac ~ %

Bash Script: How to run command with $myVar as one of the arguments?

I have a bash script that SSHes into 2 machines and runs identical commands.
I'd like to store this in a var, but I'm not sure how to reference the contents of the var when running the command
ssh -o StrictHostKeyChecking=no ubuntu#123.123.123 -i ./travis/id_rsa <<-END
sudo su;
...
echo "Done!";
END
ssh -o StrictHostKeyChecking=no ubuntu#456.456.456 -i ./travis/id_rsa <<-END
sudo su;
...
echo "Done!";
END
I tried something like this but it didn't work:
script=$(cat <<-END
sudo su;
...
echo "Done!";
END
)
ssh -o StrictHostKeyChecking=no ubuntu#123.123.123 -i ./travis/id_rsa $script
ssh -o StrictHostKeyChecking=no ubuntu#456.456.456 -i ./travis/id_rsa $script
If I am at all able to understand what you are asking, you really don't want to put the commands in a variable.
for host in 123.123.123 456.456.456; do
ssh -o StrictHostKeyChecking=no ubuntu#"$host" -i ./travis/id_rsa<<-\____here
sudo -s <<-_________there
: your script goes here
________there
echo "Done."
____here
done
If you really wanted to assign a multi-line variable (but trust me, you don't) the syntax for that is simply
script='sudo -s <<\____there
: your commands
____there
echo "Done."'
But there really is no need to do this, andeit actually complicates things down the line. You see, passing in properly quoted strings as arguments to ssh is extremely tricky - you have the local shell and the remote shell and both require additional quoting or escaping in order to correctly pass through shell metacharacters; and the usual caveats with eval apply, only you are effectively running a hidden eval by way of passing in executable code as a string for the remote shell.
I believe you want to do something like this:
cmds="sudo bash -c 'command1; command2; command3;'"
ssh ... "$cmds"

is it possible to use variables in remote ssh command?

I'd like to execute several commands in sequence on a remote machine, and some of the later commands depend on earlier ones. In the simplest possible example I get this:
ssh my_server "echo this is my_server; abc=2;"
this is my_server
abc=2: Command not found.
ssh my_server "echo this is my_server; abc=2; echo abc is $abc"
abc: undefined variable
For a bit of background info, what I actually want to do is piece together a path and launch a java application:
ssh my_server 'nohup sh -c "( ( echo this is my_server; jabref_exe=`which jabref`; jabref_dir=`dirname $jabref_exe`; java -jar $jabref_dir/../jabref.jar` $1 &/dev/null ) & )"' &
jabref_dir: Undefined variable.
That way, whenever jabref gets updated to a new version on the server, I won't have to manually update the path to the jar file. The jabref executable doesn't take arguments, but launching it with java -jar does, which is why I have to juggle the path a bit.
At the moment I have the list of commands in a separate script file and call
ssh my_server 'nohup sh -c "( ( my_script.sh &/dev/null ) & )"' &
which works, but since the ssh call is already inside one script file it would be nice to have everything together.
In this example
ssh my_server "echo this is my_server; abc=2;"
abc is set on the remote side, so it should be clear why it is not set on your local machine.
In the next example,
ssh my_server "echo this is my_server; abc=2; echo abc is $abc"
your local shell tries to expand $abc in the argument before it is ever sent to the remote host. A slight modification would work as you expected:
ssh my_server 'echo this is my_server; abc=2; echo abc is $abc'
The single quotes prevent your local shell from trying to expand $abc, and so the literal text makes it to the remote host.
To finally address your real question, try this:
jabref_dir=$( ssh my_server 'jabref_exe=$(which jabref); jabref_dir=$(dirname $jabref_exe);
java -jar $jabref_dir/../jabref.jar > /dev/null; echo $jabref_dir' )
This will run the quoted string as a command on your remote server, and output exactly one string: $jabref_dir. That string is captured and stored in a variable on your local host.
With some inspiration from chepner, I now have a solution that works, but only when called from a bash shell or bash script. It doesn't work from tcsh.
ssh my_server "bash -c 'echo this is \$HOSTNAME; abc=2; echo abc is \$abc;'"
Based on this, the code below is a local script which runs jabref on a remote server (although with X-forwarding by default and passwordless authentication the user can't tell it's remote):
#!/bin/bash
if [ -f "$1" ]
then
fname_start=$(echo ${1:0:4})
if [ "$fname_start" = "/tmp" ]
then
scp $1 my_server:$1
ssh my_server "bash -c 'source load_module jdk; source load_module jabref; java_exe=\$(which java); jabref_exe=\$(which jabref); jabref_dir=\$(echo \${jabref_exe%/bin/jabref});eval \$(java -jar \$jabref_dir/jabref.jar $1)'" &
else
echo input argument must be a file in /tmp.
else
echo this function requires 1 argument
fi
and this is the 1-line script load_module, since modulecmd sets environment variables and I couldn't figure out how to do that without sourcing a script.
eval `/path/to/modulecmd bash load $1`;
I also looked at heredocs, inspired by How to use SSH to run a shell script on a remote machine? and http://tldp.org/LDP/abs/html/here-docs.html. The nice part is that it works even from tcsh. I got this working from the command line, but not inside a script. That's probably easy enough to fix, but I've got a solution now so I'm happy :-)
ssh my_server 'bash -s' << EOF
echo this is \$HOSTNAME; abc=2; echo abc is \$abc;
EOF

using ssh with stat command bash [duplicate]

I'd like to execute several commands in sequence on a remote machine, and some of the later commands depend on earlier ones. In the simplest possible example I get this:
ssh my_server "echo this is my_server; abc=2;"
this is my_server
abc=2: Command not found.
ssh my_server "echo this is my_server; abc=2; echo abc is $abc"
abc: undefined variable
For a bit of background info, what I actually want to do is piece together a path and launch a java application:
ssh my_server 'nohup sh -c "( ( echo this is my_server; jabref_exe=`which jabref`; jabref_dir=`dirname $jabref_exe`; java -jar $jabref_dir/../jabref.jar` $1 &/dev/null ) & )"' &
jabref_dir: Undefined variable.
That way, whenever jabref gets updated to a new version on the server, I won't have to manually update the path to the jar file. The jabref executable doesn't take arguments, but launching it with java -jar does, which is why I have to juggle the path a bit.
At the moment I have the list of commands in a separate script file and call
ssh my_server 'nohup sh -c "( ( my_script.sh &/dev/null ) & )"' &
which works, but since the ssh call is already inside one script file it would be nice to have everything together.
In this example
ssh my_server "echo this is my_server; abc=2;"
abc is set on the remote side, so it should be clear why it is not set on your local machine.
In the next example,
ssh my_server "echo this is my_server; abc=2; echo abc is $abc"
your local shell tries to expand $abc in the argument before it is ever sent to the remote host. A slight modification would work as you expected:
ssh my_server 'echo this is my_server; abc=2; echo abc is $abc'
The single quotes prevent your local shell from trying to expand $abc, and so the literal text makes it to the remote host.
To finally address your real question, try this:
jabref_dir=$( ssh my_server 'jabref_exe=$(which jabref); jabref_dir=$(dirname $jabref_exe);
java -jar $jabref_dir/../jabref.jar > /dev/null; echo $jabref_dir' )
This will run the quoted string as a command on your remote server, and output exactly one string: $jabref_dir. That string is captured and stored in a variable on your local host.
With some inspiration from chepner, I now have a solution that works, but only when called from a bash shell or bash script. It doesn't work from tcsh.
ssh my_server "bash -c 'echo this is \$HOSTNAME; abc=2; echo abc is \$abc;'"
Based on this, the code below is a local script which runs jabref on a remote server (although with X-forwarding by default and passwordless authentication the user can't tell it's remote):
#!/bin/bash
if [ -f "$1" ]
then
fname_start=$(echo ${1:0:4})
if [ "$fname_start" = "/tmp" ]
then
scp $1 my_server:$1
ssh my_server "bash -c 'source load_module jdk; source load_module jabref; java_exe=\$(which java); jabref_exe=\$(which jabref); jabref_dir=\$(echo \${jabref_exe%/bin/jabref});eval \$(java -jar \$jabref_dir/jabref.jar $1)'" &
else
echo input argument must be a file in /tmp.
else
echo this function requires 1 argument
fi
and this is the 1-line script load_module, since modulecmd sets environment variables and I couldn't figure out how to do that without sourcing a script.
eval `/path/to/modulecmd bash load $1`;
I also looked at heredocs, inspired by How to use SSH to run a shell script on a remote machine? and http://tldp.org/LDP/abs/html/here-docs.html. The nice part is that it works even from tcsh. I got this working from the command line, but not inside a script. That's probably easy enough to fix, but I've got a solution now so I'm happy :-)
ssh my_server 'bash -s' << EOF
echo this is \$HOSTNAME; abc=2; echo abc is \$abc;
EOF

Resources