2

I use sshfs to remotely mount a remote folder to my local machine, the problem is that I need a way to know that I'm working under remote folder on local machine OR local folder to avoid accidentally do harm to remote one.

How to have zsh/bash prompt to reflect that I'm currently in remote mountpoint so I will do actions more carefully?

Example: sshfs now when I cd to local_of_remote_mountpoint, the prompt will notify me that this is folder that was mounted using sshfs like: user@host: sshfs% ls

  • Is it your aim to have your shell's prompt remind you of the mount point you are on (e.g. setting PS1 in your shell's startup file), or to have cd emit a warning when you enter a remote directory (e.g. defining a cd function that wraps the cd command)? – fra-san Feb 20 at 10:49
  • Probably not, as in example in my post, in reality I would use a symbol with maybe red color instead of sshfs in prompt, I don't need to know path - mountpoint. But zsh should check if the folder is a mountpoint that mounted with sshfs somehow. @fra-san – Tuyen Pham Feb 20 at 15:43
3

We face two distinct issues here:

  1. How to know if the current working directory (PWD) is on a filesystem mounted by sshfs;
  2. How to reflect point 1 in the shell prompt.

Is the current working directory part of a filesystem mounted by sshfs?

Comparing PWD with mount points is not reliable. It may not work, for instance, if a component of your PWD is a symbolic link to a mount point.

On GNU/Linux, tools as df and findmnt are able to determine the mount point a directory is on. Also, both can report the FSTYPE attribute of a filesystem, that is set to fuse.sshfs for those mounted by sshfs.

You can thus check with:

# df always prints a header line, we want to skip it
[ "fuse.sshfs" = "$(df --output=fstype . | tail -n +2)" ] && ...

or:

[ "fuse.sshfs" = "$(findmnt --noheadings --output=FSTYPE --target .)" ] && ...

You can wrap this test in a script and make it available in your PATH. Here we will call it check_sshfs_mp:

#!/bin/sh
[ "fuse.sshfs" = "$(findmnt --noheadings --output=FSTYPE --target .)" ] && exit 0
exit 1

How to set the shell prompt based on the mount point we are on?

In both bash and zsh you can set the PS1 shell variable, which is expanded as the primary prompt.

In bash you can do it in wrappers for the relevant functions: cd, pushd and popd (credits: thanks to this answer to "Conditional PS1" for reminding me cd is not the only one), adding this code to your ~/.bashrc:

cd () { builtin cd "$@" && chpwd; }

pushd () { builtin pushd "$@" && chpwd; }

popd () { builtin popd "$@" && chpwd; }

chpwd () {
    normal='\[\e[0m\]'
    red='\[\e[1;31m\]'
    PS1='[\u@\h \W]\$ '
    if command -v check_sshfs_mp > /dev/null 2>&1; then
        check_sshfs_mp && PS1='[\u@\h '"$red"'R'"$normal"' \W]\$ '
    fi
}

The chpwd function invokes check_sshfs_mp and, if its exit status is 0, adds a single red R letter to PS1.
You may want to adjust the default value of PS1 according to your preferences.

In zsh it is somehow easier, since zsh executes the chpwd special function whenever the current working directory is changed (references: The Z Shell Manual, Special Functions). You can add a prompt-setting function to the array of hook functions that are executed along with chpwd.
Assuming you are not using user contributions that interfere with prompt setting, you can add this code to your ~/.zshrc:

chpwd_check_sshfs_mp () {
    PS1='%m%# '
    if command -v check_sshfs_mp > /dev/null 2>&1; then
        check_sshfs_mp && PS1='%m %F{red}R%f %# '
    fi
}
chpwd_functions+=( chpwd_check_sshfs_mp )

Again, we are simply adding a red R to the primary prompt when chech_sshfs_mp succeeds.

Note that wrapping cd and adding a chpwd_functions hook function will not ensure the prompt is set correctly if the PWD your terminal is started in is on a sshfs mounted filesystem. You will need at least a cd to trigger the correct prompt, and this may be misleading.

User contributions to zsh that perform prompt theming will require ad-hoc solutions to customize prompts.
You can nevertheless shut down prompt theming with

prompt off

to experiment with this code.


Integrating points brought up in comments about zsh:

If you prefer to specify prompt coloring with ANSI escape sequences (instead of color names) you may change the PS1 assignment line in the above code snippet to:

check_sshfs_mp && PS1="$(print '%m %{\e[1;31m%}R%{\e[0m%} %# ')"

or

PS1='%m'"$(check_sshfs_mp && print '%{\e[1;31m%} R %{\e[0m%}')"'%# '

Note that print is required to turn \es into actual escape characters and the %{...%} form is needed to avoid the prompt code to misbehave. Reference: zsh FAQ: How do I get coloured prompts on my colour xterm?

If you prefer to have the check performed every time the primary prompt is set, you can avoid adding a hook function to chpwd_functions and add to your .zshrc:

setopt PROMPT_SUBST
export PS1='%m$(check_sshfs_mp && print "%{\e[1;31m%} R %{\e[0m%}")%# '

The PROMPT_SUBST option is needed to enable command substitution in prompt displaying. Reference: Prompt Expansion in The Z Shell Manual.

  • Thanks, probably I got what you mention, I'll test it tomorrow. – Tuyen Pham Feb 21 at 16:05
  • I need to bring this check_sshfs_mp && PS1='%m %F{red}R%f %# ' into PS1/PROMPT itself like PS1=<...>check_sshfs_mp && %F{red}R%f %<..> but how? Because I would bring it in $( ) due to check_sshfs_mp is a function and PS1 wouldn't understand %F{red}R%f % part if I put it in $( ). – Tuyen Pham Feb 22 at 1:33
  • I think I'll use $() and print to print out red R. Thanks. – Tuyen Pham Feb 22 at 1:43
  • 1
    (cont'd) 2) check_sshfs_mp is called within a chpwd hook function to avoid unnecessarily running it any time the prompt is displayed. But you are free not to use the chpwd_check_sshfs_mp function and just put setopt PROMPT_SUBST; export PROMPT='%m$(check_sshfs_mp && print '"'"'%{\e[1;31m%} R %{\e[0m%}'"'"')%# '; in your .zshrc. The PROMPT_SUBST option is needed to enable command substitution in prompt displaying. – fra-san Feb 22 at 9:09
  • 1
    @TuyenPham It works, but there are reasons for using single quoted %{...} expressions. See the last paragraph of this zsh FAQ item. It may be ok to use simple "s instead of the clunky '"'"'s needed to enclose the expression in single quotes, tough. – fra-san Feb 22 at 11:46
2

Along the line of what @fra.san suggested, you can modify your LOCAL prompt by tweaking PS1 so it reflects the fact that a folder in the path was mounted using sshfs.

First check that yr terminal supports color prompts. Include the following in yr ~/.kshrc or ~/.bashrc:

# Option is turned off by default to not distract the user.
force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
    # you have color support; assume it's compliant with Ecma-48
    # (ISO/IEC-6429). If support lacks use `setf` rather than `setaf`.
        color_prompt=yes
    else
        color_prompt=""
    fi
fi

Next, every time you mount a remote volume on your local FS with the wrapper sshfs_wrp, automatically append the path to that mounted remote volume to a temporary file, e.g. /tmp/tmp_rem_vol. You can do so with a simple wrapper to the sshfs command:

#!/usr/bin/sh
# script name: sshfs_wrp   # wrapper for sshfs
mount_path="$( echo "$@"| cut -d" " -f $# )"  # usually last parameter to `sshfs`
# Note: above will break when parameters passed to sshfs_wrp 
# contain blanks between quotes. 
echo "$mount_path" >> /tmp/tmp_rem_vol
sshfs "$@" 
exit 0

Make sure the script is executable and its location is reachable with the PATH environment variable.

Next, define a function in your ~/.kshrc or ~/.bashrc to set a flag variable, remFlag, to [R] in case the string "$(pwd)" indicates you currently work inside a remote volume subtree, previously mounted with sshfs_wrp. I used case for portability.
EDIT: Note that as soon as you find a match in the case pattern match section, you must exit the while loop.

function check_rem_vol () {
    if [ -s /tmp/tmp_rem_vol ] ; then
        while read rem_vol; do
            case "$(pwd)" in 
                # check whether pwd contains sshfs_wrp-mounted volume 
                *"${rem_vol}"*) remFlag="[R]" ; break  ;;  
                *) remFlag="" ;;
            esac
        done < /tmp/tmp_rem_vol
    else
        remFlag=""
    fi
    echo "$remFlag"
}

Finally, modify yr prompt PS1 in yr ~/.kshrc or ~/.bashrc, by including the remote volume flag, rem_vol, in bright green in the prompt in case some remote volume was mounted using sshfs_wrp.

if [ "$color_prompt" = yes ]; then
    # For Debian based systems only
    #PS1="${debian_chroot:+($debian_chroot)}\[\e[0;38;5;166m\][\#/\!]\[\e[1;34m\] \w\[\e[38;5;46m\] \$(check_rem_vol)\[\e[1;38;5;166m\]>\[\e[0m\]"

    # For Archlinux
    PS1="\\[\\e[0;38;5;166m\\][\\#/\\!]\\[\\e[1;34m\\] \\w\\[\\e[38;5;46m\\] \$(check_rem_vol)\\[\\e[1;38;5;166m\\]\\> \\[\\e[0m\\]"
else
    # For Debian based systems only
    #PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
    #PS1="${debian_chroot:+}[\#/\!] \[\w \$(check_rem_vol)\]>"

    # For Archlinux
    PS1="[\\#/\\!] \\[\\w \$(check_rem_vol)\\]>"
fi
export PS1
unset color_prompt force_color_prompt

This solution can be improved to reduce the number of moving parts and to improve the creation of the /tmp/tmp_rem_vol temporary file, or files, in case more than one ssh connection coexist and remote volumes are mounted from different remote hosts.

This should work both for one ssh connection, within a bash or ksh shell, on Debian or Arch Lx platforms.

When un-mounting the remote volume(s), or at session logout, or when closing the ssh connection, you should also take care of erasing /tmp/tmp_rem_vol to avoid persistence problem. You will find plenty of references and tips out there to automate that as well.

HTH

  • What would be the case when I have multiple remote mountpoints on the system? I assume this command *"${rem_vol}"*) remFlag="[R]" ;; will match against each line in tmp file, and I think we'd need to clean the tmp file in case of no remote mountpoint? – Tuyen Pham Feb 20 at 17:24
  • @TuyemPham: In case of multiple remote mountpoints, see my edit above. I modified the script so you can exit the while read loop at the first match. And yes, you should clean up with \rm -f /tmp/tmp_rem_vol 2> /dev/null upon logging out. When there is no remote vol mount point or if you do not use the wrapper sshfs_wrp,/tmp/tmp_rem_vol is simply not created. – Cbhihe Feb 21 at 7:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.