No, SSH only invokes a login shell when logging you into a shell, not when running a command. This means that if you want your login initialization files to run (e.g. ~/.profile), you need to do this explicitly. This is a deliberate design choice: in the days where most logins were on a text terminal, many users would run commands in their .profile which only make sense when logging in for an interactive session. (This is much less common now that most people log in locally to a graphical session.) You wouldn't want a quick ssh example.com ls, or worse an rsync example.com:somefile ., to start Emacs and turn on your modem to fetch your email!
The command in SSH is a string, obtained by concatenating the command line arguments with a space in between. For example ssh u@h shopt -q login_shell runs the command shopt -q login_shell. It's exactly equivalent to ssh u@h 'shopt -q login_shell', or ssh u@h 'shopt -q' login_shell, etc.
SSH passes that string to the user's login shell. SSH does not know about any other shell. The server might be running on a non-Unix system or in a restricted environment where there is no program called sh or bash.
The SSH server runs the executable that is listed in the user database as the user's login shell, but not necessarily as a login shell. When the command line is non-empty, it passes a three-argument list:
argv[0] is the program's base name, following the usual convention.
argv[1] is the string -c.
argv[2] is the command passed by the client.
For an empty command line, the SSH server invokes the user's login shell as a login shell. This means a one-argument list:
argv[0] is a dash (-) followed by the program's base name.
For example, here's the output of ssh localhost 'cat /proc/$$/cmdline | tr \\0 \\n' on a Linux machine:
sh
-c
cat /proc/$$/cmdline | tr \\0 \\n
For a login shell, it's more difficult to observe, because the login shell itself will typically run other programs. The easy way to see what's going on is to temporarily disable the login shell's initialization file(s), for example by temporarily renaming ~/.profile (or ~/.bash_profile, ~/.zprofile, etc. depending on which shell you use). Here's an example on an account with /bin/sh as the login shell:
$ mv ~/.profile ~/not.profile
$ ssh localhost
(/etc/motd content goes here)
Last login: Mon Apr 24 18:00:30 2023
$ cat /proc/$$/cmdline; echo
-sh
$ exit
Connection to localhost closed.
$ mv ~/not.profile ~/.profile
cd $(dirname $(which bash)); sudo ln -s bash -bash; chsh -s $(which -bash). – Anthony Apr 27 '23 at 18:00ssh host 'pstree -s $PPID', I seesystemd---sshd---sshd---sshd---pstree, whereas if I runssh host 'sleep 0; pstree -s $PPID', I seesystemd---sshd---sshd---sshd---bash---pstree. I'm surprised that the former doesn't showbashin the tree, and also surprised that prependingsleep 0 ;causesbashto show up in the process tree. Can you explain why this happens in the context of your answer? – Daniel Kessler Jul 02 '23 at 19:54cmdinbash -c 'cmd'but not inbash -c 'other-cmd; cmdorbash -c 'cmd > redir... Other shells like ksh or zsh optimise more aggressively and consistently. – Stéphane Chazelas Jul 02 '23 at 19:57sshdcallsbashwhich callspstree, but in the former case, due to some optimization in how the command is forked,bashdoesn't appear in thepstreeoutput. In other words,sshdis not truly directly callingpstree, right? – Daniel Kessler Jul 02 '23 at 20:00sshdforks a child process that executes bash and in that same process bash, executespstree, so during its lifetime that process has been running code fromsshd,bashandpstree. Had you donessh host 'env pstree, it would also have runenvinbetweenbashandpstree– Stéphane Chazelas Jul 02 '23 at 20:02-lsense) are very slow (due to some cruft in/etc/bashrc), which makes usingmagitpainful. Thanks to you, I now have a much better understanding of what I want to happen :) – Daniel Kessler Jul 02 '23 at 20:05ssh host cmd,sshdrunsbash -c cmd, not as a login shell. Login shells don't read bashrc, just profile (though profile could source bashrc). bash -c cmd is also not an interactive shell, so bashrc should not be read either, but on some systems, bash is configured at compile time to read bashrc when invoked by sshd (interactively or not) which may be what you're seeing. – Stéphane Chazelas Jul 02 '23 at 20:12exec ssh hostnameto get the process started (which will invoke a login shell that will fire bashrc). As a result, magit commands that use the existing TRAMP process (e.g., staging an entire file) are very fast on my remote (since they send the command to the already-running remote shell), but those that trigger a new TRAMP process (e.g., staging a hunk within a file) are slow (because they start a new ssh connection, wait for bashrc to run, and then send the command). – Daniel Kessler Jul 02 '23 at 20:20.profilesources it (which they often do to work around that misdesign of bash). It's not only bash reading .profile or .bashrc that's slow when you dossh hostorssh host cmd, establishing the ssh connection and authentication can be slow.sshcan be told to reuse a previous connection. See ControlMaster, ControlPersist inman ssh_config. – Stéphane Chazelas Jul 02 '23 at 20:25.bashrc/.bash_profileshould be just eliminated. To do so, I recommend to stick with "just".profile- as a side effect it forces you to write portable shell scripts in the first place. Personally my original problem wast that ssh doesn't execute "login shell" init files when executes commands instead of shell. – Anthony Aug 07 '23 at 22:07