I have a system that I can only log in to under my username (myuser), but I need to run commands as other user (scriptuser). So far, I have come up with the following to run the commands I need:
ssh -tq myuser@hostname "sudo -u scriptuser bash -c \"ls -al\""
If however, when I try to run a more complex command, such as [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"
I quickly get into trouble with quoting. I'm not sure how I could pass this example complex command to bash -c
, when \"
already delimites the boundaries of the command I'm passing (and so I don't know how to quote /tmp/Some directory, which includes a spaces.
Is there a general solution allowing me to pass any command no matter how complex/crazy the quoting is, or is this some sort of limitation I have reached? Are there other possible and perhaps more readable solutions?
A trick I use sometimes is to use base64 to encode the commands, and pipe it to bash on the other site:
This will encode the script, with any commas, backslashes, quotes and variables inside a safe string, and send it to the other server. (
-w0
is required to disable line wrapping, which happens at column 76 by default). On the other side,$(base64 -d)
will decode the script and feed it to bash to be executed.I never got any problem with it, no matter how complex the script was. Solves the problem with escaping, because you don't need to escape anything. It does not creates a file on the remote host, and you can run vastly complicated scripts with ease.
See the
-tt
option? Read thessh(1)
manual.One thing I often do is use vim and use the
:!cat % | ssh -tt somemachine
trick.I think the easiest solution lies in a modification of @thanasisk's comment.
Create a script,
scp
it to the machine, then run it.Have the script
rm
itself at the start. The shell has opened the file, so it's been loaded, and can then be removed without problems.By doing things in this order (
rm
first, other stuff second) it'll even be removed when it fails at some point.You can use the
%q
format specifier withprintf
to take care of the variable escaping for you:printf -v
writes the output to a variable (in this case,$cmd_str
). I think that this is the simplest way to do it. There's no need to transfer any files or encode the command string (as much as I like the trick).Here's a more complex example showing that it works for things like square brackets and ampersands too:
I haven't tested it with
sudo
but it should be as simple as:If you want, you can skip a step and avoid creating the intermediate variable:
Or even just avoid creating a variable entirely:
UPDATE: Examples now explicitly use
sudo
.Here's a way to use Bash syntax with compound assignments to execute arbitrarily complex commands over SSH with sudo:
Formatting commands properly to be executed dynamically somewhere else (e.g. nested shells, remote SSH shells, etc.) can be very tricky - see https://stackoverflow.com/a/53197638/111948 for a good description of why. Complicated bash structures, like the syntax above, can help these commands work properly.
For more information about how
cat
and<<
combine to form inline here documents, please see https://stackoverflow.com/a/21761956/111948Are you aware that you can use
sudo
to give you a shell where you can run commands as the chosen user?You can define the script on your local machine and then
cat
and pipe it to the remote machine:Simple and easy.
If you use a sufficiently modern Bash shell, you could put your commands in a function and print that function as a string using
export -p -f function_name
. The result of that string can then contain arbitrary commands that do not necessarily have to run as root.An example using pipes from my answer on Unix.SE:
An example that retrieves saves the file for the login user and installs a program a root:
If you would like to run the whole command using
sudo
, you could use this:Here is my (not really tested) crappy solution for this:
Not you can do:
The next step is to create a
betterssh
script which already does this: