bash returns different results when commands are passed to bash shell via ssh or HEREDOC [duplicate] - bash

Below is an example of a ssh script using a heredoc (the actual script is more complex). Is it possible to use both local and remote variables within an SSH heredoc or command?
FILE_NAME is set on the local server to be used on the remote server. REMOTE_PID is set when running on the remote server to be used on local server. FILE_NAME is recognised in script. REMOTE_PID is not set.
If EOF is changed to 'EOF', then REMOTE_PID is set and `FILE_NAME is not. I don't understand why this is?
Is there a way in which both REMOTE_PID and FILE_NAME can be recognised?
Version 2 of bash being used. The default remote login is cshell, local script is to be bash.
FILE_NAME=/example/pdi.dat
ssh user#host bash << EOF
# run script with output...
REMOTE_PID=$(cat $FILE_NAME)
echo $REMOTE_PID
EOF
echo $REMOTE_PID

You need to escape the $ sign if you don't want the variable to be expanded:
$ x=abc
$ bash <<EOF
> x=def
> echo $x # This expands x before sending it to bash. Bash will see only "echo abc"
> echo \$x # This lets bash perform the expansion. Bash will see "echo $x"
> EOF
abc
def
So in your case:
ssh user#host bash << EOF
# run script with output...
REMOTE_PID=$(cat $FILE_NAME)
echo \$REMOTE_PID
EOF
Or alternatively you can just use a herestring with single quotes:
$ x=abc
$ bash <<< '
> x=def
> echo $x # This will not expand, because we are inside single quotes
> '
def

remote_user_name=user
instance_ip=127.0.0.1
external=$(ls /home/)
ssh -T -i ${private_key} -l ${remote_user_name} ${instance_ip} << END
internal=\$(ls /home/)
echo "\${internal}"
echo "${external}"
END

Related

Calling local variable in remote host

below script is not working in remote host
PARAMETER="123 456"
ssh user#host PARAMETER="$PARAMETER" bash -s <<- __EOF
echo \$PARAMETER
__EOF
but when I try Below it is working fine
PARAMETER="123"
ssh user#host PARAMETER="$PARAMETER" bash -s <<- __EOF
echo \$PARAMETER
__EOF
Looks like it is not accepting space in the variable. Can anyone help?
As locally, you have to quote variable expansion to disable word splitting. echo "$var" not echo $var.
And you have to quote the value for unquoting by ssh:
ssh user#host PARAMETER="$(printf "%q" "$PARAMETER")" bash -s <<- __EOF
echo "\$PARAMETER"
__EOF
If you are using stdin, I suggest to use to transfer all context with stdin without caring of super-double-quoting:
# define function with the code you want to run, normally quoted
work() {
echo "$PARAMETER"
}
ssh user#host bash -s <<EOF
$(declare -p PARAMETER) # serialize variables
$(declare -f work) # serialize functions
work # run the function
EOF

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 ~ %

for loop over ssh getting unbound variable error

I have below syntax that would compare the modified date of xml files against f1_mdate variable.
typeset -f mdate | ssh 101.101.101.101 "cd "$tar_dir"; $(cat); for xml_file in *.xml; do mdate_xml=$(mdate $xml_file) if [[ "$f1_mdate" == "$mdate_xml" ]]; then echo 1; fi; done"
xml_file: unbound variable
mdate_xml: unbound variable
can somebody please help point out why it is getting unbound variable?
I believe the problem is quoting; this will be clearer if I put line breaks where the shell sees closing quotes:
typeset -f mdate | ssh 10.225.28.45 "cd "\
$tar_dir"; $(cat); for xml_file in *.xml; do mdate_xml=$(mdate $xml_file) if [[ "\
$f1_mdate" == "\
$mdate_xml" ]]; then echo 1; fi; done"
In terms of how to write that correctly... that gets very tricky, because you have two layers of shell which are interpreting commands, stripping quotes and interpolating variables. I usually write a small shell script, scp it to the remote machine and run that remotely, just to get around that... or use a tool like ansible.
As an example of how to get data to and from the remote machine: I created /tmp/a.sh on the remote machine foo. Here's the content of /tmp/a.sh:
while read line
do
echo "xyzzy> $line"
done
On my local machine, I have /tmp/xyz containing
foo
bar
baz
Running
cat /tmp/xyz | ssh foo bash /tmp/a.sh
will give
xyzzy> foo
xyzzy> bar
xyzzy> baz
xyzzy>
You can re-direct the output to a file:
cat /tmp/xyz | ssh foo bash /tmp/a.sh > a.txt
The file a.txt will be on your local machine.

Collecting the output of a remote ssh command in a variable

How to capture the output of a remote command in a bash script.
For example
ssh $USERNAME#$SUT<<EOD
COUNT=$(ls -la | wc -l)
EOD
Planning this for a larger script with multiple such instances, where I need to store and use the remote command output.
It should be:
VAR=$(ssh "$USERNAME"#"$HOST" -- remote_command -option)
You want to execute remote_command remotely and store it in a variable locally. That's what the above command does.
If you want to execute a multiline command remotely you use the following construct with a here doc:
VAR=$(ssh "$USERNAME"#"$HOST" <<EOF
remote_command -option
another_command
...
EOF
)
Btw, unless you want interpolate local variables into the remote command, you probably want to deactivate local shell expansions in the here doc using <<'EOF' as the start delimiter (note the '):
VAR=$(ssh "$USERNAME"#"$HOST" <<'EOF'
remote_command -option
another_command
...
EOF
)
In the above form you can use shell variables, command substitution etc in the remote script. Like this:
VAR=$(ssh "$USERNAME"#"$HOST" <<'EOF'
COUNT=$(remote_command -option)
another_command "${COUNT}"
if $((COUNT+1)) ; then
foo -bar
fi
... and so on. all expansions happen remotely
EOF
)

ssh bash receive variable from a remote file

I need to read the variable from a remote file over SSH and compare it. But I get a variable in the wrong format. how to do it correctly?
#!/bin/bash
pass='dpassspass'
user='root#10.10.19.18'
IP="10.2.1.41"
path=/sys/variable/serv
#not work## No such file or directory# write=$(sshpass -p $ovhpass ssh -t $user echo "$IP" > $path)
sshpass -p $pass ssh -t $user << EOF
echo "$IP" > $path
EOF
my_var=$(sshpass -p $pass ssh -t $user "cd /sys_ovh; ./serv.bash")
echo mystart-"$my_var"-myend
read=$(sshpass -p $pass ssh -t $user cat $path)
echo start-"$read"-end
echo start-"$IP"-end
if [ "$read" == "$IP" ]; then
echo "run"
fi
output:
Connection to 10.10.19.18 closed.
-myendt-10.2.1.41
Connection to 10.10.19.18 closed.
-endt-10.2.1.41
start-10.2.1.41-end
Where I make a mistake? How to take data from the SSH?
The vars my_var and read are filled with a string ending with '\r', telling echo to go back to the first column. I think this is a problem with your local script. You can correct that with
tr -d "\r" < myfile > myfile2
Your fundamental problem comes from using unquoted here documents for the commands. You should properly understand in which order the shell interprets these contructs.
ssh remote cmd >file
executes cmd remotely, but first redirects the output from the ssh command to the local file.
ssh remote "cmd >’$file'"
The quotes cause the redirection to be part of the remote command line. The variable file is interpreted first, by the local shell, though.
ssh remote 'cmd >"$file"`
The single quotes prevent the local shell from modifying the command before sending it. Thus, he variable interpolation and the redirection are both handled by the remote shell, in this order.
So your commented-out "not work" command could easily be fixed with proper quoting. However, it will be much more elegant and efficient to use a single remote session, and execute all the commands in one go. Mixing the local variable IP with remote variables calls for some rather elaborate escaping, though. A major simplification would be to pass the value on standard input, so that the entire remote script can be single quoted.
#!/bin/bash
pass='dpassspass'
user='root#10.10.19.18'
IP="10.2.1.41"
result=$(echo "$IP" |
sshpass -p "$pass" ssh -t "$user" '
path=/sys/variable/serv
cat > "$path"
cd /sys_ovh
./serv.bash
cat "$path"')
echo mystart-"${result%$'\n'*}"-myend
echo start-"${result#*$'\n'}"-end
echo start-"$IP"-end
if [ "${result#*$'\n'}" == "$IP" ]; then
echo "run"
fi
The output from the remote shell is two lines; we pick it apart by using the shell's prefix and suffix substitution operators.

Resources