I found a problem with Debian's Bash-completion for pmount and pumount, and intended to send a patch with the bug report - but I got carried away, and ended up completely re-writing the completion functions.

I'd like a review of this before sending my new functions with the bug report.

The original was in /etc/bash_completion.d/pmount, but the modern approach is to have two files in /usr/share/bash-completion/completions/ to support demand-loading of completion functions. We assume that the _init_completion is provided as part of this mechanism, to initialize shell variables cur and prev more usefully than the usual $2 and $3 (e.g. skipping redirection words).

I've gone to some lengths to find the actual available charsets and filesystem types that can be used, but the hairiest code is in finding the available names for devices (I like to be able to use the symlinks in /dev/disk/by-label/ to ensure I'm using the device/partition I'm expecting, for example).


/usr/share/bash-completion/completions/pmount

_pmount() {
    # shellcheck disable=SC2034
    local cur prev words cword
    _init_completion || return

   case "$prev" in
       -@(t|-type))
      COMPREPLY=($(grep "^[[:space:]]$cur" /proc/filesystems) $(find "/lib/modules/$(uname -r)/kernel/fs" -name "*.ko" -print0 | xargs -r -0 /sbin/modinfo | sed -ne 's/^alias: *fs-//p' | grep "^$cur"))
      return 0
      ;;

      -@(c|-charset))
      local encodings=(/sys/module/nls_* $(find "/lib/modules/$(uname -r)/kernel/fs/nls" -name '*.ko' -print0 | xargs -r -0 /sbin/modinfo | sed -ne 's/^\(name\|alias\): *//p'))
      COMPREPLY=($(compgen -W "${encodings[*]##*nls_}" -- "$cur"))
      return 0
      ;;
      -@(u|d|f|-umask|-dmask|-fmask))
      case "$cur" in
          '') COMPREPLY=( {0..7} ) ;;
          [0-7]|[0-7][0-7]) COMPREPLY=( $cur{0..7} ) ;;
          [0-7][0-7][0-7]) COMPREPLY=( $cur ) ;;
          *) return 1 ;;
      esac
      return 0
      ;;

      -@(p|-passphrase))
      _filedir
      return 0
      ;;

   esac

   if [[ "$cur" == -* ]]; then
        # transform "--help" output into completion list
       COMPREPLY=($(compgen -W "$(pmount --help | sed -e '/^..-/!d' -e 's/:.*//' -e 's/<[^>]*>//g' -e 'y/,/ /')" -- "$cur"))
   else
       local i allowed removable devices search
       local IFS=$'\n'
       allowed=($(grep -v '^[[:space:]]*#' /etc/pmount.allow))
       removable=($(for i in /sys/block/*
                    do
                        grep -Fxq 1 "$i/removable" || continue
                        # Replace with its partitions, if it has any - N.B. final /. is crucial, as
                        # subsystem entries are symlinks!
                        find "$i"/*/subsystem/. -maxdepth 0 -samefile "$i"/subsystem/. -print0 \
                            | awk -F/ -v RS='\0' -v i="${i##*/}" '{print"/dev/"$(NF-2)} END{if(!NR)print"/dev/"i}'
                        # # alternative non-awk version:
                        # j=$(find "$i"/*/subsystem/. -maxdepth 0 -samefile "$i"/subsystem/. -exec dirname '{}' \;)
                        # sed -e 's,.*/,/dev/,' <<<"${j:-$i}"
                    done))
       # Select only actual block devices that aren't already mounted
       # N.B. expansion of $allowed is unquoted, as wildcards are permitted
       devices=($(for i in ${allowed[*]} "${removable[@]}"; do test -b "$i" && echo "$i"; done | grep -vxF "$(cut -d' ' /proc/mounts -f1)"))
       test "${#devices[@]}" -gt 0 || return 0
       for i in "${devices[@]}"
       do search+=(-o -samefile "$i")
       done
       # alternative names - symlinks in /dev/disk/*/
       devices+=($(find -L /dev/disk -false "${search[@]}"))
       # all found names for mountable block devices, with and without initial /dev/
       COMPREPLY=($(compgen -W "$(printf '%q\n' "${devices[@]}" "${devices[@]#/dev/}")" -- "$cur"))
   fi
} &&
complete -F _pmount pmount

/usr/share/bash-completion/completions/pumount

_pumount() {
    # shellcheck disable=SC2034
    local cur prev words cword
    _init_completion || return

    if [[ "$cur" == -* ]]; then
        # transform "--help" output into completion list
       COMPREPLY=($(compgen -W "$(pumount --help | sed -e '/^..-/!d' -e 's/:.*//' -e 's/<[^>]*>//g' -e 'y/,/ /')" -- "$cur"))
   else
       local i mdir devices mounts search symlinks
       mdir=$(readlink -f /media)
       # shellcheck disable=SC2013
       for i in $(cut -d' ' -f1,2 /proc/mounts | grep -F " $mdir/")
       do
           # expand backslash escapes
           i=$(printf '%b' "$i")
           if test -b "$i"
           then
               search+=(-o -samefile "$i")
               devices+=("$i" "${i#/dev/}")
           elif test -d "$i"
           then
               mounts+=("$i" "${i#$mdir/}")
           fi
       done

       # alternative names - symlinks in /dev/disk/*/ and device or mountpoint basenames
       local IFS=$'\n'
       symlinks=($(find -L /dev/disk -false "${search[@]}"))
       COMPREPLY=($(compgen -W "$(printf '%q\n' "${mounts[@]}" "${devices[@]}" "${symlinks[@]}" "${symlinks[@]#/dev/}")" -- "$cur"))
   fi
} &&
complete -F _pumount pumount

Some specific questions:

  • Should I be checking that commands such as modinfo actually exist? Which commands don't need checking? (I believe that grep and cut are in "Essential" packages, so can be assumed, but I'm less sure about readlink, for example).
  • Is it guaranteed that /media is the only place to find the mountpoints?
  • How can I make those long lines wrap nicely without becoming even less clear?

Obviously, any other observations or improvements are invited, too!

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.