From 1e94aa430c0eefce9396e993272309788c6a21d0 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Tue, 23 Aug 2016 14:28:18 +1200 Subject: Fix up ?d() functions Count arguments in right places; return 2 on usage errors where possible; minimise subshell activity; move directory replacement functionality to its own function `rd()` rather than overloading `cd`. --- README.markdown | 5 ++-- sh/shrc.d/bd.sh | 9 ++++--- sh/shrc.d/cd.sh | 76 --------------------------------------------------------- sh/shrc.d/pd.sh | 12 ++++----- sh/shrc.d/rd.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++ sh/shrc.d/sd.sh | 16 ++++++------ sh/shrc.d/ud.sh | 26 +++++++++++--------- 7 files changed, 101 insertions(+), 106 deletions(-) delete mode 100644 sh/shrc.d/cd.sh create mode 100644 sh/shrc.d/rd.sh diff --git a/README.markdown b/README.markdown index ff73a498..b1948e4b 100644 --- a/README.markdown +++ b/README.markdown @@ -167,8 +167,6 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `bc()` silences startup messages from GNU `bc(1)`. * `bd()` changes into a named ancestor of the current directory. * `diff()` forces the unified format for `diff(1)`. -* `cd()` wraps the `cd` builtin to allow for a second parameter for string - substitution, emulating a Zsh function I like. * `ed()` tries to get verbose error messages, a prompt, and a Readline environment for `ed(1)`. * `env()` sorts the output of `env(1)` if it was invoked with no arguments, @@ -192,6 +190,9 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `pd()` changes to the argument's parent directory. * `pwgen()` generates just one decent password with `pwgen(1)`. * `rcsdiff()` forces a unified format for `rcsdiff(1)`. +* `rd()` replaces the first instance of its first argument with its second + argument in `$PWD`, emulating a feature of the Zsh `cd` builtin that I + like. * `scp()` tries to detect forgotten hostnames in `scp(1)` command calls. * `scr()` creates a temporary directory and changes into it. * `sd()` changes into a sibling of the current directory. diff --git a/sh/shrc.d/bd.sh b/sh/shrc.d/bd.sh index a5344ae7..81ddedb8 100644 --- a/sh/shrc.d/bd.sh +++ b/sh/shrc.d/bd.sh @@ -1,13 +1,16 @@ # Move back up the directory tree to the first directory matching the name bd() { + # Check argument count + if [ "$#" -gt 1 ] ; then + printf >&2 'bd(): Too many arguments' + return 2 + fi + # Set positional parameters to an option terminator and what will hopefully # end up being a target directory set -- "$( - # Check there's no more than one argument - [ "$#" -le 1 ] || exit 1 - # The requested pattern is the first argument, defaulting to just the # parent directory req=${1:-..} diff --git a/sh/shrc.d/cd.sh b/sh/shrc.d/cd.sh deleted file mode 100644 index 7bfacd6d..00000000 --- a/sh/shrc.d/cd.sh +++ /dev/null @@ -1,76 +0,0 @@ -# If given two arguments, replace the first instance of the first argument with -# the second argument in $PWD, and make that the target of cd(). This POSIX -# version cannot handle options, but it can handle an option terminator (--), -# so e.g. `cd -- -foo -bar` should work. -cd() { - - # First check to see if we can perform the substitution at all; otherwise, - # we won't be changing any parameters - if ( - - # If we have any options, we can't do it, because POSIX shell doesn't - # let us (cleanly) save the list of options for use later in the script - for arg ; do - case $arg in - --) break ;; - -*) opts=1 ; shift ;; - esac - done - - # Shift off -- if it's the first argument - [ "$1" = -- ] && shift - - # Print an explanatory error if there were options and then two - # arguments - if [ "$#" -eq 2 ] && [ -n "$opts" ] ; then - printf >&2 'cd(): Can'\''t combine options and substitution\n' - fi - - # Check we have no options and two non-null arguments - [ -z "$opts" ] || return - [ "$#" -eq 2 ] || return - [ -n "$1" ] || return - [ -n "$2" ] || return - - ) ; then - - # Set the positional parameters to an option terminator and what will - # hopefully end up being the substituted directory name - set -- -- "$( - - # If the first of the existing positional arguments is --, shift it - # off - [ "$1" = -- ] && shift - - # Current path: e.g. /foo/ayy/bar/ayy - cur=$PWD - # Pattern to replace: e.g. ayy - pat=$1 - # Text with which to replace pattern: e.g. lmao - rep=$2 - - # /foo/ - curtc=${cur%%"$pat"*} - # /bar/ayy - curlc=${cur#*"$pat"} - # /foo/lmao/bar/ayy - new=${curtc}${rep}${curlc} - - # Check that a substitution resulted in an actual change and that - # we ended up with a non-null target, or print an error to stderr - if [ "$cur" = "$curtc" ] || [ -z "$new" ]; then - printf >&2 'cd(): Substitution failed\n' - exit 1 - fi - - # Print the target - printf '%s\n' "$new" - )" - - # If the subshell printed nothing, return with failure - [ -n "$2" ] || return - fi - - # Execute the cd command as normal - command cd "$@" -} diff --git a/sh/shrc.d/pd.sh b/sh/shrc.d/pd.sh index c022b1e8..de4ea23b 100644 --- a/sh/shrc.d/pd.sh +++ b/sh/shrc.d/pd.sh @@ -4,16 +4,16 @@ # argument, this just shifts up a directory, i.e. `cd ..` pd() { + # Check argument count + if [ "$#" -gt 1 ] ; then + printf >&2 'pd(): Too many arguments\n' + return 2 + fi + # Change the positional parameters from the target to its containing # directory set -- "$( - # Check argument count - if [ "$#" -gt 1 ] ; then - printf >&2 'pd(): Too many arguments\n' - exit 2 - fi - # Figure out target dirname dirname=${1:-..} dirname=${dirname%/} diff --git a/sh/shrc.d/rd.sh b/sh/shrc.d/rd.sh new file mode 100644 index 00000000..9fd99a55 --- /dev/null +++ b/sh/shrc.d/rd.sh @@ -0,0 +1,63 @@ +# +# Replace the first instance of the first argument string with the second +# argument string in $PWD, and make that the target of the cd builtin. This is +# to emulate a feature of the `cd` builtin in Zsh that I like, but that I think +# should be a separate command rather than overloading `cd`. +# +# $ pwd +# /usr/local/bin +# $ rd local +# $ pwd +# /usr/bin +# $ rd usr opt +# $ pwd +# /opt/bin +# +rd() { + + # Check argument count + case $# in + 1|2) ;; + *) + printf >&2 \ + 'rd(): Need a string and optionally a replacement\n' + return 2 + ;; + esac + + # Set the positional parameters to an option terminator and what will + # hopefully end up being the substituted directory name + set -- "$( + + # Current path: e.g. /foo/ayy/bar/ayy + cur=$PWD + # Pattern to replace: e.g. ayy + pat=$1 + # Text with which to replace pattern: e.g. lmao + # This can be a null string or unset, in order to *remove* the pattern + rep=$2 + + # /foo/ + curtc=${cur%%"$pat"*} + # /bar/ayy + curlc=${cur#*"$pat"} + # /foo/lmao/bar/ayy + new=${curtc}${rep}${curlc} + + # Check that a substitution resulted in an actual change and that we + # ended up with a non-null target, or print an error to stderr + if [ "$cur" = "$curtc" ] || [ -z "$new" ] ; then + printf >&2 'rd(): Substitution failed\n' + exit 1 + fi + + # Print the target + printf '%s\n' "$new" + )" + + # If the subshell printed nothing, return with failure + [ -n "$1" ] || return + + # Try to change into the determined directory + command cd -- "$@" +} diff --git a/sh/shrc.d/sd.sh b/sh/shrc.d/sd.sh index 80ae7c12..c5b1106a 100644 --- a/sh/shrc.d/sd.sh +++ b/sh/shrc.d/sd.sh @@ -1,6 +1,6 @@ # -# sd -- sibling/switch directory -- Shortcut to switch to another directory -# with the same parent, i.e. a sibling of the current directory. +# Shortcut to switch to another directory with the same parent, i.e. a sibling +# of the current directory. # # $ pwd # /home/you @@ -34,14 +34,14 @@ # sd() { + # Check argument count + if [ "$#" -gt 1 ] ; then + printf >&2 'sd(): Too many arguments\n' + return 2 + fi + set -- "$( - # Check argument count - if [ "$#" -gt 1 ] ; then - printf >&2 'sd(): Too many arguments\n' - exit 1 - fi - # Set the positional parameters to either the requested directory, or # all siblings of the current directory if no request spec=$1 diff --git a/sh/shrc.d/ud.sh b/sh/shrc.d/ud.sh index 259f3167..44a3a81d 100644 --- a/sh/shrc.d/ud.sh +++ b/sh/shrc.d/ud.sh @@ -2,23 +2,27 @@ # like cd .., cd ../.., etc ud() { + # Check argument count + if [ "$#" -gt 1 ] ; then + printf >&2 'ud(): Too many arguments\n' + return 2 + fi + + # Check first argument, number of steps upward, default to 1. + # "0" is weird, but valid; "-1" however makes no sense at all + if [ "${1:-1}" -lt 0 ] ; then + printf >&2 'ud(): Invalid step count\n' + return 2 + fi + # Change the positional parameters from the number of steps given to a # "../../.." string set -- "$( - # Check first argument, number of steps upward, default to 1 - # "0" is weird, but valid; "-1" however makes no sense at all - steps=${1:-1} - if [ "$steps" -lt 0 ] ; then - printf >&2 'ud(): Invalid step count\n' - exit 2 - fi - - # Check second argument, target directory, default to $PWD + # Append /.. to the target (default PWD) the specified number of times dirname=${2:-"$PWD"} - - # Append /.. to the target the specified number of times i=0 + steps=${1:-1} while [ "$i" -lt "$steps" ] ; do dirname=${dirname%/}/.. i=$((i+1)) -- cgit v1.2.3