Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass commands as input to another command (su, ssh, sh, etc)

Tags:

bash

shell

unix

sh

ssh

I have a script where I need to start a command, then pass some additional commands as commands to that command. I tried

su echo I should be root now: who am I exit echo done. 

... but it doesn't work: The su succeeds, but then the command prompt is just staring at me. If I type exit at the prompt, the echo and who am i etc start executing! And the echo done. doesn't get executed at all.

Similarly, I need for this to work over ssh:

ssh remotehost # this should run under my account on remotehost su ## this should run as root on remotehost whoami exit ## back exit # back 

How do I solve this?

I am looking for answers which solve this in a general fashion, and which are not specific to su or ssh in particular. The intent is for this question to become a canonical for this particular pattern.

like image 378
tripleee Avatar asked Jun 02 '16 08:06

tripleee


2 Answers

Adding to tripleee's answer:

It is important to remember that the section of the script formatted as a here-document for another shell is executed in a different shell with its own environment (and maybe even on a different machine).

If that block of your script contains parameter expansion, command substitution, and/or arithmetic expansion, then you must use the here-document facility of the shell slightly differently, depending on where you want those expansions to be performed.

1. All expansions must be performed within the scope of the parent shell.

Then the delimiter of the here document must be unquoted.

command <<DELIMITER ... DELIMITER 

Example:

#!/bin/bash  a=0 mylogin=$(whoami) sudo sh <<END     a=1     mylogin=$(whoami)     echo a=$a     echo mylogin=$mylogin END echo a=$a echo mylogin=$mylogin 

Output:

a=0 mylogin=leon a=0 mylogin=leon 

2. All expansions must be performed within the scope of the child shell.

Then the delimiter of the here document must be quoted.

command <<'DELIMITER' ... DELIMITER 

Example:

#!/bin/bash  a=0 mylogin=$(whoami) sudo sh <<'END'     a=1     mylogin=$(whoami)     echo a=$a     echo mylogin=$mylogin END echo a=$a echo mylogin=$mylogin 

Output:

a=1 mylogin=root a=0 mylogin=leon 

3. Some expansions must be performed in the child shell, some - in the parent.

Then the delimiter of the here document must be unquoted and you must escape those expansion expressions that must be performed in the child shell.

Example:

#!/bin/bash  a=0 mylogin=$(whoami) sudo sh <<END     a=1     mylogin=\$(whoami)     echo a=$a     echo mylogin=\$mylogin END echo a=$a echo mylogin=$mylogin 

Output:

a=0 mylogin=root a=0 mylogin=leon 
like image 186
Leon Avatar answered Oct 10 '22 17:10

Leon


A shell script is a sequence of commands. The shell will read the script file, and execute those commands one after the other.

In the usual case, there are no surprises here; but a frequent beginner error is assuming that some commands will take over from the shell, and start executing the following commands in the script file instead of the shell which is currently running this script. But that's not how it works.

Basically, scripts work exactly like interactive commands, but how exactly they work needs to be properly understood. Interactively, the shell reads a command (from standard input), runs that command (with input from standard input), and when it's done, it reads another command (from standard input).

Now, when executing a script, standard input is still the terminal (unless you used a redirection) but the commands are read from the script file, not from standard input. (The opposite would be very cumbersome indeed - any read would consume the next line of the script, cat would slurp all the rest of the script, and there would be no way to interact with it!) The script file only contains commands for the shell instance which executes it (though you can of course still use a here document etc to embed inputs as command arguments).

In other words, these "misunderstood" commands (su, ssh, sh, sudo, bash etc) when run alone (without arguments) will start an interactive shell, and in an interactive session, that's obviously fine; but when run from a script, that's very often not what you want.

All of these commands have ways to accept commands by ways other than in an interactive terminal session. Typically, each command supports a way to pass it commands as options or arguments:

su root -c 'who am i' ssh user@remote uname -a sh -c 'who am i; echo success' 

Many of these commands will also accept commands on standard input:

printf 'uname -a; who am i; uptime' | su printf 'uname -a; who am i; uptime' | ssh user@remote printf 'uname -a; who am i; uptime' | sh 

which also conveniently allows you to use here documents:

ssh user@remote <<'____HERE'     uname -a     who am i     uptime ____HERE  sh <<'____HERE'     uname -a     who am i     uptime ____HERE 

For commands which accept a single command argument, that command can be sh or bash with multiple commands:

sudo sh -c 'uname -a; who am i; uptime' 

As an aside, you generally don't need an explicit exit because the command will terminate anyway when it has executed the script (sequence of commands) you passed in for execution.

like image 34
tripleee Avatar answered Oct 10 '22 19:10

tripleee