running a pipe command with variable substitution on remote host - bash

I'd to run a piped command with variable substitution on a remote host and redirect the output. Given that the login shell is csh, I have to used "bash -c". With help from users nlrc and jerdiggity, a command with no variable substitution can be formulated as:
localhost$ ssh -f -q remotehost 'bash -c "ls /var/tmp/ora_flist.sh|xargs -L1 cat >/var/tmp/1"'
but the single quote above will preclue using variable substitution, say, substituting ora_flist.sh for $filename. how can I accomplish that?
Thanks.

Something like this should work:
ssh -f -q remotehost 'bash -c "ls /var/tmp/ora_flist.sh|xargs -L1 cat >/var/tmp/1"'

So your problem was that you want the shell variable to be extended locally. Just leave it outside the single quotes, e.g.
ssh -f -q remotehost 'bash -c "ls '$filename' | xargs ..."'
Also very useful trick to avoid the quoting hell is to use heredoc, e.g.
ssh -f -q remotehost <<EOF
bash -c "ls $filename | xargs ... "
EOF

Related

Bash script placing quotes around command

I have a Bash script that will get an IP to use as part of an SSH tunnel, but running this script the SSH tunnel fails. When using set -x I can see it places the arguments to the SSH command in single quotes and manually running this line results in the same error.
The Script:
ssh -N -L 9000:${ip_array[$2]}:443 ssh-server
The first argument is used elsewhere in the script for something else which is why the second is used here. ssh-server is an alias in my SSH config to the server i am tunneling through.
The output I get is:
ssh -N -L '9000:"172.0.0.1":443' ssh-server
Could this be because the script to fetch the IP returns strings to the array?
you can try removing the double-quotes first :
ip=$(echo "${ip_array[$2]}" | sed "s/\"//g")
ssh -N -L 9000:${ip}:443 ssh-server
Or just use shell parameter expansion to remove the quotes:
ssh -N -L 9000:${ip_array[$2]//"/}:443 ssh-server
That lone double quote may mess up your editor's syntax highlighting.
Get rid of the quotes by piping it through the tr command:
ssh -N -L 9000:$( echo ${ip_array[$2]} | tr -d '"' ):443 ssh-server

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"

Passing Bash Command Through SSH - Executing Variable Capture

I am passing the following command straight through SSH:
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /key/path server#111.111.111.111 'bash -s' << EOF
FPM_EXISTS=`ps aux | grep php-fpm`
if [ ! -z "$FPM_EXISTS" ]
then
echo "" | sudo -S service php5-fpm reload
fi
EOF
I get the following error:
[2015-02-25 22:45:23] local.INFO: bash: line 1: syntax error near unexpected token `('
bash: line 1: ` FPM_EXISTS=root 2378 0.0 0.9 342792 18692 ? Ss 17:41 0:04 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
It's like it is trying to execute the output of ps aux | grep php-fpm instead of just capturing git the variable. So, if I change the command to try to capture ls, it acts like it tries to execute that as well, of course returning "command not found" for each directory.
If I just paste the contents of the Bash script into a file and run it it works fine; however, I can't seem to figure out how to pass it through SSH.
Any ideas?
You need to wrap starting EOF in single quotes. Otherwise ps aux | grep php-fpm would get interpreted by the local shell.
The command should look like this:
ssh ... server#111.111.111.111 'bash -s' << 'EOF'
FPM_EXISTS=$(ps aux | grep php-fpm)
if [ ! -z "$FPM_EXISTS" ]
then
echo "" | sudo -S service php5-fpm reload
fi
EOF
Check this: http://tldp.org/LDP/abs/html/here-docs.html (Section 19.7)
Btw, I would encourage you to use $() instead of backticks consequently for command substitution because of the ability to nest them. You will have more fun, believe me. Check this for example: What is the benefit of using $() instead of backticks in shell scripts?
You should wrap the EOF in single quotes.
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /key/path server#111.111.111.111 'bash -s' << 'EOF'
FPM_EXISTS=`ps aux | grep php-fpm`
if [ ! -z "$FPM_EXISTS" ]
then
echo "" | sudo -S service php5-fpm reload
fi
EOF

script doesn't see arg in '$ ssh bash script arg'

I'd like to see both commands print hello
$ bash -l -c "/bin/echo hello"
hello
$ ssh example_host bash -l -c /bin/echo hello
$
How can hello be passed as a parameter in the ssh command?
The bash -l -c is needed, so login shell startup scripts are executed.
Getting ssh to start a login shell would solve the problem too.
When you pass extra args after -c, they're put into the argv of the shell while that command is executing. You can see that like so:
bash -l -c '/bin/echo "$0" "$#"' hello world
...so, those arguments aren't put on the command line of echo (unless you go out of your way to make it so), but instead are put on the command line of the shell which you're telling to run echo with no arguments.
That is to say: When you run
bash -l -c /bin/echo hello
...that's the equivalent of this:
(exec -a hello bash -c /bin/echo)
...which puts hello into $0 of a bash which runs only /bin/echo. Since running /bin/echo doesn't look at $0, of course it's not going to print hello.
Now, because executing things via ssh means you're going through two steps of shell expansion, it adds some extra complexity. Fortunately, you can have the shell handle that for you automatically, like so:
printf -v cmd_str '%q ' bash -l -c '/bin/echo "$0" "$#"' hello world
ssh remote_host "$cmd_str"
This tells bash (printf %q is a bash extension, not available in POSIX printf) to quote your command such that it expands to itself when processed by a shell, then feeds the result into ssh.
All that said -- treating $0 as a regular parameter is bad practice, and generally shouldn't be done absent a specific and compelling reason. The Right Thing is more like the following:
printf -v cmd '%q ' /bin/echo hello world # your command
printf -v cmd '%q ' bash -l -c "$cmd" # your command, in a login shell
ssh remotehost "$cmd" # your command, in a login shell, in ssh

use expect to automate the piped command

I want to automate a piped command cat ~/.ssh/id_rsa.pub | ssh root#host 'cat >> .ssh/authorized_keys' in expect. When using spawn command to execute the command,
spawn cat ~/.ssh/id_rsa.pub | ssh root#host 'cat >> .ssh/authorized_keys'
it throws error msg,
cat |: No such file or directory
cat ssh: No such file or directory
...
How should I spawn the piped commands?
Because you want to technically execute multiple shell commands in one hit (and spawn doesn't handle the piped I/O), you need to encapsulate them in a script then use spawn to execute the script.
The script you want already exists as ssh-copy-id, however if you want a trimmed down version you can create a script file on the fly then pass that into expect spawn:
cat > /tmp/sshkeycopy.sh <<MYEOF
cat ~/.ssh/id_rsa.pub | ssh user#host 'mkdir -p -m 600 ~/.ssh; cat >> ~/.ssh/authorized_keys'
MYEOF
chmod u+x /tmp/sshkeycopy.sh
expect -c "
spawn /tmp/sshkeycopy.sh
expect { ... }"
With this bash example, the shell will handle the pipe as expected and just throw the password prompt out for expect to handle.
Does spawn handle input redirection?
spawn ssh root#host "cat >> .ssh/authorized_keys" < ~/.ssh/id_rsa.pub
Input redirection would be preferred over using cat with a single file if you weren't using expect.
Edit: use double quotes around cat command, instead of single quote (which does not work, as tested)
You can use this pattern:
spawn bash -c "cat MEH | ssh root#host 'cat >>HMM' "
This has been tested and works on Linux
#!/usr/bin/expect -f
set DOMAIN www.somedomain.tld
set TARFILE "[exec bash -c "echo $DOMAIN | cut -d'.' -f 1"].cert.tar"
puts "$TARFILE\r"

Resources