1

Very similar with Run a command in an interactive shell with ssh after sourcing .bashrc, yet the answer there doesn't work for me.

I want to to execute remote command via ssh under the full interactive shell. I.e.,
run a remote command under the login shell, with some parameters.

Basically, I need the following two cases to work:

ssh user@remote_computer -t bash -l -c '/bin/echo PATH is $PATH'
ssh user@remote_computer -t 'bash -l -c "java -version"'

But currently I got a blank line from case 1, and java: command not found from case 2:

$ ssh user@remote_computer -t bash -l -c '/bin/echo PATH is $PATH'

Connection to remote_computer closed.

$ ssh user@remote_computer -t bash -l -c 'true; /bin/echo PATH is $PATH'
PATH is /usr/local/bin:/usr/bin:/bin:/usr/games
Connection to remote_computer closed.

ssh user@remote_computer -t bash -l -c 'true; java -version'
bash: line 1: java: command not found
Connection to remote_computer closed.

UPDATE:

At least two people think it is a quoting problem, but please can any one explain why it is a quoting problem, and how can I get what I wanted above. For e.g., this is also what I had tried:

ssh user@remote_computer -t 'bash -l -c "java -version"'
bash: line 1: java: command not found
Connection to remote_computer closed.

And I've run out of ideas how to quote it in a different way. Please help!

If I run the same command after ssh user@remote_computer, I'll get:

$ bash -l -c "java -version"
openjdk version "11.0.15" 2022-04-19 LTS
OpenJDK Runtime Environment Zulu11.56+19-CA (build 11.0.15+10-LTS)
OpenJDK 64-Bit Server VM Zulu11.56+19-CA (build 11.0.15+10-LTS, mixed mode)

So this is clearly not a quoting problem to me.

9
  • Please edit your question and explain what happens when you try the solutions from the question you linked to. Also, try ssh user@remote_computer -t bash -l -c 'true; echo PATH is $PATH' does that work as expected?
    – terdon
    Commented Jul 29, 2022 at 18:29
  • @terdon, OP updated. The output of PATH is obviously not from the login shell.
    – xpt
    Commented Jul 29, 2022 at 18:38
  • 1
    See the paragraph named "Discussion" and the procedure in the paragraph named "Solution" in this answer. Also this: How can I single-quote or escape the whole command line in Bash conveniently? Commented Jul 29, 2022 at 18:46
  • I don't understand @KamilMaciorowski, please see my updated OP.
    – xpt
    Commented Jul 29, 2022 at 20:35
  • 1
    something like 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
    – thrig
    Commented Aug 29, 2022 at 2:43

1 Answer 1

3

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.

1
  • An excellent answer, deserving of a bit more credit than it's gotten after 2k views.
    – mc0e
    Commented Dec 3, 2023 at 0:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .