aboutsummaryrefslogtreecommitdiff
path: root/bash
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2015-10-21 13:08:09 +1300
committerTom Ryder <tom@sanctum.geek.nz>2015-10-21 13:08:09 +1300
commitdcfc80aaa112498498d93bae3ae0eb2088244c47 (patch)
tree889854ba1c96f1c86465da1bbd5cc87f0c8d4b9f /bash
parentHandle spaces correctly in completions (diff)
downloaddotfiles-dcfc80aaa112498498d93bae3ae0eb2088244c47.tar.gz
dotfiles-dcfc80aaa112498498d93bae3ae0eb2088244c47.zip
Tidy up completion considerably; no more compgen
* Remove all instances of compgen; for filename completion it's quite broken as it relies on implicit wordsplitting in array context, and doesn't have an option to print with a null delimiter; replaced with manual for/while loops instead * Add IFS= to while/read loops over filenames * Use "dirname/s" instead of "dir/s" variables to avoid keyword collisions and for clarity * Remove some unnecessary variables * Use shorter syntax for loop exit conditions * Move completion options into functions where applicable rather than having them on the completion definition itself
Diffstat (limited to 'bash')
-rw-r--r--bash/bashrc.d/apf.bash2
-rw-r--r--bash/bashrc.d/bd.bash56
-rw-r--r--bash/bashrc.d/cf.bash20
-rw-r--r--bash/bashrc.d/fnl.bash8
-rw-r--r--bash/bashrc.d/ftp.bash25
-rw-r--r--bash/bashrc.d/git.bash21
-rw-r--r--bash/bashrc.d/gpg.bash27
-rw-r--r--bash/bashrc.d/make.bash20
-rw-r--r--bash/bashrc.d/mysql.bash53
-rw-r--r--bash/bashrc.d/pass.bash33
-rw-r--r--bash/bashrc.d/path.bash177
-rw-r--r--bash/bashrc.d/sd.bash46
-rw-r--r--bash/bashrc.d/ssh.bash25
-rw-r--r--bash/bashrc.d/ud.bash51
-rw-r--r--bash/bashrc.d/vr.bash4
15 files changed, 325 insertions, 243 deletions
diff --git a/bash/bashrc.d/apf.bash b/bash/bashrc.d/apf.bash
index af759577..01b38437 100644
--- a/bash/bashrc.d/apf.bash
+++ b/bash/bashrc.d/apf.bash
@@ -85,7 +85,7 @@ apf() {
# Read all the null-delimited arguments from the file
local -a args
local arg
- while read -d '' -r arg ; do
+ while IFS= read -d '' -r arg ; do
args=("${args[@]}" "$arg")
done < "$argfile"
diff --git a/bash/bashrc.d/bd.bash b/bash/bashrc.d/bd.bash
index 110fca06..e4a6738e 100644
--- a/bash/bashrc.d/bd.bash
+++ b/bash/bashrc.d/bd.bash
@@ -34,24 +34,24 @@ bd() {
[[ $req != / ]] || req=${req%/}
# What to do now depends on the request
- local dir
+ local dirname
case $req in
# If no argument at all, just go up one level
'')
- dir=..
+ dirname=..
;;
# Just go straight to the root or dot directories if asked
/|.|..)
- dir=$req
+ dirname=$req
;;
# Anything else with a leading / needs to anchor to the start of the
# path
/*)
- dir=$req
- if [[ $PWD != "$dir"/* ]] ; then
+ dirname=$req
+ if [[ $PWD != "$dirname"/* ]] ; then
printf 'bash: %s: Directory name not in path\n' \
"$FUNCNAME" >&2
return 1
@@ -59,13 +59,13 @@ bd() {
;;
# In all other cases, iterate through the directory tree to find a
- # match, or whittle the dir down to an empty string trying
+ # match, or whittle the dirname down to an empty string trying
*)
- dir=${PWD%/*}
- while [[ -n $dir && $dir != */"$req" ]] ; do
- dir=${dir%/*}
+ dirname=${PWD%/*}
+ while [[ -n $dirname && $dirname != */"$req" ]] ; do
+ dirname=${dirname%/*}
done
- if [[ -z $dir ]] ; then
+ if [[ -z $dirname ]] ; then
printf 'bash: %s: Directory name not in path\n' \
"$FUNCNAME" >&2
return 1
@@ -74,26 +74,32 @@ bd() {
esac
# Try to change into the determined directory
- builtin cd "${opts[@]}" -- "$dir"
+ builtin cd "${opts[@]}" -- "$dirname"
}
# Completion setup for bd
_bd() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
-
- # Build a list of dirs in $PWD
- local -a dirs
- while read -d / -r dir ; do
- if [[ -n $dir ]] ; then
- dirs=("${dirs[@]}" "$dir")
- fi
- done < <(printf %s "$PWD")
-
- # Complete with matching dirs
+
+ # The completions given are filenames and may require escaping
compopt -o filenames
- local IFS=$'\n'
- COMPREPLY=( $(compgen -W "${dirs[*]}" -- "$word") )
+
+ # Only makes sense for the first argument
+ ((COMP_CWORD == 1)) || return 1
+
+ # Build a list of dirnames in $PWD
+ local -a dirnames
+ IFS=/ read -d '' -a dirnames < <(printf '%s\0' "${PWD#/}")
+
+ # Remove the last element in the array (the current directory)
+ ((${#dirnames[@]})) || return 1
+ dirnames=("${dirnames[@]:0:$((${#dirnames[@]}-1))}")
+
+ # Add the matching dirnames to the reply
+ local dirname
+ for dirname in "${dirnames[@]}" ; do
+ [[ $dirname == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$dirname")
+ done
}
complete -F _bd bd
diff --git a/bash/bashrc.d/cf.bash b/bash/bashrc.d/cf.bash
index be66ecea..4109fb18 100644
--- a/bash/bashrc.d/cf.bash
+++ b/bash/bashrc.d/cf.bash
@@ -1,30 +1,30 @@
# Count files
cf() {
- local dir
+ local dirname
# Specify directory to check
- dir=${1:-$PWD}
+ dirname=${1:-$PWD}
# Error conditions
- if [[ ! -e $dir ]] ; then
+ if [[ ! -e $dirname ]] ; then
printf 'bash: %s: %s does not exist\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
- elif [[ ! -d $dir ]] ; then
+ elif [[ ! -d $dirname ]] ; then
printf 'bash: %s: %s is not a directory\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
- elif [[ ! -r $dir ]] ; then
+ elif [[ ! -r $dirname ]] ; then
printf 'bash: %s: %s is not readable\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
# Count files and print; use a subshell so options are unaffected
(
shopt -s dotglob nullglob
- declare -a files=("$dir"/*)
- printf '%d\t%s\n' "${#files[@]}" "$dir"
+ declare -a files=("$dirname"/*)
+ printf '%d\t%s\n' "${#files[@]}" "$dirname"
)
}
complete -A directory cf
diff --git a/bash/bashrc.d/fnl.bash b/bash/bashrc.d/fnl.bash
index 9f894654..efdaa1c5 100644
--- a/bash/bashrc.d/fnl.bash
+++ b/bash/bashrc.d/fnl.bash
@@ -23,20 +23,20 @@ fnl() {
fi
# Create a temporary directory or bail
- local template dir
+ local template dirname
template=$FUNCNAME.$1.XXXXX
- if ! dir=$(mktemp -dt -- "$template") ; then
+ if ! dirname=$(mktemp -dt -- "$template") ; then
return
fi
# Run the command and save its exit status
local ret
- "$@" >"$dir"/stdout 2>"$dir"/stderr
+ "$@" >"$dirname"/stdout 2>"$dirname"/stderr
ret=$?
# Note these are *not* local variables
# shellcheck disable=SC2034
- fnl_stdout=$dir/stdout fnl_stderr=$dir/stderr
+ fnl_stdout=$dirname/stdout fnl_stderr=$dirname/stderr
declare -p fnl_std{out,err}
# Return the exit status of the command, not the declare builtin
diff --git a/bash/bashrc.d/ftp.bash b/bash/bashrc.d/ftp.bash
index b8e8c55e..f046a683 100644
--- a/bash/bashrc.d/ftp.bash
+++ b/bash/bashrc.d/ftp.bash
@@ -1,14 +1,13 @@
# Completion for ftp with .netrc machines
_ftp() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
+
+ # Do default completion if no results
+ compopt -o default
# Bail if the .netrc file is illegible
local netrc
netrc=$HOME/.netrc
- if [[ ! -r $netrc ]] ; then
- return 1
- fi
+ [[ -r $netrc ]] || return 1
# Tokenize the file
local -a tokens
@@ -16,19 +15,23 @@ _ftp() {
# Iterate through tokens and collect machine names
local -a machines
- local -i machine
+ local -i nxm
local token
for token in "${tokens[@]}" ; do
- if ((machine)) ; then
+ if ((nxm)) ; then
machines=("${machines[@]}" "$token")
- machine=0
+ nxm=0
elif [[ $token == machine ]] ; then
- machine=1
+ nxm=1
fi
done
# Generate completion reply
- COMPREPLY=( $(compgen -W "${machines[*]}" -- "$word") )
+ local machine
+ for machine in "${machines[@]}" ; do
+ [[ $machine == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$machine")
+ done
}
-complete -F _ftp -o default ftp
+complete -F _ftp ftp
diff --git a/bash/bashrc.d/git.bash b/bash/bashrc.d/git.bash
index 5965607c..182cef46 100644
--- a/bash/bashrc.d/git.bash
+++ b/bash/bashrc.d/git.bash
@@ -1,27 +1,22 @@
# Completion for git local branch names
_git() {
- # Bail if not a git repo (or no git!)
- if ! git rev-parse --git-dir >/dev/null 2>&1 ; then
- return 1
- fi
+ # Use default completion if no matches
+ compopt -o default
- # Get current and previous word
- local word first
- word=${COMP_WORDS[COMP_CWORD]}
- first=${COMP_WORDS[1]}
+ # Bail if not a git repo (or no git!)
+ git rev-parse --git-dir >/dev/null 2>&1 || return 1
# Switch on the previous word
- case $first in
+ case ${COMP_WORDS[1]} in
# If the first word is appropriate, complete with branch/tag names
checkout|merge|rebase)
- local -a branches
local branch
while read -r branch ; do
- branches=("${branches[@]}" "${branch##*/}")
+ [[ $branch == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$branch")
done < <(git for-each-ref refs/{heads,tags} 2>/dev/null)
- COMPREPLY=( $(compgen -W "${branches[*]}" -- "$word") )
return
;;
@@ -31,5 +26,5 @@ _git() {
;;
esac
}
-complete -F _git -o default git
+complete -F _git git
diff --git a/bash/bashrc.d/gpg.bash b/bash/bashrc.d/gpg.bash
index 83bda3d7..3f01db64 100644
--- a/bash/bashrc.d/gpg.bash
+++ b/bash/bashrc.d/gpg.bash
@@ -14,23 +14,22 @@ gpg() {
# Completion for gpg with long options
_gpg() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
+
+ # Complete with directories/files if no matches
+ compopt -o default
# Bail if no gpg(1)
- if ! hash gpg 2>/dev/null ; then
- return 1
- fi
+ hash gpg 2>/dev/null || return 1
- # Bail if word doesn't start with two dashes
- if [[ $word != --* ]] ; then
- return 1
- fi
+ # Bail if not completing an option
+ [[ ${COMP_WORDS[COMP_CWORD]} == --* ]] || return 1
- # Generate completion reply
- COMPREPLY=( $(compgen -W \
- "$(gpg --dump-options 2>/dev/null)" \
- -- "$word") )
+ # Generate completion reply from gpg(1) options
+ local option
+ while read -r option ; do
+ [[ $option == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$option")
+ done < <(gpg --dump-options 2>/dev/null)
}
-complete -F _gpg -o default gpg
+complete -F _gpg gpg
diff --git a/bash/bashrc.d/make.bash b/bash/bashrc.d/make.bash
index df4c0f56..216894fd 100644
--- a/bash/bashrc.d/make.bash
+++ b/bash/bashrc.d/make.bash
@@ -1,17 +1,16 @@
# Completion setup for Make, completing targets
_make() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
- # Quietly bail if no legible Makefile
- if [[ ! -r Makefile ]] ; then
- return 1
- fi
+ # Do default completion if no matches
+ compopt -o default
+
+ # Bail if no legible Makefile
+ [[ ! -r Makefile ]] || return 1
# Build a list of targets by parsing the Makefile
local -a targets tokens
local target line
- while read -r line ; do
+ while read -r line ; do
if [[ $line == *:* ]] ; then
target=$line
target=${target%%:*}
@@ -25,7 +24,10 @@ _make() {
done < Makefile
# Complete with matching targets
- COMPREPLY=( $(compgen -W "${targets[*]}" -- "$word") )
+ for target in "${targets[@]}" ; do
+ [[ $target == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$target")
+ done
}
-complete -F _make -o default make
+complete -F _make make
diff --git a/bash/bashrc.d/mysql.bash b/bash/bashrc.d/mysql.bash
index 5ef6f67c..33231c13 100644
--- a/bash/bashrc.d/mysql.bash
+++ b/bash/bashrc.d/mysql.bash
@@ -23,26 +23,41 @@ mysql() {
# Completion setup for MySQL for configured databases
_mysql() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
-
- # Check directory exists and has at least one .cnf file
- local dir
- dir=$HOME/.mysql
- if [[ ! -d $dir ]] || (
- shopt -s nullglob dotglob
- declare -a files=("$dir"/*.cnf)
- ((! ${#files[@]}))
- ) ; then
- return 1
- fi
+
+ # The completed results are filenames, and if there are no matches, do
+ # default completion
+ compopt -o filenames -o default
+
+ # Only makes sense for first argument
+ ((COMP_CWORD == 1)) || return 1
+
+ # Bail if directory doesn't exist
+ local dirname
+ dirname=$HOME/.mysql
+ [[ -d $dirname ]] || return 1
# Return the names of the .cnf files sans prefix as completions
- local -a items
- items=("$dir"/*.cnf)
- items=("${items[@]##*/}")
- items=("${items[@]%%.cnf}")
- COMPREPLY=( $(compgen -W "${items[*]}" -- "$word") )
+ local db
+ while IFS= read -d '' -r db ; do
+ [[ $db == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$db")
+ done < <(
+
+ # Set options so that globs expand correctly
+ shopt -s dotglob nullglob
+
+ # Collect all the config file names, strip off leading path and .cnf
+ local -a cnfs
+ cnfs=("$dirname"/*.cnf)
+ cnfs=("${cnfs[@]#$dirname/}")
+ cnfs=("${cnfs[@]%.cnf}")
+
+ # Bail if no files to prevent empty output
+ ((${#cnfs[@]})) || exit 1
+
+ # Print the conf names, null-delimited
+ printf '%s\0' "${cnfs[@]}"
+ )
}
-complete -F _mysql -o default mysql
+complete -F _mysql mysql
diff --git a/bash/bashrc.d/pass.bash b/bash/bashrc.d/pass.bash
index 8513e5dd..e8748118 100644
--- a/bash/bashrc.d/pass.bash
+++ b/bash/bashrc.d/pass.bash
@@ -1,7 +1,5 @@
-# Requires Bash >= 4.0 for dotglob, nullglob, and globstar
-if ((BASH_VERSINFO[0] < 4)) ; then
- return
-fi
+# Requires Bash >= 4.0 for globstar
+((BASH_VERSINFO[0] >= 4)) || return
# Custom completion for pass(1), because I don't like the one included with the
# distribution
@@ -10,25 +8,23 @@ _pass()
# If we can't read the password directory, just bail
local passdir
passdir=${PASSWORD_STORE_DIR:-$HOME/.password-store}
- if [[ ! -r $passdir ]] ; then
- return 1
- fi
+ [[ -r $passdir ]] || return 1
# Iterate through list of .gpg paths, extension stripped, null-delimited,
# and filter them down to the ones matching the completing word (compgen
# doesn't seem to do this properly with a null delimiter)
- local word entry
- word=${COMP_WORDS[COMP_CWORD]}
- while read -d '' -r entry ; do
- if [[ $entry == "$word"* ]] ; then
- COMPREPLY=("${COMPREPLY[@]}" "$entry")
- fi
+ local entry
+ while IFS= read -d '' -r entry ; do
+ [[ $entry == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+
+ # We have to use printf %q here to quote the entry, as it may include
+ # spaces or newlines, just like any filename
+ COMPREPLY=("${COMPREPLY[@]}" "$(printf %q "$entry")")
done < <(
# Set shell options to expand globs the way we expect
shopt -u dotglob
- shopt -s nullglob
- shopt -s globstar
+ shopt -s globstar nullglob
# Change into password directory, or bail
cd -- "$passdir" 2>/dev/null || exit
@@ -38,10 +34,11 @@ _pass()
entries=(**/*.gpg)
entries=("${entries[@]%.gpg}")
+ # Bail if no entries to prevent empty output
+ ((${#entries[@]})) || exit 1
+
# Print all the entries, null-delimited
- if ((${#entries[@]})) ; then
- printf '%s\0' "${entries[@]}"
- fi
+ printf '%s\0' "${entries[@]}"
)
}
complete -F _pass pass
diff --git a/bash/bashrc.d/path.bash b/bash/bashrc.d/path.bash
index e73e6b2f..c3e02627 100644
--- a/bash/bashrc.d/path.bash
+++ b/bash/bashrc.d/path.bash
@@ -22,7 +22,7 @@ USAGE:
$FUNCNAME h[elp]
Print this help message (also done if command not found)
$FUNCNAME l[ist]
- Print the current dirs in PATH, one per line (default command)
+ Print the current directories in PATH, one per line (default command)
$FUNCNAME i[nsert] DIR
Add a directory to the front of PATH, checking for existence and uniqueness
$FUNCNAME a[ppend] DIR
@@ -52,30 +52,30 @@ EOF
insert|i)
local -a patharr
IFS=: read -a patharr < <(printf '%s\n' "$PATH")
- local dir
- dir=$1
- [[ $dir == / ]] || dir=${dir%/}
- if [[ -z $dir ]] ; then
+ local dirname
+ dirname=$1
+ [[ $dirname == / ]] || dirname=${dirname%/}
+ if [[ -z $dirname ]] ; then
printf 'bash: %s: need a directory path to insert\n' \
"$FUNCNAME" >&2
return 1
fi
- if [[ ! -d $dir ]] ; then
+ if [[ ! -d $dirname ]] ; then
printf 'bash: %s: %s not a directory\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
- if [[ $dir == *:* ]] ; then
+ if [[ $dirname == *:* ]] ; then
printf 'bash: %s: Cannot add insert directory %s with colon in name\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
- if path check "$dir" ; then
+ if path check "$dirname" ; then
printf 'bash: %s: %s already in PATH\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
- patharr=("$dir" "${patharr[@]}")
+ patharr=("$dirname" "${patharr[@]}")
path set "${patharr[@]}"
;;
@@ -83,30 +83,30 @@ EOF
append|add|a)
local -a patharr
IFS=: read -a patharr < <(printf '%s\n' "$PATH")
- local dir
- dir=$1
- [[ $dir == / ]] || dir=${dir%/}
- if [[ -z $dir ]] ; then
+ local dirname
+ dirname=$1
+ [[ $dirname == / ]] || dirname=${dirname%/}
+ if [[ -z $dirname ]] ; then
printf 'bash: %s: need a directory path to append\n' \
"$FUNCNAME" >&2
return 1
fi
- if [[ ! -d $dir ]] ; then
+ if [[ ! -d $dirname ]] ; then
printf 'bash: %s: %s not a directory\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
- if [[ $dir == *:* ]] ; then
+ if [[ $dirname == *:* ]] ; then
printf 'bash: %s: Cannot append directory %s with colon in name\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
- if path check "$dir" ; then
+ if path check "$dirname" ; then
printf 'bash: %s: %s already in PATH\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
- patharr=("${patharr[@]}" "$dir")
+ patharr=("${patharr[@]}" "$dirname")
path set "${patharr[@]}"
;;
@@ -114,23 +114,23 @@ EOF
remove|rm|r)
local -a patharr
IFS=: read -a patharr < <(printf '%s\n' "$PATH")
- local dir
- dir=$1
- [[ $dir == / ]] || dir=${dir%/}
- if [[ -z $dir ]] ; then
+ local dirname
+ dirname=$1
+ [[ $dirname == / ]] || dirname=${dirname%/}
+ if [[ -z $dirname ]] ; then
printf 'bash: %s: need a directory path to remove\n' \
"$FUNCNAME" >&2
return 1
fi
- if ! path check "$dir" ; then
+ if ! path check "$dirname" ; then
printf 'bash: %s: %s not in PATH\n' \
- "$FUNCNAME" "$dir" >&2
+ "$FUNCNAME" "$dirname" >&2
return 1
fi
local -a newpatharr
local part
for part in "${patharr[@]}" ; do
- [[ $dir == "$part" ]] && continue
+ [[ $dirname == "$part" ]] && continue
newpatharr=("${newpatharr[@]}" "$part")
done
path set "${newpatharr[@]}"
@@ -139,9 +139,9 @@ EOF
# Set the PATH to the given directories without checking existence or uniqueness
set|s)
local -a newpatharr
- local dir
- for dir ; do
- newpatharr=("${newpatharr[@]}" "$dir")
+ local dirname
+ for dirname ; do
+ newpatharr=("${newpatharr[@]}" "$dirname")
done
PATH=$(IFS=: ; printf '%s' "${newpatharr[*]}")
;;
@@ -150,17 +150,17 @@ EOF
check|c)
local -a patharr
IFS=: read -a patharr < <(printf '%s\n' "$PATH")
- local dir
- dir=$1
- [[ $dir == / ]] || dir=${dir%/}
- if [[ -z $dir ]] ; then
+ local dirname
+ dirname=$1
+ [[ $dirname == / ]] || dirname=${dirname%/}
+ if [[ -z $dirname ]] ; then
printf 'bash: %s: need a directory path to check\n' \
"$FUNCNAME" >&2
return 1
fi
local part
for part in "${patharr[@]}" ; do
- if [[ $dir == "$part" ]] ; then
+ if [[ $dirname == "$part" ]] ; then
return 0
fi
done
@@ -179,51 +179,68 @@ EOF
# Completion for path
_path() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
-
- # Complete operation as first word
- if ((COMP_CWORD == 1)) ; then
- COMPREPLY=( $(compgen -W \
- 'help list insert append remove set check' \
- -- "$word") )
- else
- case ${COMP_WORDS[1]} in
- # Complete with one directory
- insert|i|append|add|a|check|c)
- if ((COMP_CWORD == 2)) ; then
+ # What to do depends on which word we're completing
+ case $COMP_CWORD in
+
+ # Complete operation as first word
+ 1)
+ for cmd in help list insert append remove set check ; do
+ [[ $cmd == "${COMP_REPLY[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$cmd")
+ done
+ ;;
+
+ # Complete with either directories or $PATH entries as all other words
+ *)
+ case ${COMP_WORDS[1]} in
+
+ # Complete with a directory
+ insert|i|append|add|a|check|c|set|s)
compopt -o filenames
- local IFS=$'\n'
- COMPREPLY=( $(compgen -A directory -- "$word") )
- fi
- ;;
-
- # Complete with any number of directories
- set|s)
- compopt -o filenames
- local IFS=$'\n'
- COMPREPLY=( $(compgen -A directory -- "$word") )
- ;;
-
- # Complete with directories from PATH
- remove|rm|r)
- local -a promptarr
- IFS=: read -a promptarr < <(printf '%s\n' "$PATH")
- local part
- for part in "${promptarr[@]}" ; do
- if [[ $part && $part == "$word"* ]] ; then
+ local dirname
+ while IFS= read -d '' -r dirname ; do
+ [[ $dirname == "${COMP_WORDS[COMP_CWORD]}"/* ]] \
+ || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$dirname")
+ done < <(
+
+ # Set options to glob correctly
+ shopt -s dotglob nullglob
+
+ # Collect directory names, strip trailing slash
+ local -a dirnames
+ dirnames=(*/)
+ dirnames=("${dirnames[@]%/}")
+
+ # Bail if no results to prevent empty output
+ ((${#dirnames[@]})) || exit 1
+
+ # Print results, null-delimited
+ printf '%s\0' "${dirnames[@]}"
+ )
+ ;;
+
+ # Complete with directories from PATH
+ remove|rm|r)
+ compopt -o filenames
+ local -a promptarr
+ IFS=: read -d '' -a promptarr < <(printf '%s\0' "$PATH")
+ local part
+ for part in "${promptarr[@]}" ; do
+ [[ $part == "${COMP_WORDS[COMP_CWORD]}"* ]] \
+ || continue
COMPREPLY=("${COMPREPLY[@]}" "$part")
- fi
- done
- ;;
-
- # No completion
- *)
- return 1
- ;;
- esac
- fi
+ done
+ ;;
+
+ # No completion
+ *)
+ return 1
+ ;;
+ esac
+ ;;
+ esac
}
complete -F _path path
diff --git a/bash/bashrc.d/sd.bash b/bash/bashrc.d/sd.bash
index d4ba49b3..ccc4aec7 100644
--- a/bash/bashrc.d/sd.bash
+++ b/bash/bashrc.d/sd.bash
@@ -55,11 +55,11 @@ sd() {
# Set up local variable for the sibling to which we'll attempt to move,
# assuming we find one
- local dir
+ local dirname
# If we have one argument, it's easy, we just try to move to that one
if (($# == 1)) ; then
- dir=$1
+ dirname=$1
# If no argument, the user is lazy; if there's only one sibling, we'll do
# what they mean and switch to it
@@ -68,7 +68,7 @@ sd() {
# This subshell switches on globbing functions to try to find all the
# current directory's siblings; it exits non-zero if it found anything
# other than one
- if ! dir=$(
+ if ! dirname=$(
shopt -s dotglob extglob nullglob
local -a siblings
@@ -92,7 +92,7 @@ sd() {
# This block is run if the subshell fails due to there not being a
# single sibling
) ; then
- printf 'bash: %s: No single sibling dir\n' \
+ printf 'bash: %s: No single sibling directory\n' \
"$FUNCNAME" >&2
return 1
fi
@@ -105,17 +105,43 @@ sd() {
fi
# Try to change into the determine directory
- builtin cd "${opts[@]}" -- ../"$dir"
+ builtin cd "${opts[@]}" -- ../"$dirname"
}
# Completion function for sd; any sibling directories, excluding the self
_sd() {
- local word curdir
- word=${COMP_WORDS[COMP_CWORD]}
- curdir=${PWD##*/}
+
+ # The completed results are filenames
compopt -o filenames
- local IFS=$'\n'
- COMPREPLY=( $(cd .. && compgen -d -X "$curdir" -- "$word") )
+
+ # Only makes sense for the first argument
+ ((COMP_CWORD == 1)) || return 1
+
+ # Current directory can't be root directory
+ [[ $PWD != / ]] || return 1
+
+ # Build list of matching sibiling directories
+ while IFS= read -d '' -r dirname ; do
+ [[ $dirname == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$dirname")
+ done < <(
+
+ # Set options to glob correctly
+ shopt -s dotglob extglob nullglob
+
+ # Collect directory names, exclude current directory, strip leading ../
+ # and trailing /
+ local -a dirnames
+ dirnames=(../!("${PWD##*/}")/)
+ dirnames=("${dirnames[@]#../}")
+ dirnames=("${dirnames[@]%/}")
+
+ # Bail if no results to prevent empty output
+ ((${#dirnames[@]})) || exit 1
+
+ # Print results, null-delimited
+ printf '%s\0' "${dirnames[@]}"
+ )
}
complete -F _sd sd
diff --git a/bash/bashrc.d/ssh.bash b/bash/bashrc.d/ssh.bash
index ea3c70a6..223aace8 100644
--- a/bash/bashrc.d/ssh.bash
+++ b/bash/bashrc.d/ssh.bash
@@ -1,23 +1,26 @@
# Completion for ssh/sftp/ssh-copy-id with config hostnames
_ssh() {
- local word
- word=${COMP_WORDS[COMP_CWORD]}
+
+ # Use default completion if no matches
+ compopt -o default
# Read hostnames from existent config files, no asterisks
local -a hosts
local config option value
for config in "$HOME"/.ssh/config /etc/ssh/ssh_config ; do
- if [[ -e $config ]] ; then
- while read -r option value _ ; do
- if [[ $option == Host && $value != *'*'* ]] ; then
- hosts=("${hosts[@]}" "$value")
- fi
- done < "$config"
- fi
+ [[ -e $config ]] || continue
+ while read -r option value _ ; do
+ [[ $option == Host ]] || continue
+ [[ $value != *'*'* ]] || continue
+ hosts=("${hosts[@]}" "$value")
+ done < "$config"
done
# Generate completion reply
- COMPREPLY=( $(compgen -W "${hosts[*]}" -- "$word") )
+ for host in "${hosts[@]}" ; do
+ [[ $host == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$host")
+ done
}
-complete -F _ssh -o default ssh sftp ssh-copy-id
+complete -F _ssh ssh sftp ssh-copy-id
diff --git a/bash/bashrc.d/ud.bash b/bash/bashrc.d/ud.bash
index b5279004..7a715a52 100644
--- a/bash/bashrc.d/ud.bash
+++ b/bash/bashrc.d/ud.bash
@@ -32,34 +32,53 @@ ud() {
# Check and save optional second argument, target directory; default to
# $PWD (typical usage case)
- local dir
- dir=${2:-$PWD}
- if [[ ! -e $dir ]] ; then
- printf 'bash: %s: Target dir %s does not exist\n' "$FUNCNAME" "$2" >&2
+ local dirname
+ dirname=${2:-$PWD}
+ if [[ ! -e $dirname ]] ; then
+ printf 'bash: %s: Target directory %s does not exist\n' "$FUNCNAME" "$2" >&2
return 1
fi
# Append /.. to the target the specified number of times
local -i i
for (( i = 0 ; i < steps ; i++ )) ; do
- dir=${dir%/}/..
+ dirname=${dirname%/}/..
done
# Try to change into it
- cd "${opts[@]}" -- "$dir"
+ cd "${opts[@]}" -- "$dirname"
}
-# Completion is only useful for the second argument
+# Completion setup for ud
_ud() {
- if ((COMP_CWORD == 2)) ; then
- local word
- word=${COMP_WORDS[COMP_CWORD]}
- compopt -o filenames
- local IFS=$'\n'
- COMPREPLY=( $(compgen -A directory -- "$word" ) )
- else
- return 1
- fi
+
+ # The completions given are filenames and may require escaping
+ compopt -o filenames
+
+ # Only makes sense for the second argument
+ ((COMP_CWORD == 2)) || return 1
+
+ # Iterate through directories, null-separated, add them to completions
+ local dirname
+ while IFS= read -d '' -r dirname ; do
+ [[ $dirname == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue
+ COMPREPLY=("${COMPREPLY[@]}" "$dirname")
+ done < <(
+
+ # Set options to glob correctly
+ shopt -s dotglob nullglob
+
+ # Collect directory names, strip trailing slashes
+ local -a dirnames
+ dirnames=(*/)
+ dirnames=("${dirnames[@]%/}")
+
+ # Bail if no results to prevent empty output
+ ((${#dirnames[@]})) || exit 1
+
+ # Print results null-delimited
+ printf '%s\0' "${dirnames[@]}"
+ )
}
complete -F _ud ud
diff --git a/bash/bashrc.d/vr.bash b/bash/bashrc.d/vr.bash
index a8d836a3..3eb1fbdd 100644
--- a/bash/bashrc.d/vr.bash
+++ b/bash/bashrc.d/vr.bash
@@ -37,8 +37,8 @@ vr() {
return
fi
- # If we have a .svn dir, iterate upwards until we find an ancestor that
- # doesn't; hopefully that's the root
+ # If we have a .svn directory, iterate upwards until we find an ancestor
+ # that doesn't; hopefully that's the root
if [[ -d $path/.svn ]] ; then
local search
search=$path