2

I have a series of bash commands, some with interactive prompts, that I need run on a remote machine. I have to have them called in a certain order for different scenarios, so I've been trying to make a bash script to automate the process for me. However, it seems like every way to start an ssh session with a bash script results in the the redirection of stdin to whatever string or file was used to initiate the script in the first place.

Is there a way I can specify that a certain script be executed on a remote machine, but also forward stdin through ssh to the local machine to enable the user to interact with any prompts?

Here's a list of requirements I have to clarify what I'm trying to do.

  1. Run a script on a remote machine.
  2. Somewhere in the middle of that remote script be command that will prompt for input. Example: git commit will bring up vim.
    • If that command is git commit and it brings up vim, the user should be able to interact with vim as if it was running locally on their machine.
    • If that command prompts for a [y/n] response, the user should be able to input their answer.
  3. After the user enters the necessary information—by quitting vim or pressing return on a prompt—the script should continue to run like normal.
  4. My script will then terminate the ssh session. The end product is that commands were executed for the user without them needing to be aware that it was through a remote connection.

I've been testing various different methods with the following script that I want run on the remote machine.

#!/bin/bash
echo hello
vim
echo goodbye
exit

It's crucial that the user be able to use vim, and then, when the user finishes, "goodbye" should be printed to the screen and the remote session should be terminated.

I've tried uploading a temporary script to the remote machine and then running ssh user@host bash /tmp/myScript, but that seems to also take over stdin completely, rendering it impossible to let the user respond to prompts for user input. I've tried adding the -t and -T options (I'm not sure if they're different), but I still get the same result.

One commenter mentioned using expect, spawn, and interact, but I'm not sure how to use those tools together to get my desired behavior. It seems like interact will result in the user gaining control over stdin, but then there's no way to have it relinquished once the user quits vim in order to let my script continue execution.

Is my desired behavior even possible?

sbrun
  • 197
  • 2
  • 13
  • @anishsane yeah, I was using that flag to send commands throug `ssh` with a heredoc, but that totally took over standard-in, and didn't allow me to interact with any prompts that appeared later in execution. `vim` was especially unhappy about it—not taking over the entire screen and rejecting all input. I could type with my keyboard when it appeared, but that just resulted in random escape codes appearing at the top of the screen that didn't effect the vim instance at all. – sbrun May 05 '16 at 07:09
  • 1
    If you are redirecting `stdin`, how do you expect it to be interactive? – anishsane May 05 '16 at 07:31
  • Yes, that's exactly the problem. How can I do this without redirecting `stdin`? – sbrun May 05 '16 at 07:33
  • Either use `expect` to `spawn ssh` & use `interact` in between commands, or create a long string & use `ssh user@host command args` syntax or scp the heredoc contents to remote system & execute file from there... – anishsane May 05 '16 at 07:39
  • You've lost me. How do I use those commands? I'm not familiar with `expect`, `spawn`, and `interact`. – sbrun May 05 '16 at 08:10
  • First option using expect: See [this](http://stackoverflow.com/questions/19101879/bash-expect-script-for-ssh) question. Skip the `send exit` part. Second option: something like `ssh user@host "ls; echo hello world; /some/more/script argument1 argument2"`. Third option: `ssh user@host 'cat >/tmp/myscr' << EOF ... EOF` followed by `ssh user@host bash /tmp/myscr` – anishsane May 05 '16 at 08:21
  • I tried your temp script suggestion. That didn't work because `ssh user@host bash /tmp/myscr` seems to treat the `bash /tmp/myscr` as the new `stdin`. I've tried playing with expect and interact, but it seems like interact will always result in `:q` returning to the command prompt. I need more of my script to run after `vim` is exited. – sbrun May 05 '16 at 19:11
  • `ssh user@host bash /tmp/myscr` seems to treat the bash /tmp/myscr as the new stdin. That's unexpected. You should be able to run commands in this way. – anishsane May 06 '16 at 00:59
  • The commands run fine, it's just that `stdin` is still being redirected. I'm looking for a solution that preserves `stdin`. – sbrun May 06 '16 at 01:16
  • 1
    `ssh -t user@host bash /tmp/myscr ` ? – anishsane May 06 '16 at 03:31
  • Some commands are simply not intended for non-interactive use. I would suggest that if you're trying to make a script type commands into a remote `vim` instance, you probably want to find Another Way. If you want to edit files, `sed` and `ex` may be useful alternatives that lend themselves to scripting far more than `vim`. – ghoti May 06 '16 at 16:29
  • @ghoti I don't want to automate input to vim, I want the user to be able to do that themselves. I just want to ensure that commands from my script are run once vim is closed. – sbrun May 06 '16 at 16:39

2 Answers2

2

Ok, I think I've found my problem. I was creating a wrapper script for ssh that looked like this:

#!/bin/bash

tempScript="/tmp/myScript"
remote=user@host

commands=$(</dev/stdin)

cat <(echo "$commands") | ssh $remote "cat > $tempScript && chmod +x $tempScript" &&
ssh -t $remote $tempScript
errorCode=$?

ssh $remote << RM
  if [[ -f $tempScript ]]; then
    rm $tmpScript
  fi
RM

exit $errorCode

It was there that I was redirecting stdin, not ssh. I should have mentioned this when I formulated my question. I read through that script over and over again, but I guess I just overlooked that one line. Removing that line totally fixed my problem.

Just to clarify, changing my script to the following totally fixed my problem.

#!/bin/bash

tempScript="/tmp/myScript"
remote=user@host

commands="$@"

cat <(echo "$commands") | ssh $remote "cat > $tempScript && chmod +x $tempScript" &&
ssh -t $remote $tempScript
errorCode=$?

ssh $remote << RM
  if [[ -f $tempScript ]]; then
    rm $tmpScript
  fi
RM

exit $errorCode

Once I changed my wrapper script, my test script described in the question worked! I was able to print "hello" to the screen, vim appeared and I was able to use it like normal, and then once I quit vim "goodbye" was printed and the ssh client closed.

The commenters to the question were pointing me in the right direction the whole time. I'm sorry I only told part of my story.

sbrun
  • 197
  • 2
  • 13
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.

SourceSimian
  • 1,301
  • 8
  • 5