16

I have a script which connects to a remote server and check if some package is installed:

ssh root@server 'bash -s' < myscript.sh

myscript.sh:

OUT=`rpm -qa | grep ntpdate`
if [ "$OUT" != "" ] ; then
    echo "ntpdate already installed"
else
    yum install $1
fi

This example could be simplified. Here is myscript2.sh which has same problem:

read -p "Package is not installed. Do you want to install it (y/n)?" choise

My problem is that bash can not read my answers interactively.

Is there a way to execute local script remotely without losing ability to prompt user?

  • Can you elaborate? It's not really clear what you're asking for? Some more of your code would be helpful. – slm Nov 10 '13 at 13:55
  • 1
    Just an FYI, the reason this does not work is because you are passing your script over STDIN. Thus when bash goes to read from STDIN, it gets your script (or nothing since it's already read the whole script and there's nothing left). – Patrick Nov 10 '13 at 18:10
  • 1
    Useful resource related to this: backreference.org/2011/08/10/… – slm Nov 23 '13 at 3:08
22

Try something like this:

$ ssh -t yourserver "$(<your_script)"

The -t forces a tty allocation, $(<your_script) reads the whole file and in this cases passes the content as one argument to ssh, which will be executed by the remote user's shell.

If the script needs parameters, pass them after the script:

$ ssh -t yourserver "$(<your_script)" arg1 arg2 ...

Works for me, not sure if it's universal though.

  • 3
    That's exactly what i was looking for, thank you! – Anthony Ananich Nov 10 '13 at 17:25
  • Nice solution, but is there a way to pass an argument to your script, while retaining the advantages of the syntax you used in your answer? Using ssh -t yourserver "$(<your_script --some_arg)" just results in bash: --some_arg: command not found. – sampablokuper Jan 2 '18 at 18:50
  • 1
    @sampablokuper: try ssh ... $(<script) script_arg1 script_arg2 ... – Mat Jan 2 '18 at 20:13
  • @Mat, thanks, WFM :) Maybe add it to the body of your answer? – sampablokuper Jan 3 '18 at 16:55
3

Your problem is that ssh starts a login, non-interactive shell on the remote machine. The obvious easy solution would be to copy the script to the remote server and run it from there:

scp myscript.sh root@server:/tmp && ssh root@server /tmp/myscript.sh

If copying is not an option for whatever reason, I would modify the script to first connect and check if $1 is installed, then reconnect and install as necessary:

OUT=$(ssh root@server rpm -qa | grep "$1");
if [ "$OUT" != "" ] ; then
    echo "$1 already installed"
else
   read -p "Package $1 is not installed. Do you want to install it (y/n)?" choice
   if [ "$choice" -eq "y" ]; then
       ssh root@server yum install "$1"
   fi
fi
  • That's what I was afraid of. Connection to ssh is the longest operation and I was trying to avoid it. – Anthony Ananich Nov 10 '13 at 15:49
  • @AnthonyAnanich You can save the connection establishment time by setting up a master connection. – Gilles Nov 10 '13 at 20:02
  • @Gilles I was thinking of suggesting that but I thought it would not work with my suggestion. If the 1st ssh connections acts as the master, it will be closed after the $OUT=$(ssh root@server rpm -qa | grep "$1"); exits and then the 2nd connection will take as much time as the first. Am I wrong? – terdon Nov 10 '13 at 20:18
  • 1
    @terdon Run something like ssh -M foo.example.com sleep 99999999 or ssh -M foo.example.com read <somefifo as the master and kill it explicitly (with kill or echo done >somefifo) when you're done. – Gilles Nov 10 '13 at 20:30
0

Here is a good explanation.

So I adapted the script to

hostname
echo -n "Make your choice :"
read choice
echo "You typed " ${choice}
echo done

and this didn't work.

so I moved the script to the remote to avoid the local redirection on ssh. (My commands are in a file named f)

cat f | ssh user@remotehost.com 'cat >remf'
ssh user@remotehost bash remf

This worked. Here's the output :

christian@clafujiu:~/tmp$ ssh localhost bash tmp/f
christian@localhost's password: 
Linux clafujiu 2.6.32-52-generic #114-Ubuntu SMP Wed Sep 11 19:00:15 UTC 2013 i686 GNU/Linux
Sun Nov 10 14:58:56 GMT 2013
Make your choice :abc
You typed  abc
done

As @terdon mentioned that the original intension was to run local scripts remotely, the remote copy can be automated, this is just one example all on one line.

REMID=`cat f |ssh user@remotehost 'cat > remf_$$; echo $$'` ;ssh root@redtoadservices.com "bash remf_${REMID} ; rm -v remf_${REMID}"
  • 2
    How did it "work"? Did you run this script with bash -s < script.sh like the OP did? Could you include the explanation in your answer instead of linking to it? – terdon Nov 10 '13 at 14:33
  • Oops, so sorry I forgot to add something critical. I also copied the script to the remote to avoid the local redirect. I'll append this to my answer to make it clear. – X Tian Nov 10 '13 at 14:53
  • 1
    If you copy the script to the remote the problem goes away. The OP's issue is that he is trying to give interactive input to a local script running remotely. Of course it will run of you copy it over. The OP want to run this script when connecting to a remote server, presumably to many remote servers. I doubt copying it over is practical unless you automate it. – terdon Nov 10 '13 at 14:57
0

I've searched for solutions to this problem several times in the past, however never finding a fully satisfactory one. Piping into ssh looses your interactivity. Two connects (scp/ssh) is slower, and your temporary file might be left lying around. And the whole script on the command line often ends up in escaping hell.

Recently I encountered that the command line buffer size is usually quite large ('getconf ARG_MAX > 2MB where I looked). And this got me thinking about how I could use this and mitigate the escaping issue.

The result is:

ssh -t <host> /bin/bash "<(echo "$(cat my_script | base64 | tr -d '\n')" | base64 --decode)" <arg1> ...

or using a here document and cat:

ssh -t <host> /bin/bash $'<(cat<<_ | base64 --decode\n'$(cat my_script | base64)$'\n_\n)' <arg1> ...

I've expanded on this idea to produce a fully working BASH example script sshx that can run arbitrary scripts (not just BASH), where arguments can be local input files too, over ssh. See here.

  • Interesting, but how and/or when is this any better than Mat’s answer? – Scott May 15 at 1:05
  • 1
    Mat's answer is a good first pass (which I often use), however it can not handle a script of arbitrary content, such as " characters which will need to be escaped. And also it is restricted to BASH scripts. My solution is a general case for any script content of any installed scripting language. Further, in sshx I further demonstrate serialising argument files, which is pretty nifty. – SourceSimian May 20 at 20:13
  • (1) Can you post an MCVE of a script for which Mat’s answer fails (and yours works)? (2) See What is wrong with “echo $(stuff)” or “echo `stuff`”? Your echo "$(cat my_script | base64)" | base64 --decode looks equivalent(-ish) to cat my_script | base64 | base64 --decode, which looks a lot like a no-op. – Scott May 20 at 22:17
  • Hi @Scott. (1): Consider the script: echo "ARG1=$1, USER=$USER, END". a) $ ssh host "$(<my_bash)" foo -> ARG1=, USER=user, END foo. b) $ ssh host bash "<(echo $(cat my_bash|base64) | base64 --decode)" foo -> ARG1=foo, USER=user, END. Here (a) is effectively running: bash -c $'#!/bin/bash\necho "ARG1=$1, USER=$USER, END" foo' or something similar. Note the arg doesn't work. Where (b) is running: bash <(echo IyEvY...5EIgo= | base64 --decode) foo. (2) I used base64 as the transport encoding. – SourceSimian May 21 at 20:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.