From c4c982226a45f5360fb21490491d20ba6cd5dd3a Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Fri, 19 Aug 2016 23:26:32 +1200 Subject: Remove debugging code from eds(1) --- bin/eds | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/eds b/bin/eds index 3d0fceed..e39215d4 100755 --- a/bin/eds +++ b/bin/eds @@ -42,8 +42,7 @@ for arg ; do done # Run the editor over the arguments -echo "${VISUAL:-"${EDITOR:-ed}"}" "$@" -exit +"${VISUAL:-"${EDITOR:-ed}"}" "$@" # Make any created scripts executable if they now appear to be files for script ; do -- cgit v1.2.3 From 88e4b992d90f2a7283058a1a93c388a9576bad5b Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Fri, 19 Aug 2016 23:58:25 +1200 Subject: Move OLDPWD setting to POSIX sh dir With an attempt at correct trapping; may still require tweaking --- Makefile | 1 + bash/bash_logout | 3 --- sh/logout | 2 ++ sh/profile | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 sh/logout diff --git a/Makefile b/Makefile index 5d8369c9..433a9f43 100644 --- a/Makefile +++ b/Makefile @@ -267,6 +267,7 @@ install-sh : check-sh install -m 0755 -d -- \ "$(HOME)"/.profile.d \ "$(HOME)"/.shrc.d + install -pm 0644 -- sh/logout "$(HOME)"/.logout install -pm 0644 -- sh/profile "$(HOME)"/.profile install -pm 0644 -- sh/profile.d/* "$(HOME)"/.profile.d install -pm 0644 -- sh/shrc "$(HOME)"/.shrc diff --git a/bash/bash_logout b/bash/bash_logout index e8137e55..afb088b8 100644 --- a/bash/bash_logout +++ b/bash/bash_logout @@ -12,6 +12,3 @@ fi if ((SHLVL == 1)) ; then clear_console -q 2>/dev/null fi - -# Write PWD to a file if set -printf '%s\n' "$PWD" > "${OLDPWD_FILE:-$HOME/.oldpwd}" diff --git a/sh/logout b/sh/logout new file mode 100644 index 00000000..4da61192 --- /dev/null +++ b/sh/logout @@ -0,0 +1,2 @@ +# Write PWD to a file if set +printf '%s\n' "$PWD" >"${OLDPWD_FILE:-"$HOME"/.oldpwd}" diff --git a/sh/profile b/sh/profile index 5dfe0ef3..7fea250b 100644 --- a/sh/profile +++ b/sh/profile @@ -9,6 +9,21 @@ for sh in "$HOME"/.profile.d/*.sh ; do done unset -v sh +# Trap on exit to run ~/.logout if it exists +logout_trap() { + if [ -f "$HOME"/.logout ] ; then + . "$HOME"/.logout + fi + if [ "$1" != EXIT ] ; then + trap - "$1" + kill "-$1" "$$" + fi +} +for sig in EXIT HUP INT TERM ; do + trap "logout_trap $sig" "$sig" +done +unset -v sig + # If ENV is unset after running those scripts and ~/.shrc exists, set it as ENV if [ -z "$ENV" ] && [ -f "$HOME"/.shrc ] ; then ENV=$HOME/.shrc -- cgit v1.2.3 From 554587c78c460b0401b84bba62db40866dbe42c1 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 00:10:32 +1200 Subject: Use terser shell for executable installs --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 433a9f43..4d35779f 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ install-bash-completion : install-bash install-bin : bin/sd2u bin/su2d bin/unf check-bin install-bin-man install -m 0755 -d -- "$(HOME)"/.local/bin for name in bin/* ; do \ - [ -x "$$name" ] || continue ; \ + [ -x "$$name" ] && \ install -m 0755 -- "$$name" "$(HOME)"/.local/bin ; \ done @@ -187,7 +187,7 @@ install-finger : install-games : games/acq games/kvlt games/zs check-games install-games-man install -m 0755 -d -- "$(HOME)"/.local/games for name in games/* ; do \ - [ -x "$$name" ] || continue ; \ + [ -x "$$name" ] && \ install -m 0755 -- "$$name" "$(HOME)"/.local/games ; \ done -- cgit v1.2.3 From 1ce0e2ad424bf38768094488774eff23b3871ccc Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 00:43:35 +1200 Subject: Port all bash_profile.d scripts to POSIX sh Also require flag files in ~/.welcome for displaying or not displaying login stuff --- Makefile | 4 +--- README.markdown | 9 ++++----- bash/bash_profile | 6 ------ bash/bash_profile.d/fortune.bash | 19 ------------------- bash/bash_profile.d/remind.bash | 22 ---------------------- bash/bash_profile.d/verse.bash | 24 ------------------------ check/bash | 2 +- sh/profile | 1 + sh/profile.d/fortune.sh | 24 ++++++++++++++++++++++++ sh/profile.d/remind.sh | 19 +++++++++++++++++++ sh/profile.d/verse.sh | 28 ++++++++++++++++++++++++++++ 11 files changed, 78 insertions(+), 80 deletions(-) delete mode 100644 bash/bash_profile.d/fortune.bash delete mode 100644 bash/bash_profile.d/remind.bash delete mode 100644 bash/bash_profile.d/verse.bash create mode 100644 sh/profile.d/fortune.sh create mode 100644 sh/profile.d/remind.sh create mode 100644 sh/profile.d/verse.sh diff --git a/Makefile b/Makefile index 4d35779f..a7efba43 100644 --- a/Makefile +++ b/Makefile @@ -141,12 +141,10 @@ install-abook : install-bash : check-bash install-sh install -m 0755 -d -- \ "$(HOME)"/.config \ - "$(HOME)"/.bashrc.d \ - "$(HOME)"/.bash_profile.d + "$(HOME)"/.bashrc.d install -pm 0644 -- bash/bashrc "$(HOME)"/.bashrc install -pm 0644 -- bash/bashrc.d/* "$(HOME)"/.bashrc.d install -pm 0644 -- bash/bash_profile "$(HOME)"/.bash_profile - install -pm 0644 -- bash/bash_profile.d/* "$(HOME)"/.bash_profile.d install -pm 0644 -- bash/bash_logout "$(HOME)"/.bash_logout install-bash-completion : install-bash diff --git a/README.markdown b/README.markdown index 47197122..d6ace0d3 100644 --- a/README.markdown +++ b/README.markdown @@ -98,11 +98,10 @@ they should work in most `sh(1)` implementations. Individual scripts called by management. All of these boil down to exporting variables appropriate to the system and the software it has available. -My `.bash_profile` calls `.profile` and then runs subscripts in -`.bash_profile.d`. It then runs `.bashrc`, which only applies for interactive -shells; subscripts for that in turn are loaded from `.bashrc.d`. The contents -of the two directories changes depending on the host, so only specific scripts -in it are versioned. +My `.bash_profile` calls `.profile`, and then `.bashrc`, which only applies for +interactive shells. Subscripts for `.bashrc` are loaded from `.bashrc.d`. The +contents of the two directories changes depending on the host, so only specific +scripts in it are versioned. My interactive and scripting shell of choice is Bash; as a GNU/Linux admin who ends up installing Bash on \*BSD machines anyway, I very rarely have to write diff --git a/bash/bash_profile b/bash/bash_profile index db1d9bc8..69350102 100644 --- a/bash/bash_profile +++ b/bash/bash_profile @@ -13,12 +13,6 @@ elif ((BASH_VERSINFO[0] == 2)) && return fi -# Load any supplementary scripts -for sh in "$HOME"/.bash_profile.d/*.bash ; do - [[ -e $sh ]] && source "$sh" -done -unset -v sh - # If ~/.bashrc exists, source that too; the test for interactivity is in there if [[ -f $HOME/.bashrc ]] ; then source "$HOME"/.bashrc diff --git a/bash/bash_profile.d/fortune.bash b/bash/bash_profile.d/fortune.bash deleted file mode 100644 index cc16ff05..00000000 --- a/bash/bash_profile.d/fortune.bash +++ /dev/null @@ -1,19 +0,0 @@ -# Only if shell is interactive -if [[ $- != *i* ]] ; then - return -fi - -# Only if fortune(6) available -if ! hash fortune 2>/dev/null ; then - return -fi - -# Print from subshell to keep namespace clean -( - if [[ -d $HOME/.local/share/games/fortunes ]] ; then - FORTUNE_PATH=${FORTUNE_PATH:-$HOME/.local/share/games/fortunes} - fi - printf '\n' - fortune -s "$FORTUNE_PATH" - printf '\n' -) diff --git a/bash/bash_profile.d/remind.bash b/bash/bash_profile.d/remind.bash deleted file mode 100644 index a8f14599..00000000 --- a/bash/bash_profile.d/remind.bash +++ /dev/null @@ -1,22 +0,0 @@ -# Only if shell is interactive -if [[ $- != *i* ]] ; then - return -fi - -# Only if rem(1) available -if ! hash rem 2>/dev/null ; then - return -fi - -# Only if reminders file exists -if [[ ! -e ${DOTREMINDERS:-$HOME/.reminders} ]] ; then - return -fi - -# Print from subshell to keep namespace clean -( - while IFS= read -r reminder ; do - printf '* %s\n' "$reminder" - done < <(rem -hq) - printf '\n' -) diff --git a/bash/bash_profile.d/verse.bash b/bash/bash_profile.d/verse.bash deleted file mode 100644 index 69c48021..00000000 --- a/bash/bash_profile.d/verse.bash +++ /dev/null @@ -1,24 +0,0 @@ -# Only if shell is interactive -if [[ $- != *i* ]] ; then - return -fi - -# Only if verse(1) available -if ! hash verse 2>/dev/null ; then - return -fi - -# Run verse(1) if we haven't seen it already today (the verses are selected by -# date); run in a subshell to keep vars out of global namespace -( - date=$(date +%Y-%m-%d) - versefile=${VERSEFILE:-$HOME/.verse} - if [[ -e $versefile ]] ; then - IFS= read -r lastversedate < "$versefile" - fi - if [[ $date > $lastversedate ]] ; then - verse - printf '\n' - printf '%s\n' "$date" > "$versefile" - fi -) diff --git a/check/bash b/check/bash index f203c2c1..525bec34 100755 --- a/check/bash +++ b/check/bash @@ -1,5 +1,5 @@ #!/bin/sh -for bash in bash/* bash/bashrc.d/* bash/bash_profile.d/* ; do +for bash in bash/* bash/bashrc.d/* ; do [ -f "$bash" ] || continue bash -n "$bash" || exit done diff --git a/sh/profile b/sh/profile index 7fea250b..7266d64e 100644 --- a/sh/profile +++ b/sh/profile @@ -20,6 +20,7 @@ logout_trap() { fi } for sig in EXIT HUP INT TERM ; do + # shellcheck disable=SC2064 trap "logout_trap $sig" "$sig" done unset -v sig diff --git a/sh/profile.d/fortune.sh b/sh/profile.d/fortune.sh new file mode 100644 index 00000000..a5894108 --- /dev/null +++ b/sh/profile.d/fortune.sh @@ -0,0 +1,24 @@ +# Only if shell is interactive +case $- in + *i*) ;; + *) return ;; +esac + +# Only if not in a tmux window +[ -z "$TMUX" ] || return + +# Only if ~/.welcome/fortune exists and ~/.hushlogin doesn't +[ -e "$HOME"/.welcome/fortune ] || return +! [ -e "$HOME"/.hushlogin ] || return + +# Only if fortune(6) available +command -v fortune >/dev/null 2>&1 || return + +# Print from subshell to keep namespace clean +( + if [ -d "$HOME"/.local/share/games/fortunes ] ; then + : "${FORTUNE_PATH:="$HOME"/.local/share/games/fortunes}" + fi + fortune -s "$FORTUNE_PATH" + printf '\n' +) diff --git a/sh/profile.d/remind.sh b/sh/profile.d/remind.sh new file mode 100644 index 00000000..3ef0f353 --- /dev/null +++ b/sh/profile.d/remind.sh @@ -0,0 +1,19 @@ +# Only if shell is interactive +case $- in + *i*) ;; + *) return ;; +esac + +# Only if not in a tmux window +[ -z "$TMUX" ] || return + +# Only if ~/.welcome/remind exists and ~/.hushlogin doesn't +[ -e "$HOME"/.welcome/remind ] || return +! [ -e "$HOME"/.hushlogin ] || return + +# Only if rem(1) available +command -v rem >/dev/null 2>&1 || return + +# Print reminders with asterisks +rem -hq | sed 's/^/* /' +printf '\n' diff --git a/sh/profile.d/verse.sh b/sh/profile.d/verse.sh new file mode 100644 index 00000000..781d68bc --- /dev/null +++ b/sh/profile.d/verse.sh @@ -0,0 +1,28 @@ +# Only if shell is interactive +case $- in + *i*) ;; + *) return ;; +esac + +# Only if not in a tmux window on this machine +[ -z "$TMUX" ] || return + +# Only if ~/.welcome/verse exists and ~/.hushlogin doesn't +[ -e "$HOME"/.welcome/verse ] || return +! [ -e "$HOME"/.hushlogin ] || return + +# Only if verse(1) available +command -v verse >/dev/null 2>&1 || return + +# Run verse(1) if we haven't seen it already today (the verses are selected by +# date); run in a subshell to keep vars out of global namespace +( + now=$(date +%Y-%m-%d) + if [ -f "$HOME"/.verse ] ; then + last=$(cat -- "$HOME"/.verse) + fi + [ "$now" \> "$last" ] || exit + verse + printf '\n' + printf '%s\n' "$now" > "$HOME"/.verse +) -- cgit v1.2.3 From b45805dc46ff9cc1d94588f88c68b000ee95b0b8 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 01:21:19 +1200 Subject: Pass null arg to manual cleanup() call in apf(1) --- bin/apf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/apf b/bin/apf index 956142fe..17a30be9 100755 --- a/bin/apf +++ b/bin/apf @@ -48,7 +48,7 @@ if [ -f "$argf" ] ; then done < "$revf" # We can remove the temporary stuff now, which allows us to exec safely - cleanup + cleanup '' fi # Run the command with the changed arguments -- cgit v1.2.3 From 90c21a27bb648a3c592cc7d2ea18a944177c8f83 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 01:26:38 +1200 Subject: Move oldpwd.sh functionality into profile --- sh/profile | 16 +++++++++++----- sh/profile.d/oldpwd.sh | 5 ----- 2 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 sh/profile.d/oldpwd.sh diff --git a/sh/profile b/sh/profile index 7266d64e..b2889295 100644 --- a/sh/profile +++ b/sh/profile @@ -3,11 +3,11 @@ if [ -d "$HOME"/.local/bin ] ; then PATH=$HOME/.local/bin:$PATH fi -# Load all supplementary scripts in ~/.profile.d -for sh in "$HOME"/.profile.d/*.sh ; do - [ -e "$sh" ] && . "$sh" -done -unset -v sh +# If we can read ~/.oldpwd, make its contents our OLDPWD +if [ -r "$HOME"/.oldpwd ] ; then + IFS= read -r OLDPWD < "$HOME"/.oldpwd + export OLDPWD +fi # Trap on exit to run ~/.logout if it exists logout_trap() { @@ -25,6 +25,12 @@ for sig in EXIT HUP INT TERM ; do done unset -v sig +# Load all supplementary scripts in ~/.profile.d +for sh in "$HOME"/.profile.d/*.sh ; do + [ -e "$sh" ] && . "$sh" +done +unset -v sh + # If ENV is unset after running those scripts and ~/.shrc exists, set it as ENV if [ -z "$ENV" ] && [ -f "$HOME"/.shrc ] ; then ENV=$HOME/.shrc diff --git a/sh/profile.d/oldpwd.sh b/sh/profile.d/oldpwd.sh deleted file mode 100644 index 91dd1dba..00000000 --- a/sh/profile.d/oldpwd.sh +++ /dev/null @@ -1,5 +0,0 @@ -# If we can read ~/.oldpwd, make its contents our OLDPWD -if [ -r "${OLDPWD_FILE:-$HOME/.oldpwd}" ] ; then - IFS= read -r OLDPWD < "${OLDPWD_FILE:-$HOME/.oldpwd}" - export OLDPWD -fi -- cgit v1.2.3 From f74838ab32c5dd12b534d1555a31128e7ed08e0c Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 02:17:29 +1200 Subject: Keep SSH_TTY updated in tmux panes --- tmux/tmux.conf.m4 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tmux/tmux.conf.m4 b/tmux/tmux.conf.m4 index ac19ebe3..f7fc1e8e 100644 --- a/tmux/tmux.conf.m4 +++ b/tmux/tmux.conf.m4 @@ -6,13 +6,11 @@ set-environment -gru DISPLAY # Force the browser to be Lynx in case we inherited a non-null DISPLAY set-environment -g BROWSER 'lynx' -# The only environment variables I want tmux to update for me are SSH_CLIENT -# and SSH_CONNECTION, both of which are occasionally useful -set-option -g update-environment 'SSH_CLIENT SSH_CONNECTION' +# The only environment variables I want tmux to update for me are SSH_CLIENT, +# SSH_CONNECTION, and SSH_TTY, all of which are occasionally useful +set-option -g update-environment 'SSH_CLIENT SSH_CONNECTION SSH_TTY' -# Setting this prevents each new pane being a login shell, purely for -# efficiency reasons; I've not yet encountered a situation where I need tmux to -# create login shells +# Setting this makes each new pane a non-login shell, which suits me better set-option -g default-command "$SHELL" # All of my terminals are 256 colors, so use the appropriate termcap/terminfo, -- cgit v1.2.3 From 2200d95ff52279f895e8f2a838d7cfa78b97742b Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 02:25:21 +1200 Subject: Add sshi(1) --- README.markdown | 1 + bin/sshi | 28 ++++++++++++++++++++++++++++ man/man1/sshi.1 | 15 +++++++++++++++ 3 files changed, 44 insertions(+) create mode 100755 bin/sshi create mode 100644 man/man1/sshi.1 diff --git a/README.markdown b/README.markdown index d6ace0d3..3bf1e604 100644 --- a/README.markdown +++ b/README.markdown @@ -426,6 +426,7 @@ Installed by the `install-bin` target: * `rmrej(1)` deletes rejected hunks from a failed `patch(1)` run. * `shb(1)` attempts to build shebang lines for scripts from `$PATH`. * `spr(1)` posts its input to the sprunge.us pastebin. +* `sshi(1)` prints human-readable SSH connection details. * `stex(1)` strips extensions from filenames. * `sue(8)` execs `sudoedit(8)` as the owner of all the file arguments given, perhaps in cases where you may not necessarily have `root` `sudo(8)` diff --git a/bin/sshi b/bin/sshi new file mode 100755 index 00000000..2f11a61d --- /dev/null +++ b/bin/sshi @@ -0,0 +1,28 @@ +#!/bin/sh +# Print some human-readable information from SSH_CONNECTION + +# Check we have an SSH_CONNECTION variable +if [ -z "$SSH_CONNECTION" ] ; then + printf >&2 'sshi: SSH_CONNECTION appears empty\n' + exit 2 +fi + +# Print the two variables into a subshell so we can chop them up with read +printf '%s\n' "$SSH_CONNECTION" "${SSH_TTY:-unknown}" | ( + + # Read connection details from first line + read -r ci cp si sp + + # Read TTY from second line + read -r tty + + # Try to resolve the client and server IPs + ch=$(dig -x "$ci" +short 2>/dev/null | sed 1q) + sh=$(dig -x "$si" +short 2>/dev/null | sed 1q) + + # Print the results in a human-readable format + printf "%s:%u -> %s:%u (%s)\n" \ + "${ch:-"$ci"}" "$cp" \ + "${sh:-"$si"}" "$sp" \ + "$tty" +) diff --git a/man/man1/sshi.1 b/man/man1/sshi.1 new file mode 100644 index 00000000..8f9ee41d --- /dev/null +++ b/man/man1/sshi.1 @@ -0,0 +1,15 @@ +.TH SSHI 1 "August 2016" "Manual page for sshi" +.SH NAME +.B sshi +\- show human-readable details of the current SSH connection +.SH SYNOPSIS +.B sshi +.SH DESCRIPTION +.B sshi +interprets the contents of the SSH_CONNECTION variable available in the +environment of SSH client sessions and presents it in a slightly more +human-readable format, including an attempt to resolve the IP addresses. +.SH SEE ALSO +ssh(1) +.SH AUTHOR +Tom Ryder -- cgit v1.2.3 From c99b0ebaa22e6532b6d8a0b09a99db51855a8190 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 02:52:49 +1200 Subject: Remove OLDPWD hack It doesn't really make sense anyway --- Makefile | 1 - sh/logout | 2 -- sh/profile | 22 ---------------------- 3 files changed, 25 deletions(-) delete mode 100644 sh/logout diff --git a/Makefile b/Makefile index a7efba43..49304591 100644 --- a/Makefile +++ b/Makefile @@ -265,7 +265,6 @@ install-sh : check-sh install -m 0755 -d -- \ "$(HOME)"/.profile.d \ "$(HOME)"/.shrc.d - install -pm 0644 -- sh/logout "$(HOME)"/.logout install -pm 0644 -- sh/profile "$(HOME)"/.profile install -pm 0644 -- sh/profile.d/* "$(HOME)"/.profile.d install -pm 0644 -- sh/shrc "$(HOME)"/.shrc diff --git a/sh/logout b/sh/logout deleted file mode 100644 index 4da61192..00000000 --- a/sh/logout +++ /dev/null @@ -1,2 +0,0 @@ -# Write PWD to a file if set -printf '%s\n' "$PWD" >"${OLDPWD_FILE:-"$HOME"/.oldpwd}" diff --git a/sh/profile b/sh/profile index b2889295..5dfe0ef3 100644 --- a/sh/profile +++ b/sh/profile @@ -3,28 +3,6 @@ if [ -d "$HOME"/.local/bin ] ; then PATH=$HOME/.local/bin:$PATH fi -# If we can read ~/.oldpwd, make its contents our OLDPWD -if [ -r "$HOME"/.oldpwd ] ; then - IFS= read -r OLDPWD < "$HOME"/.oldpwd - export OLDPWD -fi - -# Trap on exit to run ~/.logout if it exists -logout_trap() { - if [ -f "$HOME"/.logout ] ; then - . "$HOME"/.logout - fi - if [ "$1" != EXIT ] ; then - trap - "$1" - kill "-$1" "$$" - fi -} -for sig in EXIT HUP INT TERM ; do - # shellcheck disable=SC2064 - trap "logout_trap $sig" "$sig" -done -unset -v sig - # Load all supplementary scripts in ~/.profile.d for sh in "$HOME"/.profile.d/*.sh ; do [ -e "$sh" ] && . "$sh" -- cgit v1.2.3 From 304f4afceef0d421f2bba1ab9acff98fc83b000d Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 12:08:31 +1200 Subject: Port bd() to POSIX sh --- bash/bashrc.d/bd.bash | 78 --------------------------------------------------- sh/shrc.d/bd.sh | 67 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 78 deletions(-) delete mode 100644 bash/bashrc.d/bd.bash create mode 100644 sh/shrc.d/bd.sh diff --git a/bash/bashrc.d/bd.bash b/bash/bashrc.d/bd.bash deleted file mode 100644 index 23a2d380..00000000 --- a/bash/bashrc.d/bd.bash +++ /dev/null @@ -1,78 +0,0 @@ -# Move back up the directory tree to the first directory matching the name -bd() { - - # 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 - - # We should have zero or one arguments after all that, bail if there are - # more - if (($# > 1)) ; then - printf 'bash: %s: usage: %s [PATH]\n' \ - "$FUNCNAME" "$FUNCNAME" >&2 - return 2 - fi - - # The requested pattern is the first argument; strip trailing slashes if - # there are any - local req=$1 - [[ $req != / ]] || req=${req%/} - - # What to do now depends on the request - local dirname - case $req in - - # If no argument at all, just go up one level - '') - dirname=.. - ;; - - # Just go straight to the root or dot directories if asked - /|.|..) - dirname=$req - ;; - - # Anything else with a leading / needs to anchor to the start of the - # path - /*) - dirname=$req - if [[ $PWD != "$dirname"/* ]] ; then - printf 'bash: %s: Directory name not in path\n' \ - "$FUNCNAME" >&2 - return 1 - fi - ;; - - # In all other cases, iterate through the directory tree to find a - # match, or whittle the dirname down to an empty string trying - *) - dirname=${PWD%/*} - while [[ -n $dirname && $dirname != */"$req" ]] ; do - dirname=${dirname%/*} - done - if [[ -z $dirname ]] ; then - printf 'bash: %s: Directory name not in path\n' \ - "$FUNCNAME" >&2 - return 1 - fi - ;; - esac - - # Try to change into the determined directory - builtin cd "${opts[@]}" -- "$dirname" -} diff --git a/sh/shrc.d/bd.sh b/sh/shrc.d/bd.sh new file mode 100644 index 00000000..0d1abbcf --- /dev/null +++ b/sh/shrc.d/bd.sh @@ -0,0 +1,67 @@ +# Move back up the directory tree to the first directory matching the name +bd() { + + # Set positional parameters to an option terminator and what will hopefully + # end up being a target directory + set -- -- "$( + + # If the first of the existing positional arguments is --, shift it + # off + [ "$1" = -- ] && shift + + # There's no more than one argument after that + [ "$#" -le 1 ] || exit 1 + + # The requested pattern is the first argument, defaulting to just the + # parent directory + req=${1:-..} + + # Strip trailing slashes if a trailing slash isn't the whole pattern + [ "$req" = / ] || req=${req%/} + + # What to do now depends on the request + case $req in + + # Just go straight to the root or dot directories if asked + /|.|..) + dirname=$req + ;; + + # Anything with a leading / needs to anchor to the start of the + # path. A strange request though. Why not just use cd? + /*) + dirname=$req + case $PWD in + "$dirname"/*) ;; + *) dirname='' ;; + esac + ;; + + # In all other cases, iterate through the PWD to find a match, or + # whittle the target down to an empty string trying + *) + dirname=$PWD + while [ -n "$dirname" ] ; do + dirname=${dirname%/*} + case $dirname in + */"$req") break ;; + esac + done + ;; + esac + + # Check we have a target after all that + if [ -z "$dirname" ] ; then + printf >&2 'bd(): Directory name not in path\n' + exit 1 + fi + + # Print the target + printf '%s\n' "$dirname" + + # If the subshell failed, return from the function with the same exit value + )" || return + + # Try to change into the determined directory + command cd "$@" +} -- cgit v1.2.3 From 5bea26067960815ecd462d3ee8f94379d13f38a5 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 12:08:50 +1200 Subject: Improve error handling of cd() a bit --- sh/shrc.d/cd.sh | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/sh/shrc.d/cd.sh b/sh/shrc.d/cd.sh index dd98a422..37574f78 100644 --- a/sh/shrc.d/cd.sh +++ b/sh/shrc.d/cd.sh @@ -4,7 +4,8 @@ # so e.g. `cd -- -foo -bar` should work. cd() { - # First check to see if we can perform the substitution at all + # 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 @@ -12,14 +13,21 @@ cd() { for arg ; do case $arg in --) break ;; - -*) return 1 ;; + -*) opts=1 ; shift ;; esac done # Shift off -- if it's the first argument [ "$1" = -- ] && shift - # Check we have two non-null arguments + # 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 @@ -48,23 +56,19 @@ cd() { # /foo/lmao/bar/ayy new=${curtc}${rep}${curlc} - # Check pattern was actually in $PWD; this indirectly checks that - # $PWD and $pat are both actually set, too; it's valid for $rep to - # be empty, though - [ "$cur" != "$curtc" ] || exit + # 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 - # Check we ended up with something to change into - [ -n "$new" ] || exit - - # Print the replaced result + # Print the target printf '%s\n' "$new" - )" - # Check we have a second argument - if [ -z "$2" ] ; then - printf >&2 'cd(): Substitution failed\n' - return 1 - fi + # If the subshell failed, return from the function with the same exit + # value + )" || return fi # Execute the cd command as normal -- cgit v1.2.3 From 5c918328111bfdbdaa7838a8f10f499c30e98eeb Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 12:15:13 +1200 Subject: Remove option term spec from bd() It doesn't accept options; cd() needs to because it's a wrapper --- sh/shrc.d/bd.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sh/shrc.d/bd.sh b/sh/shrc.d/bd.sh index 0d1abbcf..5593ff4d 100644 --- a/sh/shrc.d/bd.sh +++ b/sh/shrc.d/bd.sh @@ -3,13 +3,9 @@ bd() { # Set positional parameters to an option terminator and what will hopefully # end up being a target directory - set -- -- "$( + set -- "$( - # If the first of the existing positional arguments is --, shift it - # off - [ "$1" = -- ] && shift - - # There's no more than one argument after that + # Check there's no more than one argument [ "$#" -le 1 ] || exit 1 # The requested pattern is the first argument, defaulting to just the @@ -63,5 +59,5 @@ bd() { )" || return # Try to change into the determined directory - command cd "$@" + command cd -- "$@" } -- cgit v1.2.3 From 416fc33ff1e9e034cf2bb4a58bb177f46606afd5 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 12:29:24 +1200 Subject: Port ud() to POSIX sh --- README.markdown | 2 +- bash/bashrc.d/ud.bash | 50 -------------------------------------------------- sh/shrc.d/ud.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 51 deletions(-) delete mode 100644 bash/bashrc.d/ud.bash create mode 100644 sh/shrc.d/ud.sh diff --git a/README.markdown b/README.markdown index 3bf1e604..6e6f2bbb 100644 --- a/README.markdown +++ b/README.markdown @@ -187,6 +187,7 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: preserved; I hate ending up `root`-owned files in my home directory. * `tmux()` changes the default command for `tmux(1)` to `attach-session -d` if a session exists, or creates a new session if one doesn't. +* `ud()` changes into an indexed ancestor of a directory. * `vim()` defines three functions to always use `vim(1)` as my `ex(1)`, `vi(1)` and `view(1)` implementation if it's available. @@ -211,7 +212,6 @@ There are a few other little tricks defined for other shells, mostly in * `readv()` prints names and values from `read` calls to `stderr`. * `readz()` is an alias for `read -d '' -r`. * `sd()` changes into a sibling of the current directory. -* `ud()` changes into an indexed ancestor of a directory. * `vared()` allows interactively editing a variable with Readline, emulating a Zsh function I like by the same name. * `vr()` tries to change to the root directory of a source control diff --git a/bash/bashrc.d/ud.bash b/bash/bashrc.d/ud.bash deleted file mode 100644 index e23de1fa..00000000 --- a/bash/bashrc.d/ud.bash +++ /dev/null @@ -1,50 +0,0 @@ -# Shortcut to step up the directory tree with an arbitrary number of steps, -# like cd .., cd ../.., etc -ud() { - - # 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 - - # Check and save optional first argument, number of steps upward; default - # to 1 if absent - local -i steps - steps=${1:-1} - if ! ((steps > 0)) ; then - printf 'bash: %s: Invalid step count %s\n' "$FUNCNAME" "$1" >&2 - return 2 - fi - - # Check and save optional second argument, target directory; default to - # $PWD (typical usage case) - 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 - dirname=${dirname%/}/.. - done - - # Try to change into it - cd "${opts[@]}" -- "$dirname" -} diff --git a/sh/shrc.d/ud.sh b/sh/shrc.d/ud.sh new file mode 100644 index 00000000..0dfd858c --- /dev/null +++ b/sh/shrc.d/ud.sh @@ -0,0 +1,42 @@ +# Shortcut to step up the directory tree with an arbitrary number of steps, +# like cd .., cd ../.., etc +ud() { + + # 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 + dirname=${2:-"$PWD"} + + # Append /.. to the target the specified number of times + i=0 + while [ "$i" -lt "$steps" ] ; do + dirname=${dirname%/}/.. + i=$((i+1)) + done + + # Check we have a target after all that + if [ -z "$dirname" ] ; then + printf >&2 'ud(): Destination construction failed\n' + exit 1 + fi + + # Print the target + printf '%s\n' "$dirname" + + # If the subshell failed, return from the function with the same exit + # value + )" || return + + # Try to change into the determined directory + command cd -- "$@" +} -- cgit v1.2.3 From 974c0eb5398534583a8e4d4a19a8066de26bcd05 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 12:43:23 +1200 Subject: Add an issue --- ISSUES.markdown | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ISSUES.markdown b/ISSUES.markdown index 68c6c482..b057fc7d 100644 --- a/ISSUES.markdown +++ b/ISSUES.markdown @@ -19,3 +19,17 @@ Known issues git-reflog(1) cals * The \xFF syntax for regex as used in rfct(1) is not POSIX. Need to decide if it's well-supported enough to keep it anyway. +* Git prompt seems to change its mind about file moves after a run of + git-status: + + tom@conan:~/.dotfiles(master)$ git mv bash/bashrc.d/ud.bash sh/shrc.d/ud.sh + tom@conan:~/.dotfiles(master!+)$ git diff --cached + diff --git a/bash/bashrc.d/ud.bash b/sh/shrc.d/ud.sh + similarity index 100% + rename from bash/bashrc.d/ud.bash + rename to sh/shrc.d/ud.sh + tom@conan:~/.dotfiles(master!+)$ + tom@conan:~/.dotfiles(master!+)$ + tom@conan:~/.dotfiles(master!+)$ git status + R bash/bashrc.d/ud.bash -> sh/shrc.d/ud.sh + tom@conan:~/.dotfiles(master+)$ -- cgit v1.2.3 From 8230f0a0ee64cc7c1e2538fd65403ffdc4a8bd8e Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 12:43:58 +1200 Subject: Move bd() from Bash func section in README --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 6e6f2bbb..b3f2396b 100644 --- a/README.markdown +++ b/README.markdown @@ -164,6 +164,7 @@ If a function can be written in POSIX `sh` without too much hackery, I put it 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. @@ -194,7 +195,6 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: There are a few other little tricks defined for other shells, mostly in `bash/bashrc.d`: -* `bd()` changes into a named ancestor of the current directory. * `fnl()` runs a command and saves its output and error into temporary files, defining variables with the filenames in them. * `grep()` tries to apply color and other options good for interactive use, -- cgit v1.2.3 From 6f36a9e6494c723b209b253dfe3c83b62741c105 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 13:45:17 +1200 Subject: Correctly bail from failed subshell --- sh/shrc.d/bd.sh | 5 +++-- sh/shrc.d/cd.sh | 6 +++--- sh/shrc.d/ud.sh | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/sh/shrc.d/bd.sh b/sh/shrc.d/bd.sh index 5593ff4d..a5344ae7 100644 --- a/sh/shrc.d/bd.sh +++ b/sh/shrc.d/bd.sh @@ -54,9 +54,10 @@ bd() { # Print the target printf '%s\n' "$dirname" + )" - # If the subshell failed, return from the function with the same exit value - )" || return + # 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/cd.sh b/sh/shrc.d/cd.sh index 37574f78..7bfacd6d 100644 --- a/sh/shrc.d/cd.sh +++ b/sh/shrc.d/cd.sh @@ -65,10 +65,10 @@ cd() { # Print the target printf '%s\n' "$new" + )" - # If the subshell failed, return from the function with the same exit - # value - )" || return + # If the subshell printed nothing, return with failure + [ -n "$2" ] || return fi # Execute the cd command as normal diff --git a/sh/shrc.d/ud.sh b/sh/shrc.d/ud.sh index 0dfd858c..259f3167 100644 --- a/sh/shrc.d/ud.sh +++ b/sh/shrc.d/ud.sh @@ -32,10 +32,10 @@ ud() { # Print the target printf '%s\n' "$dirname" + )" - # If the subshell failed, return from the function with the same exit - # value - )" || return + # If the subshell printed nothing, return with failure + [ -n "$1" ] || return # Try to change into the determined directory command cd -- "$@" -- cgit v1.2.3 From a4e4cf23ca984e1fc7633b48f94c1795a5bc7ec8 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 13:52:55 +1200 Subject: Port pd() to POSIX sh --- README.markdown | 2 +- bash/bashrc.d/pd.bash | 53 --------------------------------------------------- sh/shrc.d/pd.sh | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 54 deletions(-) delete mode 100644 bash/bashrc.d/pd.bash create mode 100644 sh/shrc.d/pd.sh diff --git a/README.markdown b/README.markdown index b3f2396b..9bbf12d9 100644 --- a/README.markdown +++ b/README.markdown @@ -180,6 +180,7 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `mkcd()` creates a directory and changes into it. * `mysql()` allows shortcuts to MySQL configuration files stored in `~/.mysql`. +* `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)`. * `scp()` tries to detect forgotten hostnames in `scp(1)` command calls. @@ -206,7 +207,6 @@ There are a few other little tricks defined for other shells, mostly in It's dependent on information written by the `ls.sh` script in `~/.profile.d`. * `path()` manages the contents of `PATH` conveniently. -* `pd()` changes to the argument's parent directory. * `prompt()` sets up my interactive prompt. * `pushd()` adds a default destination of `$HOME` to the `pushd` builtin. * `readv()` prints names and values from `read` calls to `stderr`. diff --git a/bash/bashrc.d/pd.bash b/bash/bashrc.d/pd.bash deleted file mode 100644 index 47a317e0..00000000 --- a/bash/bashrc.d/pd.bash +++ /dev/null @@ -1,53 +0,0 @@ -# Attempt to change into the argument's parent directory; preserve any options -# and pass them to cd. This is intended for use when you've got a file path in -# a variable, or in history, or in Alt+., and want to quickly move to its -# containing directory. In the absence of an argument, this just shifts up a -# directory, i.e. `cd ..` -pd() { - - # 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 - - # Determine target directory - local target - case $# in - 0) - target=.. - ;; - 1) - target=$1 - target=${target%/} - target=${target%/*} - ;; - *) - printf 'bash: %s: too many arguments\n' \ - "$FUNCNAME" >&2 - return 2 - ;; - esac - - # If we have a target directory, try to change into it - if [[ -n $target ]] ; then - builtin cd "${opts[@]}" -- "$target" - else - printf 'bash: %s: error calculating parent directory\n' \ - "$FUNCNAME" >&2 - return 2 - fi -} diff --git a/sh/shrc.d/pd.sh b/sh/shrc.d/pd.sh new file mode 100644 index 00000000..c022b1e8 --- /dev/null +++ b/sh/shrc.d/pd.sh @@ -0,0 +1,37 @@ +# Attempt to change into the argument's parent directory; This is intended for +# use when you've got a file path in a variable, or in history, or in Alt+., +# and want to quickly move to its containing directory. In the absence of an +# argument, this just shifts up a directory, i.e. `cd ..` +pd() { + + # 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%/} + dirname=${dirname%/*} + + # Check we have a target after that + if [ -z "$dirname" ] ; then + printf >&2 'ud(): Destination construction failed\n' + exit 1 + fi + + # Print the target + printf '%s\n' "$dirname" + )" + + # If the subshell printed nothing, return with failure + [ -n "$1" ] || return + + # Try to change into the determined directory + command cd -- "$@" +} -- cgit v1.2.3 From 7e10222deeb3f53bebc6514ddfd6321a4ac4c894 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 14:15:58 +1200 Subject: Port sd() to POSIX sh --- README.markdown | 2 +- bash/bashrc.d/sd.bash | 109 -------------------------------------------------- sh/shrc.d/sd.sh | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 110 deletions(-) delete mode 100644 bash/bashrc.d/sd.bash create mode 100644 sh/shrc.d/sd.sh diff --git a/README.markdown b/README.markdown index 9bbf12d9..e9e04201 100644 --- a/README.markdown +++ b/README.markdown @@ -185,6 +185,7 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `rcsdiff()` forces a unified format for `rcsdiff(1)`. * `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. * `sudo()` forces `-H` for `sudo(8)` calls so that `$HOME` is never preserved; I hate ending up `root`-owned files in my home directory. * `tmux()` changes the default command for `tmux(1)` to `attach-session -d` @@ -211,7 +212,6 @@ There are a few other little tricks defined for other shells, mostly in * `pushd()` adds a default destination of `$HOME` to the `pushd` builtin. * `readv()` prints names and values from `read` calls to `stderr`. * `readz()` is an alias for `read -d '' -r`. -* `sd()` changes into a sibling of the current directory. * `vared()` allows interactively editing a variable with Readline, emulating a Zsh function I like by the same name. * `vr()` tries to change to the root directory of a source control diff --git a/bash/bashrc.d/sd.bash b/bash/bashrc.d/sd.bash deleted file mode 100644 index ad4a0deb..00000000 --- a/bash/bashrc.d/sd.bash +++ /dev/null @@ -1,109 +0,0 @@ -# -# 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. -# -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 dirname - - # If we have one argument, it's easy, we just try to move to that one - if (($# == 1)) ; then - 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 - 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 ! dirname=$( - 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 directory\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 determined directory - builtin cd "${opts[@]}" ../"$dirname" -} diff --git a/sh/shrc.d/sd.sh b/sh/shrc.d/sd.sh new file mode 100644 index 00000000..c6513aac --- /dev/null +++ b/sh/shrc.d/sd.sh @@ -0,0 +1,82 @@ +# +# 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. +# +sd() { + + 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 of the current directory's siblings if no request + spec=$1 + set -- + if [ -n "$spec" ] ; then + set -- "$@" ../"$spec" + else + for sib in ../.* ../* ; do + case ${sib#../} in + .|..|"${PWD##*/}") continue ;; + esac + set -- "$@" "$sib" + done + fi + + # We should have exactly one sibling + case $# in + 1) ;; + 0) + printf >&2 'sd(): No siblings\n' + exit 1 + ;; + *) + printf >&2 'sd(): More than one sibling\n' + exit 1 + ;; + esac + + # Print the target + printf '%s\n' "$1" + )" + + # If the subshell printed nothing, return with failure + [ -n "$1" ] || return + + # Try to change into the determined directory + command cd -- "$@" +} -- cgit v1.2.3 From 7125f12142d865a2373570965673e6c609a2a6dc Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 14:16:57 +1200 Subject: Move grep() and ls() to POSIX funcs section --- README.markdown | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.markdown b/README.markdown index e9e04201..b1e581d5 100644 --- a/README.markdown +++ b/README.markdown @@ -175,8 +175,14 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: variables. * `gdb()` silences startup messages from `gdb(1)`. * `gpg()` quietens `gpg(1)` down for most commands. +* `grep()` tries to apply color and other options good for interactive use, + depending on the capabilities of the system `grep(1)`. It's dependent on + information written by the `grep.sh` script in `~/.profile.d`. * `hgrep()` allows searching `$HISTFILE`. * `keychain()` updates `$GPG_TTY` if set for `keychain(1)`. +* `ls()` tries to apply color to `ls(1)` for interactive use if available. + It's dependent on information written by the `ls.sh` script in + `~/.profile.d`. * `mkcd()` creates a directory and changes into it. * `mysql()` allows shortcuts to MySQL configuration files stored in `~/.mysql`. @@ -199,14 +205,8 @@ There are a few other little tricks defined for other shells, mostly in * `fnl()` runs a command and saves its output and error into temporary files, defining variables with the filenames in them. -* `grep()` tries to apply color and other options good for interactive use, - depending on the capabilities of the system `grep(1)`. It's dependent on - information written by the `grep.sh` script in `~/.profile.d`. * `keep()` stores ad-hoc shell functions and variables. * `lhn()` gets the history number of the last command. -* `ls()` tries to apply color to `ls(1)` for interactive use if available. - It's dependent on information written by the `ls.sh` script in - `~/.profile.d`. * `path()` manages the contents of `PATH` conveniently. * `prompt()` sets up my interactive prompt. * `pushd()` adds a default destination of `$HOME` to the `pushd` builtin. -- cgit v1.2.3 From f6c40a8e49105fea60c074c198b96a6a71553185 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 14:28:00 +1200 Subject: Basic PS1 setup for shrc Will be overridden completely by Bash or OpenBSD pdksh prompt() functions --- sh/shrc.d/prompt.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 sh/shrc.d/prompt.sh diff --git a/sh/shrc.d/prompt.sh b/sh/shrc.d/prompt.sh new file mode 100644 index 00000000..ef3009ae --- /dev/null +++ b/sh/shrc.d/prompt.sh @@ -0,0 +1,3 @@ +# Basic PS1 for POSIX shell +# Does every POSIX shell support these? dash does, at least. +PS1=$(printf '%s@%s$ ' "$(id -nu)" "$(hostname -s)") -- cgit v1.2.3 From 173880274a7e9425bf9f6b8a5b54a246cdd20b42 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 14:32:54 +1200 Subject: Add an issue about vr() --- ISSUES.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/ISSUES.markdown b/ISSUES.markdown index b057fc7d..bbaf12ae 100644 --- a/ISSUES.markdown +++ b/ISSUES.markdown @@ -3,6 +3,7 @@ Known issues * vr() does not handle the newer version of Subversion repositories which only have a .svn directory at the root level. +* It should also be POSIX portable without too much effort * The terminfo files probably still do not work on NetBSD (needs retesting and manual page reading). * man(1) completion doesn't work on OpenBSD as manpath(1) isn't a thing on -- cgit v1.2.3 From a143ce9c38942ebe2245ad0e7c33462087692809 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 15:53:19 +1200 Subject: Port lhn() to POSIX sh --- README.markdown | 3 ++- bash/bashrc.d/lhn.bash | 7 ------- sh/shrc.d/lhn.sh | 12 ++++++++++++ 3 files changed, 14 insertions(+), 8 deletions(-) delete mode 100644 bash/bashrc.d/lhn.bash create mode 100644 sh/shrc.d/lhn.sh diff --git a/README.markdown b/README.markdown index b1e581d5..558c8739 100644 --- a/README.markdown +++ b/README.markdown @@ -180,6 +180,8 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: information written by the `grep.sh` script in `~/.profile.d`. * `hgrep()` allows searching `$HISTFILE`. * `keychain()` updates `$GPG_TTY` if set for `keychain(1)`. +* `lhn()` gets the history number of the last command, if the POSIX `fc` + builtin is available. * `ls()` tries to apply color to `ls(1)` for interactive use if available. It's dependent on information written by the `ls.sh` script in `~/.profile.d`. @@ -206,7 +208,6 @@ There are a few other little tricks defined for other shells, mostly in * `fnl()` runs a command and saves its output and error into temporary files, defining variables with the filenames in them. * `keep()` stores ad-hoc shell functions and variables. -* `lhn()` gets the history number of the last command. * `path()` manages the contents of `PATH` conveniently. * `prompt()` sets up my interactive prompt. * `pushd()` adds a default destination of `$HOME` to the `pushd` builtin. diff --git a/bash/bashrc.d/lhn.bash b/bash/bashrc.d/lhn.bash deleted file mode 100644 index 89c6f5da..00000000 --- a/bash/bashrc.d/lhn.bash +++ /dev/null @@ -1,7 +0,0 @@ -# Print the history number of the last command -lhn () { - local last - last=$(fc -l -1) || return - [[ -n $last ]] || return - printf '%u\n' "${last%%[^0-9]*}" -} diff --git a/sh/shrc.d/lhn.sh b/sh/shrc.d/lhn.sh new file mode 100644 index 00000000..15fced6e --- /dev/null +++ b/sh/shrc.d/lhn.sh @@ -0,0 +1,12 @@ +# Print the history number of the last command +# "fc" is specified by POSIX, but does not seem to be in dash, so its being +# included here rather than in e.g. ~/.bashrc.d is a bit tenuous. +lhn () { + if ! command -v fc >/dev/null 2>&1 ; then + printf 'lhn(): fc: command not found\n' + return 1 + fi + set -- "$(fc -l -1)" + [ -n "$1" ] || return + printf '%u\n' "$1" +} -- cgit v1.2.3 From f407a5dab1e47161e4344d4ecaf2b429d717aa6e Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 15:56:23 +1200 Subject: Assume POSIX sh --- vim/after/ftplugin/sh.vim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vim/after/ftplugin/sh.vim b/vim/after/ftplugin/sh.vim index 8af94d90..81cbe6ef 100644 --- a/vim/after/ftplugin/sh.vim +++ b/vim/after/ftplugin/sh.vim @@ -2,6 +2,9 @@ " doesn't highlight let g:sh_isk='@,48-57,_,192-255,.,/' +" Assume POSIX, I never write Bourne +let g:is_posix=1 + " Use han(1) as a man(1) wrapper for Bash files if available if exists('b:is_bash') && executable('han') setlocal keywordprg=han -- cgit v1.2.3 From 6e3fd021588c87a0743dbc1ec5b3f5aba900f839 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 16:26:06 +1200 Subject: Port vr(1) to POSIX sh --- ISSUES.markdown | 3 --- README.markdown | 4 ++-- bash/bashrc.d/vr.bash | 59 --------------------------------------------------- sh/shrc.d/vr.sh | 49 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 64 deletions(-) delete mode 100644 bash/bashrc.d/vr.bash create mode 100644 sh/shrc.d/vr.sh diff --git a/ISSUES.markdown b/ISSUES.markdown index bbaf12ae..c9a832d1 100644 --- a/ISSUES.markdown +++ b/ISSUES.markdown @@ -1,9 +1,6 @@ Known issues ============ -* vr() does not handle the newer version of Subversion repositories which - only have a .svn directory at the root level. -* It should also be POSIX portable without too much effort * The terminfo files probably still do not work on NetBSD (needs retesting and manual page reading). * man(1) completion doesn't work on OpenBSD as manpath(1) isn't a thing on diff --git a/README.markdown b/README.markdown index 558c8739..c79ffd1b 100644 --- a/README.markdown +++ b/README.markdown @@ -201,6 +201,8 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `ud()` changes into an indexed ancestor of a directory. * `vim()` defines three functions to always use `vim(1)` as my `ex(1)`, `vi(1)` and `view(1)` implementation if it's available. +* `vr()` tries to change to the root directory of a source control + repository. There are a few other little tricks defined for other shells, mostly in `bash/bashrc.d`: @@ -215,8 +217,6 @@ There are a few other little tricks defined for other shells, mostly in * `readz()` is an alias for `read -d '' -r`. * `vared()` allows interactively editing a variable with Readline, emulating a Zsh function I like by the same name. -* `vr()` tries to change to the root directory of a source control - repository. #### Completion diff --git a/bash/bashrc.d/vr.bash b/bash/bashrc.d/vr.bash deleted file mode 100644 index adabb395..00000000 --- a/bash/bashrc.d/vr.bash +++ /dev/null @@ -1,59 +0,0 @@ -# Move to the root directory of a VCS working copy -vr() { - local path - path=${1:-"$PWD"} - path=${path%/} - - # Raise some helpful errors - if [[ ! -e $path ]] ; then - printf 'bash: %s: %s: No such file or directory\n' \ - "$FUNCNAME" "$path" - return 1 - fi - if [[ ! -d $path ]] ; then - printf 'bash: %s: %s: Not a directory\n' \ - "$FUNCNAME" "$path" - return 1 - fi - if [[ ! -x $path ]] ; then - printf 'bash: %s: %s: Permission denied\n' \ - "$FUNCNAME" "$path" - return 1 - fi - - # Ask Git the top level - local git_root - git_root=$(cd -- "$path" && git rev-parse --show-toplevel 2>/dev/null) - if [[ -n $git_root ]] ; then - cd -- "$git_root" - return - fi - - # Ask Mercurial the top level - local hg_root - hg_root=$(cd -- "$path" && hg root 2>/dev/null) - if [[ -n $hg_root ]] ; then - cd -- "$hg_root" - return - fi - - # 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 - while [[ -n $search ]] ; do - if [[ -d ${search%/*}/.svn ]] ; then - search=${search%/*} - else - cd -- "$search" - return - fi - done - fi - - # Couldn't find repository root, say so - printf 'bash: %s: Failed to find repository root\n' \ - "$FUNCNAME" >&2 - return 1 -} diff --git a/sh/shrc.d/vr.sh b/sh/shrc.d/vr.sh new file mode 100644 index 00000000..b8a31aee --- /dev/null +++ b/sh/shrc.d/vr.sh @@ -0,0 +1,49 @@ +# Move to the root directory of a VCS working copy +vr() { + + # Set positional parameters to the result of trying to figure out the + # repository root + set -- "$( + + # Check we have at most one argument + if [ "$#" -gt 1 ] ; then + printf >&2 'vr(): Too many arguments\n' + exit 2 + fi + + # Get path from first argument, strip trailing slash + path=${1:-"$PWD"} + [ "$path" = / ] || path=${path%/} + + # Step into the directory + cd -- "$path" || exit + + # Ask Git the top level (good) + git rev-parse --show-toplevel 2>/dev/null && exit + + # Ask Mercurial the top level (great) + hg root 2>/dev/null && exit + + # If we can get SVN info, iterate upwards until we can't; hopefully + # that's the root (bad) + while svn info >/dev/null 2>&1 ; do + root=$PWD + [ "$root" = / ] && break + cd .. || exit + done + if [ -n "$root" ] ; then + printf '%s\n' "$root" + exit + fi + + # Couldn't find repository root, say so + printf >&2 'vr(): Failed to find repository root\n' + exit 1 + )" + + # Check we figured out a target, or bail + [ -n "$1" ] || return + + # Try to change into the determined directory + command cd -- "$@" +} -- cgit v1.2.3 From 108f964896180d168768db08e92690c6328e238d Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 16:29:45 +1200 Subject: Remove readv() and readz() Neither are terribly useful --- README.markdown | 2 -- bash/bashrc.d/readv.bash | 25 ------------------------- bash/bashrc.d/readz.bash | 4 ---- 3 files changed, 31 deletions(-) delete mode 100644 bash/bashrc.d/readv.bash delete mode 100644 bash/bashrc.d/readz.bash diff --git a/README.markdown b/README.markdown index c79ffd1b..1c205261 100644 --- a/README.markdown +++ b/README.markdown @@ -213,8 +213,6 @@ There are a few other little tricks defined for other shells, mostly in * `path()` manages the contents of `PATH` conveniently. * `prompt()` sets up my interactive prompt. * `pushd()` adds a default destination of `$HOME` to the `pushd` builtin. -* `readv()` prints names and values from `read` calls to `stderr`. -* `readz()` is an alias for `read -d '' -r`. * `vared()` allows interactively editing a variable with Readline, emulating a Zsh function I like by the same name. diff --git a/bash/bashrc.d/readv.bash b/bash/bashrc.d/readv.bash deleted file mode 100644 index abd624a4..00000000 --- a/bash/bashrc.d/readv.bash +++ /dev/null @@ -1,25 +0,0 @@ -readv() { - local arg - local -a opts names - for arg ; do - case $arg in - --) - shift - break - ;; - -*) - shift - opts[${#opts[@]}]=$arg - ;; - *) - break - ;; - esac - done - names=("$@") - builtin read "${opts[@]}" "${names[@]}" || return - for name in "${names[@]}" ; do - printf >&2 '%s: %s = %s\n' \ - "$FUNCNAME" "$name" "${!name}" - done -} diff --git a/bash/bashrc.d/readz.bash b/bash/bashrc.d/readz.bash deleted file mode 100644 index 910aab4b..00000000 --- a/bash/bashrc.d/readz.bash +++ /dev/null @@ -1,4 +0,0 @@ -# Call read with a null delimiter -readz() { - builtin read -rd '' "$@" -} -- cgit v1.2.3 From fa2f423345776d8a99dae604f89e5b0ba76ceabb Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 16:47:37 +1200 Subject: Port fnl() to POSIX sh script fnl(1) No real compelling reason to make it a shell function in the first place. --- README.markdown | 4 ++-- bash/bashrc.d/fnl.bash | 41 ----------------------------------------- bin/fnl | 21 +++++++++++++++++++++ man/man1/fnl.1 | 16 ++++++++++++++++ 4 files changed, 39 insertions(+), 43 deletions(-) delete mode 100644 bash/bashrc.d/fnl.bash create mode 100755 bin/fnl create mode 100644 man/man1/fnl.1 diff --git a/README.markdown b/README.markdown index 1c205261..40556ef1 100644 --- a/README.markdown +++ b/README.markdown @@ -207,8 +207,6 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: There are a few other little tricks defined for other shells, mostly in `bash/bashrc.d`: -* `fnl()` runs a command and saves its output and error into temporary files, - defining variables with the filenames in them. * `keep()` stores ad-hoc shell functions and variables. * `path()` manages the contents of `PATH` conveniently. * `prompt()` sets up my interactive prompt. @@ -396,6 +394,8 @@ Installed by the `install-bin` target: `~/.local/bin`, for personal scripting snippets. * `fgscr(1)` finds Git repositories in a directory root and scrubs them with `gscr(1)`. +* `fnl(1)` runs a command and saves its output and error into temporary files, + printing their paths and line counts * `gms(1)` runs a set of `getmailrc` files; does much the same thing as the script `getmails` in the `getmail` suite, but runs the requests in parallel and does up to three silent retries using `try(1)`. diff --git a/bash/bashrc.d/fnl.bash b/bash/bashrc.d/fnl.bash deleted file mode 100644 index 1f543dbf..00000000 --- a/bash/bashrc.d/fnl.bash +++ /dev/null @@ -1,41 +0,0 @@ -# Run a command and push its stdout and stderr into temporary files, printing -# the names of the files once done, and saving them into two variables. Return -# the exit status of the command. -# -# $ fnl grep foo /bar -# declare -p fnl_stdout="/tmp/fnl.xQmhe/stdout" -# declare -p fnl_stderr="/tmp/fnl.xQmhe/stderr" -# -fnl() { - - # Must be called with at least one command argument - if ! (($#)) ; then - printf 'bash: %s: usage: %s COMMAND [ARG1 ...]\n' \ - "$FUNCNAME" "$FUNCNAME" >&2 - return 2 - fi - - # Try to stop infinitely recursive calls - if [[ $1 == "$FUNCNAME" ]] ; then - printf 'bash: %s: Cannot nest calls\n' \ - "$FUNCNAME" >&2 - return 2 - fi - - # Create a temporary directory or bail - local dirname - dirname=$(mktd "$FUNCNAME") || return - - # Run the command and save its exit status - local ret - "$@" >"$dirname"/stdout 2>"$dirname"/stderr - ret=$? - - # Note these are *not* local variables - # shellcheck disable=SC2034 - 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 - return "$ret" -} diff --git a/bin/fnl b/bin/fnl new file mode 100755 index 00000000..d27d1f90 --- /dev/null +++ b/bin/fnl @@ -0,0 +1,21 @@ +#!/bin/sh +# Run a command and save its output and error to temporary files + +# Check we have at least one argument +if [ "$#" -eq 0 ] ; then + printf >&2 'fnl: Command needed\n' + return 2 +fi + +# Create a temporary directory; note that we *don't* clean it up on exit +dir=$(mktd fnl) || exit + +# Run the command; keep its exit status +"$@" >"$dir"/stdout 2>"$dir"/stderr +ret=$? + +# Run wc(1) on each of the files +wc -- "$dir"/* + +# Exit with the wrapped command's exit status +exit "$ret" diff --git a/man/man1/fnl.1 b/man/man1/fnl.1 new file mode 100644 index 00000000..a578dbf4 --- /dev/null +++ b/man/man1/fnl.1 @@ -0,0 +1,16 @@ +.TH FNL 1 "August 2016" "Manual page for fnl" +.SH NAME +.B fnl +\- list the biggest files in the given directory +.SH SYNOPSIS +.B fnl +command arg1 ... +.SH DESCRIPTION +.B fnl +runs the command specifies in its arguments, writing any stdout and stderr to +separate temporary files created with mktd(1), and then runs wc(1) over them to +show their statistics and full paths. +.SH SEE ALSO +igex(1) +.SH AUTHOR +Tom Ryder -- cgit v1.2.3 From 78ca825a27ea02ed17be226f253cecb0e3a71daa Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 17:27:04 +1200 Subject: Port path() to POSIX sh That was a lot easier than I thought --- README.markdown | 2 +- bash/bashrc.d/path.bash | 180 ------------------------------------------------ sh/shrc.d/path.sh | 93 +++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 181 deletions(-) delete mode 100644 bash/bashrc.d/path.bash create mode 100644 sh/shrc.d/path.sh diff --git a/README.markdown b/README.markdown index 40556ef1..4108914d 100644 --- a/README.markdown +++ b/README.markdown @@ -188,6 +188,7 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `mkcd()` creates a directory and changes into it. * `mysql()` allows shortcuts to MySQL configuration files stored in `~/.mysql`. +* `path()` manages the contents of `PATH` conveniently. * `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)`. @@ -208,7 +209,6 @@ There are a few other little tricks defined for other shells, mostly in `bash/bashrc.d`: * `keep()` stores ad-hoc shell functions and variables. -* `path()` manages the contents of `PATH` conveniently. * `prompt()` sets up my interactive prompt. * `pushd()` adds a default destination of `$HOME` to the `pushd` builtin. * `vared()` allows interactively editing a variable with Readline, emulating diff --git a/bash/bashrc.d/path.bash b/bash/bashrc.d/path.bash deleted file mode 100644 index 61bf73c0..00000000 --- a/bash/bashrc.d/path.bash +++ /dev/null @@ -1,180 +0,0 @@ -# Function to manage contents of PATH variable within the current shell -path() { - - # Figure out command being called - local pathcmd - if (($#)) ; then - pathcmd=$1 - shift - else - pathcmd=list - fi - - # Switch between commands - case $pathcmd in - - # Print help output (also done if command not found) - help|h|-h|--help|-\?) - while IFS= read -r line ; do - printf '%s\n' "$line" - done <&2 - return 1 - fi - if [[ ! -d $dirname ]] ; then - printf 'bash: %s: %s not a directory\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - if [[ $dirname == *:* ]] ; then - printf 'bash: %s: Cannot add insert directory %s with colon in name\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - if path check "$dirname" ; then - printf 'bash: %s: %s already in PATH\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - patharr=("$dirname" "${patharr[@]}") - path set "${patharr[@]}" - ;; - - # Add a directory to the end of PATH, checking for existence and uniqueness - append|add|a) - local -a patharr - IFS=: read -a patharr < <(printf '%s\n' "$PATH") - 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 $dirname ]] ; then - printf 'bash: %s: %s not a directory\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - if [[ $dirname == *:* ]] ; then - printf 'bash: %s: Cannot append directory %s with colon in name\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - if path check "$dirname" ; then - printf 'bash: %s: %s already in PATH\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - patharr[${#patharr[@]}]=$dirname - path set "${patharr[@]}" - ;; - - # Remove all instances of a directory from PATH - remove|rm|r) - local -a patharr - IFS=: read -a patharr < <(printf '%s\n' "$PATH") - 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 "$dirname" ; then - printf 'bash: %s: %s not in PATH\n' \ - "$FUNCNAME" "$dirname" >&2 - return 1 - fi - local -a newpatharr - local part - for part in "${patharr[@]}" ; do - [[ $dirname == "$part" ]] && continue - newpatharr[${#newpatharr[@]}]=$part - done - path set "${newpatharr[@]}" - ;; - - # Set the PATH to the given directories without checking existence or uniqueness - set|s) - local -a newpatharr - local dirname - for dirname ; do - newpatharr[${#newpatharr[@]}]=$dirname - done - PATH=$(IFS=: ; printf '%s' "${newpatharr[*]}") - ;; - - # Return whether directory is a component of PATH - check|c) - local -a patharr - IFS=: read -a patharr < <(printf '%s\n' "$PATH") - 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 [[ $dirname == "$part" ]] ; then - return 0 - fi - done - return 1 - ;; - - # Unknown command - *) - printf 'bash: %s: Unknown command %s\n' \ - "$FUNCNAME" "$pathcmd" >&2 - path help >&2 - return 1 - ;; - esac -} diff --git a/sh/shrc.d/path.sh b/sh/shrc.d/path.sh new file mode 100644 index 00000000..22374310 --- /dev/null +++ b/sh/shrc.d/path.sh @@ -0,0 +1,93 @@ +# Function to manage contents of PATH variable within the current shell +path() { + + # The second argument, the directory, can never have a colon + case $2 in + *:*) + printf >&2 'path(): Illegal colon in given directory\n' + return 2 + ;; + esac + + # Check first argument to figure out operation + case $1 in + + # List current directories in $PATH + list|'') ( + path=$PATH: + while [ -n "$path" ] ; do + dir=${path%%:*} + path=${path#*:} + [ -n "$dir" ] || continue + printf '%s\n' "$dir" + done + ) ;; + + # Add a directory at the start of $PATH + insert) + if path check "$2" ; then + printf >&2 'path(): %s already in $PATH\n' + return 1 + fi + PATH=${2}${PATH:+:"$PATH"} + ;; + + # Add a directory to the end of $PATH + append) + if path check "$2" ; then + printf >&2 'path(): %s already in $PATH\n' + return 1 + fi + PATH=${PATH:+"$PATH":}${2} + ;; + + # Remove a directory from $PATH + remove) + if ! path check "$2" ; then + printf >&2 'path(): %s not in $PATH\n' + return 1 + fi + PATH=$( + path=:$path: + path=${path%%:"$2":*}:${path#*:"$2":} + path=${path#:} + path=${path%:} + printf '%s\n' "$path" + ) + ;; + + # Check whether a directory is in $PATH + check) ( + path=:$PATH: + [ "$path" != "${path%:"$2":*}" ] + ) ;; + + # Print help output (also done if command not found) + help) + cat <<'EOF' +path(): Manage contents of PATH variable + +USAGE: + path [list] + Print the current directories in PATH, one per line (default command) + path insert DIR + Add a directory to the front of PATH + path append DIR + Add a directory to the end of PATH + path remove DIR + Remove directory from PATH + path check DIR + Return whether DIR is a component of PATH + path help + Print this help message (also done if command not found) +EOF + ;; + + # Command not found + *) + printf >&2 'path(): Unknown command\n' + path help + return 2 + ;; + esac +} -- cgit v1.2.3 From a3d0e2dc7aa4d210aad7273346bd2fcee77d63ef Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 17:31:28 +1200 Subject: Dance for shellcheck in path() --- sh/shrc.d/path.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sh/shrc.d/path.sh b/sh/shrc.d/path.sh index 22374310..abeb5962 100644 --- a/sh/shrc.d/path.sh +++ b/sh/shrc.d/path.sh @@ -12,8 +12,9 @@ path() { # Check first argument to figure out operation case $1 in - # List current directories in $PATH + # List current directories in PATH list|'') ( + # shellcheck disable=SC2030 path=$PATH: while [ -n "$path" ] ; do dir=${path%%:*} @@ -26,7 +27,7 @@ path() { # Add a directory at the start of $PATH insert) if path check "$2" ; then - printf >&2 'path(): %s already in $PATH\n' + printf >&2 'path(): %s already in PATH\n' return 1 fi PATH=${2}${PATH:+:"$PATH"} @@ -35,7 +36,7 @@ path() { # Add a directory to the end of $PATH append) if path check "$2" ; then - printf >&2 'path(): %s already in $PATH\n' + printf >&2 'path(): %s already in PATH\n' return 1 fi PATH=${PATH:+"$PATH":}${2} @@ -44,10 +45,11 @@ path() { # Remove a directory from $PATH remove) if ! path check "$2" ; then - printf >&2 'path(): %s not in $PATH\n' + printf >&2 'path(): %s not in PATH\n' return 1 fi PATH=$( + # shellcheck disable=SC2031 path=:$path: path=${path%%:"$2":*}:${path#*:"$2":} path=${path#:} @@ -56,8 +58,9 @@ path() { ) ;; - # Check whether a directory is in $PATH + # Check whether a directory is in PATH check) ( + # shellcheck disable=SC2030 path=:$PATH: [ "$path" != "${path%:"$2":*}" ] ) ;; -- cgit v1.2.3 From adeab1ef1ae40c944f7e80f9d3b1c99494bf8cdc Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 17:32:59 +1200 Subject: Write path() help to stderr if unrec command --- sh/shrc.d/path.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sh/shrc.d/path.sh b/sh/shrc.d/path.sh index abeb5962..0b9e7e94 100644 --- a/sh/shrc.d/path.sh +++ b/sh/shrc.d/path.sh @@ -89,7 +89,7 @@ EOF # Command not found *) printf >&2 'path(): Unknown command\n' - path help + path help >&2 return 2 ;; esac -- cgit v1.2.3 From 8ec528a533b921b24572468e591c36fe5269a0a9 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 17:34:42 +1200 Subject: Correct empty var reference --- sh/shrc.d/path.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sh/shrc.d/path.sh b/sh/shrc.d/path.sh index 0b9e7e94..81823395 100644 --- a/sh/shrc.d/path.sh +++ b/sh/shrc.d/path.sh @@ -49,8 +49,7 @@ path() { return 1 fi PATH=$( - # shellcheck disable=SC2031 - path=:$path: + path=:$PATH: path=${path%%:"$2":*}:${path#*:"$2":} path=${path#:} path=${path%:} -- cgit v1.2.3 From ea4fbf170c1076be980bcd18bf4bf31f8eff990e Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 17:36:15 +1200 Subject: Correct path() error messages --- sh/shrc.d/path.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sh/shrc.d/path.sh b/sh/shrc.d/path.sh index 81823395..a5940dd7 100644 --- a/sh/shrc.d/path.sh +++ b/sh/shrc.d/path.sh @@ -4,7 +4,7 @@ path() { # The second argument, the directory, can never have a colon case $2 in *:*) - printf >&2 'path(): Illegal colon in given directory\n' + printf >&2 'path(): %s illegal colon\n' "$2" return 2 ;; esac @@ -27,7 +27,7 @@ path() { # Add a directory at the start of $PATH insert) if path check "$2" ; then - printf >&2 'path(): %s already in PATH\n' + printf >&2 'path(): %s already in PATH\n' "$2" return 1 fi PATH=${2}${PATH:+:"$PATH"} @@ -36,7 +36,7 @@ path() { # Add a directory to the end of $PATH append) if path check "$2" ; then - printf >&2 'path(): %s already in PATH\n' + printf >&2 'path(): %s already in PATH\n' "$2" return 1 fi PATH=${PATH:+"$PATH":}${2} @@ -45,7 +45,7 @@ path() { # Remove a directory from $PATH remove) if ! path check "$2" ; then - printf >&2 'path(): %s not in PATH\n' + printf >&2 'path(): %s not in PATH\n' "$2" return 1 fi PATH=$( -- cgit v1.2.3 From 95f251851e81399ba6e54c7d5344ec10a0627bae Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Sat, 20 Aug 2016 17:37:59 +1200 Subject: Update path() completion --- bash/bash_completion.d/path.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/bash_completion.d/path.bash b/bash/bash_completion.d/path.bash index efead3c9..b8bdc6aa 100644 --- a/bash/bash_completion.d/path.bash +++ b/bash/bash_completion.d/path.bash @@ -6,7 +6,7 @@ _path() { # Complete operation as first word local cmd - for cmd in help list insert append remove set check ; do + for cmd in list insert append remove check help ; do [[ $cmd == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$cmd done @@ -16,7 +16,7 @@ _path() { case ${COMP_WORDS[1]} in # Complete with a directory - insert|i|append|add|a|check|c|set|s) + insert|append|check) local dirname while IFS= read -rd '' dirname ; do COMPREPLY[${#COMPREPLY[@]}]=$dirname @@ -39,7 +39,7 @@ _path() { ;; # Complete with directories from PATH - remove|rm|r) + remove) local -a promptarr IFS=: read -d '' -a promptarr < <(printf '%s\0' "$PATH") local part -- cgit v1.2.3