1

I'm experiencing a strange behavior when I try to execute one of my scripts on a remote machine via ssh.

I prepend the path to my scripts dir to $PATH in my .profile. But when I run ssh kalle@Raspi foo, I get "command not found".

ssh kalle@Raspi 'echo $PATH' gives the default value, and with variables I set and export in .profile, the output is empty.

I put an echo foo in .profile, and of course, it is printed. Then I put set -vx in the first line and the order of the printed lines is weird; e. g. "foo" is printed before "+echo foo".

Can anyone figure out what's going on?

EDIT:

When I do ssh kalle@Raspi 'bash -lc "echo \$PATH"', I get my full path.

EDIT 2:

I'm sorry for the confusion, I was very tired when I wanted to run my remote script and now my memory was wrong, but I remember now what I did.

I tried with bash -lc after it didn't work with the mere command, but I missed the backslash! So I had ssh kalle@Raspi 'bash -lc "echo $PATH"' and as always when you give a command string to another command, the shell performs its various expansions on that command string.

So after quote removal, we have bash -lc "echo $PATH" which is passed to ssh as a single argument and executed on the remote machine, where the shell performs parameter substitution, i. e. $PATH is replaced by the default path before it is passed to bash -lc, making the latter useless.

And then I was stupid enough to forget that I was using bash -lc, so I thought ssh kalle@Raspi foo would cause .profile to be read because I saw the output of my echo command I had put in my .profile.

What a shame. I must have been really tired because when I wrote this question, it was absolutely natural to me that I have to protect the variable with a backslash; I didn't even have to think about it. But I still didn't realise what I realised now.

So the behavior is pretty clear and absolutely normal, but the problem remains.

I can use bash -lc as a workaround which – depending on the situation – can be a hassle with quoting and escaping, or I can put . .profile before the actual command.

Does anybody know a solution that is more elegant? Something I can adjust on the server so I can just use ssh kalle@Raspi foo?

Christoph
  • 129
  • The issue is that the remote shell started by SSH is a non-login shell, which doesn't read your .profile file. You can force Bash to start a login shell by passing the -l option to Bash, like this: ssh kalle@Raspi 'bash -lc "echo \$PATH"'. Alternatively, you can move your path modifications to the .bashrc file and make sure that your .bash_profile file sources your .bashrc file. – kuspia Apr 04 '23 at 07:13
  • @kuspia: 1) But my .profile IS executed as my "echo" test has proven. However, it seems it isn't sourced. 2) My fried, I know that. You don't need to copy things from my question; I know what I've written ;) 3) I don't have a .bash_profile since it has no benefit over .profile if Bash is the only shell you use, as in my case. Read the section "INVOCATION" of the Bash manual. – Christoph Apr 04 '23 at 15:06
  • @kuspia: I'm sorry, you're right. .profile is NOT executed. I just tested it. – Christoph Apr 04 '23 at 15:43
  • 1
    @terdon, .bashrc is read by ssh user@host 'foo bar' (Bash startup files / Invoked by remote shell daemon) – ilkkachu Apr 04 '23 at 17:22
  • @ilkkachu dammit, I keep forgetting that, thanks! But in many (most?) Linux systems, it will exit immediately because of the things explained in that question. So yes, you're right, it is read, but because of the [ -z "$PS1" ] && return or equivalent, anything that isn't at the very beginning of the file will not be read. This is why it's confusing if you add say echo foo to your ~/.bashrc and then ssh localhost hostname, you just get the host name and no foo. – terdon Apr 04 '23 at 17:35
  • Sorry, @kuspia, see above. – terdon Apr 04 '23 at 17:44
  • @terdon, oh, rats, good point! I've probably missed that before, too, since I've dropped that test from my .bashrc... (Or rather, started from a clean file and never bothered to add it. And yes, I've learned to protect any output from there with if tty -s or so.) Anyway, the fact that Bash does read it in that case is somewhat odd, IMO, it doesn't seem to fit with logic it otherwise has there. – ilkkachu Apr 04 '23 at 19:35

1 Answers1

0

Thanks to @ilkkachu, I found a solution.

I moved the variables stuff from .profile to a separate script which I source from within .profile.

Then, I modified the "If not running interactively, don't do anything" part in .bashrc as follows:

case $- in
    *i*) ;;
      *)
        # Not running interactively.
        # Check if we are the server of an SSH connection without a PTY.
        # If so, sshd is about to execute a command issued by some client,
        # in which case this script has been invoked directly by sshd, not
        # by .profile, so we need to set vars that are normally set in .profile
        # and that are important for my scripts (like $PATH) here before returning.
        # This way, I can call my scripts just via ‘ssh kalle@Raspi foo’ without
        # using ‘bash -lc’.
        #
        # References:
        # https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html
        # https://man.openbsd.org/ssh#ENVIRONMENT
    [[ $SSH_CONNECTION && -z $SSH_TTY ]] && . $HOME/scripts/bashrc-include/set-vars.sh
    return;;

esac

EDIT:

Not quite unimportant: Prove that it works without side effects.

I placed echoes at the head of .profile, .bashrc and set-vars.sh, and another echo after the case ... in ... esac above.

I should mention that I'm using the same .profile and .bashrc on all my machines.

So, let's test it:

When I open a terminal on the machine I'm writing this, I get:

.profile: Started.
set-vars.sh: Setting vars.
.bashrc: Started.
.bashrc: Interactive shell, proceeding.

When I ssh into my Raspi, I get exactly the same.

When I issue ssh kalle@Raspi 'echo $PATH', I get:

.bashrc: Started.
set-vars.sh: Setting vars.

and the correct $PATH.

EDIT 2:

A small script to switch the messages on and off:

#!/bin/bash

Author: derkallevombau

Created 2023-04-07 12:58:48

if [[ $1 =~ ^on|1|true$ ]]; then sedScript='"s/^#([\t ]echo $(basename $f):.)/\1/"' elif [[ $1 =~ ^off|0|false$ ]]; then sedScript='"s/^([\t ]echo $(basename $f):.)/#\1/"' else cat <<-END Unknown argument: '$1'.

    Invocation: bash-startup-messages-set (on|1|true)|(off|0|false)
END

exit

fi

for f in ~/.{profile,bashrc} /home/kalle/scripts/bashrc-include/set-vars.sh; do eval sed -Ei $sedScript $f done

Christoph
  • 129