diff options
-rw-r--r-- | README.markdown | 1 | ||||
-rw-r--r-- | bash/bashrc.d/sd.bash | 119 |
2 files changed, 120 insertions, 0 deletions
diff --git a/README.markdown b/README.markdown index 9bfe62d1..bfcf4f73 100644 --- a/README.markdown +++ b/README.markdown @@ -190,6 +190,7 @@ There are a few other little tricks in `bash/bashrc.d`, including: * `pd` — Change to the argument’s parent directory * `readz` — Alias for `read -d '' -r` * `scr` — Create a temporary directory and change into it +* `sd` — Switch to a sibling directory * `sprunge` — Pastebin frontend tool I pilfered from `#bash` on Freenode * `ud` — Change into an indexed ancestor of a directory diff --git a/bash/bashrc.d/sd.bash b/bash/bashrc.d/sd.bash new file mode 100644 index 00000000..77a48443 --- /dev/null +++ b/bash/bashrc.d/sd.bash @@ -0,0 +1,119 @@ +# +# sd -- sibling/switch directory -- Shortcut to switch to another directory +# with the same parent, i.e. a sibling of the current directory. +# +# $ pwd +# /home/you +# $ sd friend +# $ pwd +# /home/friend +# $ sd you +# $ pwd +# /home/you +# +# If no arguments are given and there's only one other sibling, switch to that; +# nice way to quickly toggle between two siblings. +# +# $ cd -- "(mktemp -d)" +# $ pwd +# /tmp/tmp.ZSunna5Eup +# $ mkdir a b +# $ ls +# a b +# $ cd a +# pwd +# /tmp/tmp.ZSunna5Eup/a +# $ sd +# $ pwd +# /tmp/tmp.ZSunna5Eup/b +# $ sd +# $ pwd +# /tmp/tmp.ZSunna5Eup/a +# +# Seems to work for symbolic links. Completion included. +# +sd() { + + # For completeness' sake, we'll pass any options to cd + local arg + local -a opts + for arg ; do + case $arg in + --) + shift + break + ;; + -*) + shift + opts=("${opts[@]}" "$arg") + ;; + *) + break + ;; + esac + done + + # Set up local variable for the sibling to which we'll attempt to move, + # assuming we find one + local dir + + # If we have one argument, it's easy, we just try to move to that one + if (($# == 1)) ; then + dir=$1 + + # If no argument, the user is lazy; if there's only one sibling, we'll do + # what they mean and switch to it + elif (($# == 0)) ; then + + # 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=$( + shopt -s dotglob extglob nullglob + local -a siblings + + # Generate relative paths of all siblings + siblings=(../!("${PWD##*/}")/) + + # Strip the trailing slash + siblings=("${siblings[@]%/}") + + # Strip everything up to the basename + siblings=("${siblings[@]##*/}") + + # If some number of siblings besides one, exit non-zero + if ((${#siblings[@]} != 1)) ; then + exit 1 + fi + + # Otherwise, just print it + printf %s "${siblings[0]}" + + # 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' \ + "$FUNCNAME" >&2 + return 1 + fi + + # Any other number of arguments is a usage error; say so + else + printf 'bash: %s: usage: %s [DIR]\n' \ + "$FUNCNAME" "$FUNCNAME" >&2 + return 2 + fi + + # Try to change into the determine directory + builtin cd "${opts[@]}" -- ../"$dir" +} + +# Completion function for sd; any sibling directories, excluding the self +_sd() { + local word curdir + word=${COMP_WORDS[COMP_CWORD]} + curdir=${PWD##*/} + COMPREPLY=( $(cd .. && compgen -d -X "$curdir" -- "$word") ) +} +complete -F _sd sd + |