The shell -c
command over SSH is problematic here; there are tricky
quoting issues and it is not clear to me who is running what when. Here
customjava
is only known to something that reads ~/.bashrc
for
testing purposes. ZSH is my default shell.
$ ssh -t localhost bash -c 'source ~/.bashrc;customjava -version'
/home/jhqdoe/.bashrc: line 0: source: filename argument required
source: usage: source filename [arguments]
zsh:1: command not found: customjava
Connection to 127.0.0.1 closed.
$ ssh -t localhost bash -c ':;source ~/.bashrc;customjava -version'
java version 99999
Connection to 127.0.0.1 closed.
I guess you could strace
things and ssh -v -v -v
and probably
source code dive to figure out exactly what is different between the
above two commands (:
is the null command and is less typing than
true
) but if things are already this fragile and hard to debug I
would look for some other solution. My preference is usually to quote
the whole command:
$ ssh localhost 'bash -ic "customjava -version"'
java version 99999
However this will probably become too complicated if there are more
elaborate quoting needs and variable substitutions involved in the
commands. (Complicated shell quoting is not the sort of thing I want to
debug at 2AM in the morning, so I tend to avoid it by default.)
Instead Pipe
Another method is to pipe the commands to the required shell; this
minimizes the complexity on the command to run to just a shell
invocation that is probably optional:
$ printf 'customjava -version'"\n" | ssh localhost 'bash -i'
bash$ customjava -version
java version 99999
bash$ exit
$ printf 'customjava -version'"\n" | ssh localhost 'bash -l'
java version 99999
The downside here is that standard input is not a terminal, so if a
command on the other end really needs a terminal, this will not work.
There may be warnings about this.
$ printf 'customjava -version'"\n" | ssh localhost
Pseudo-terminal will not be allocated because stdin is not a terminal.
zsh: command not found: customjava
$ printf 'customjava -version'"\n" | ssh -t localhost
Pseudo-terminal will not be allocated because stdin is not a terminal.
zsh: command not found: customjava
Fake A Terminal
If a terminal is required I might switch to expect
; this creates a
fake terminal that the commands will be run in:
#!/usr/bin/env expect
spawn -noecho ssh localhost
# assume a prompt containing at least "% " (ZSH)
expect "% "
# replace ZSH with another shell
send -- "exec bash\n"
# assume a prompt of at least "$ "
expect "$ "
send -- "customjava -version\n"
expect "$ "
set version_info $expect_out(buffer)
send -- "exit\n"
puts "got >>>$version_info<<<"
but this has other problems, notably error checking and issues detecting
the shell prompt (which someone might fiddle with, for example, so the
first thing to do might be to set the prompt to some known value for
expect
to match on). It may also fall apart if someone breaks the
interactive shell configuration in any of innumerable ways. Maybe run
/bin/sh
and hope that is not bash
? But then you may need to
configure sh
for the custom java. This leads to...
Remove the Configuration from the Shell
Yet another way to solve this would be to write a special command on the
SSH server, probably an exec wrapper, that correctly configures the
environment and then runs the java
or whatever command. Then you could
run setup-our-env bash
(an interactive shell for the humans) or
setup-our-env java -version
(an easy command to run over SSH without
the complication of an interactive shell). An exec wrapper could be as
simple as:
#!/bin/sh
PATH=/custom/java/bin:$PATH
exec "$@"
In other words, the environment settings for the custom java version
would not be all mixed up with the interactive shell configuration and
thus could be applied to any required command.
ssh user@remote_computer -t bash -l -c 'true; echo PATH is $PATH'
does that work as expected?printf 'java -version' | ssh user@remote_computer 'bash -l'
might be less bad than trying to conjure an interactive shell out of a-c
shell and other such quoting fun