23

My problem is that I need to set a few variables, and output a few lines every time I login to the ssh shell, and at the same time I have to be able to use sftp to tarnsfer files via Filezilla.

Now, as per the openssh FAQ at http://www.openssh.org/faq.html, if your startup scripts echo any kind of output, it messes up with sftp. So it either delays indefinitely, or errors out with a "Connection closed by server with exit code 128".

I have tried solutions like moving .bashrc to .bash_profile, or using the following code in .bashrc:

if [ "$TERM" != "dumb" ]
then
   source .bashc_real
fi

And:

if [ "$TERM" = "xterm" ]
then
   source .bashc_real
fi

However, nothing works. My shell terminal is bash, and I connect to sftp with filezilla.

Totor
  • 2,916
  • 3
  • 23
  • 31
Joel G Mathew
  • 890
  • 1
  • 9
  • 19
  • This is also important when you're using an IDE like PyCharm, which (might) use SFTP for remote-host file editing. Took me a while to figure out why one remote-box worked, and another broke. – Cyclops Jul 28 '20 at 13:39

6 Answers6

28

Try doing this instead

if [ "$SSH_TTY" ]
then
   source .bashc_real
fi
Mike
  • 22,310
  • 7
  • 56
  • 79
  • This worked the best in the following scenarios: sftp, ssh and switch to another user, sudo su from that other user back to me. – Ray Foss Dec 03 '20 at 17:27
18

Mike's answer will probably work. But it's worth pointing out that you can accomplish this carefully selecting which startup files to put the verbose stuff in. From the bash man page:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior.

When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists. This may be inhibited by using the --norc option. The --rcfile file option will force bash to read and execute commands from file instead of ~/.bashrc.

The sftp/scp tools start an interactive non-login shell, so .bashrc will be sourced. Many distributions source .bashrc from .bash_profile or vice versa, so it can get confusing. A good trick for testing the cleanliness of your login environment is to ssh in with a command, which simulates the same way scp/sftp connect. For example: ssh myhost /bin/true will show you exactly what scp/sftp sees when they connect.

A simple demo:

insyte@mazer:~$ echo "echo Hello from .profile" > .profile
insyte@mazer:~$ echo "echo Hello from .bashrc" > .bashrc

sazerac:~ insyte$ ssh mazer /bin/true
Hello from .bashrc
sazerac:~ insyte$

insyte@mazer:~$ rm .bashrc

sazerac:~ insyte$ ssh mazer /bin/true
sazerac:~ insyte$

The first test will cause scp/sftp/rsync etc. to break. The second version will work just fine.

Insyte
  • 9,394
  • 3
  • 28
  • 45
  • I don't agree that *"The sftp/scp tools start an **interactive** non-login shell"* as we cannot really **interact** with the shell. But I don't understand why `.bashrc` would be source'd for `scp` or `ssh host command`. – SF.express Mar 26 '13 at 08:23
  • 2
    The term "interactive" isn't subjective. It's a term used by bash to describe one of its startup modes. It is a fact that sftp/scp start an "interactive non-login shell". Feel free to argue with the developers about whether not sourcing .bashrc is appropriate in this case; I'm just telling you what it does. – Insyte Mar 26 '13 at 15:16
  • 3
    `Bash` invoked by `scp` or `ssh host command` is indeed **non-interactive**. I just found this in bash manual: *"Bash attempts to determine **when it is being run with its standard input connected to a network connection**, as when executed by the remote shell daemon, usually **rshd, or the secure shell daemon sshd**. If bash determines it is being run in this fashion, it reads and executes commands from `~/.bashrc`, if that file exists and is readable."* Here is the [interesting history](http://lists.gnu.org/archive/html/bug-bash/2012-06/msg00028.html). – SF.express Mar 27 '13 at 06:40
  • 1
    Also see the section *Remote non login non interactive shells* in [this wiki page](http://mywiki.wooledge.org/DotFiles). – SF.express Mar 27 '13 at 06:47
4

If you're using csh:

if ($?prompt)
  ... interactive stuff ...

And if it's bash:

if [[ $- == *i* ]]; then
  ... interactive stuff ...
fi

or alternatively using bash regular expressions:

if [[ $- =~ i ]]; then
  ... interactive stuff ...
fi

These lines should precede lines where you ouput/echo something back.

flying sheep
  • 103
  • 2
user163384
  • 41
  • 1
  • 1
    Hi, could you explain the code, please. I know that $? is the return level of the previous command. I dont understand $?prompt and $-, though. Or is csh specific? – Joel G Mathew Mar 07 '13 at 14:52
  • 2
    @Droidzone: `$?var` in `csh` returns 1 if `var` is defined and 0 otherwise. `$-` in `bash` would has the `i` char in its value if the shell is interactive. – SF.express Mar 26 '13 at 08:14
  • 1
    Should update answer to explain $- is a special variable of shell options. see http://stackoverflow.com/questions/5163144/what-are-the-special-dollar-sign-shell-variables – maninvan Jan 13 '16 at 17:46
1

Mike's solution worked for me as well. But since my default shell is TCSH, I had to slightly edit the fix as follows (in .tcshrc):

if ( $?SSH_TTY ) then
    exec /bin/bash
endif

Just thought I would share for everyone's benefit.

1

Here are the first lines of my (default) .bashrc file:

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

The check for an interactive session avoids messing up with SCP, SFTP or ssh remote-host command mode.

Without this, if your file .bashrc uses echo or some other stuff printing on stdout, you may get this kind of errors:

  • SFTP : Received message too long 168435779
  • SCP : protocol error: unexpected <newline>
Totor
  • 2,916
  • 3
  • 23
  • 31
0

I like some of the other solutions mentioned here better, but I thought I throw out the solution that I currently use on my bash and csh VMs to prevent SFTP disconnects due to echo commands in my startup scripts, just in case anyone finds the information helpful.

In BASH:

if [ $TERM == "xterm" ] || [ $TERM == "xterm-256color" ]; then
  echo "Xterm display identified: echo enabled"
  echo_disable="0"
else
  echo_disable="1"
fi

# Use the following for all subsequent echo commands
if [ $echo_disable == 0 ]; then
 echo "Safe to display on Xterm"
fi

In csh:

if ($TERM == "xterm") then
  echo "Xterm display identified: echo enabled"
  set echo_disable = "0"
else
  set echo_disable = "1"
endif

# Use the following for all subsequent echo commands
if !( "$echo_disable" ) echo "Safe to display on Xterm"

It's a bit brute force, but it does work.

  • I tried using the [ "$SSH_TTY" ] code above and found that it only worked for simple client programs like putty. When I use NoMachine it doesn't provide any output. This is why I needed to include "xterm-256color" in the code above. – Bob Noonan Oct 29 '19 at 15:29
  • Interestingly enough, using "if ( $?SSH_TTY ) then" also did not work for my csh VM. I checked and there is no SSH_TTY environment variable defined. So my code above may be helpful to others that have a similar situation. – Bob Noonan Oct 29 '19 at 15:54