aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2016-08-20 18:22:20 +1200
committerTom Ryder <tom@sanctum.geek.nz>2016-08-20 18:22:20 +1200
commit649f001206932c58412a7e37ef4e820fac593fde (patch)
treeb793ae41135a34b556f5dc0a699d9216b06fdd25
parentMerge branch 'master' into freebsd (diff)
parentUpdate path() completion (diff)
downloaddotfiles-649f001206932c58412a7e37ef4e820fac593fde.tar.gz
dotfiles-649f001206932c58412a7e37ef4e820fac593fde.zip
Merge branch 'master' into freebsd
-rw-r--r--ISSUES.markdown16
-rw-r--r--Makefile8
-rw-r--r--README.markdown45
-rw-r--r--bash/bash_completion.d/path.bash6
-rw-r--r--bash/bash_logout3
-rw-r--r--bash/bash_profile6
-rw-r--r--bash/bash_profile.d/fortune.bash19
-rw-r--r--bash/bash_profile.d/remind.bash22
-rw-r--r--bash/bash_profile.d/verse.bash24
-rw-r--r--bash/bashrc.d/bd.bash78
-rw-r--r--bash/bashrc.d/fnl.bash41
-rw-r--r--bash/bashrc.d/lhn.bash7
-rw-r--r--bash/bashrc.d/path.bash180
-rw-r--r--bash/bashrc.d/pd.bash53
-rw-r--r--bash/bashrc.d/readv.bash25
-rw-r--r--bash/bashrc.d/readz.bash4
-rw-r--r--bash/bashrc.d/sd.bash109
-rw-r--r--bash/bashrc.d/ud.bash50
-rw-r--r--bash/bashrc.d/vr.bash59
-rwxr-xr-xbin/apf2
-rwxr-xr-xbin/eds3
-rwxr-xr-xbin/fnl21
-rwxr-xr-xbin/sshi28
-rwxr-xr-xcheck/bash2
-rw-r--r--man/man1/fnl.116
-rw-r--r--man/man1/sshi.115
-rw-r--r--sh/profile.d/fortune.sh24
-rw-r--r--sh/profile.d/oldpwd.sh5
-rw-r--r--sh/profile.d/remind.sh19
-rw-r--r--sh/profile.d/verse.sh28
-rw-r--r--sh/shrc.d/bd.sh64
-rw-r--r--sh/shrc.d/cd.sh36
-rw-r--r--sh/shrc.d/lhn.sh12
-rw-r--r--sh/shrc.d/path.sh95
-rw-r--r--sh/shrc.d/pd.sh37
-rw-r--r--sh/shrc.d/prompt.sh3
-rw-r--r--sh/shrc.d/sd.sh82
-rw-r--r--sh/shrc.d/ud.sh42
-rw-r--r--sh/shrc.d/vr.sh49
-rw-r--r--tmux/tmux.conf.m410
-rw-r--r--vim/after/ftplugin/sh.vim3
41 files changed, 607 insertions, 744 deletions
diff --git a/ISSUES.markdown b/ISSUES.markdown
index 68c6c482..c9a832d1 100644
--- a/ISSUES.markdown
+++ b/ISSUES.markdown
@@ -1,8 +1,6 @@
Known issues
============
-* vr() does not handle the newer version of Subversion repositories which
- only have a .svn directory at the root level.
* 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
@@ -19,3 +17,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+)$
diff --git a/Makefile b/Makefile
index 5d8369c9..49304591 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
@@ -157,7 +155,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 +185,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
diff --git a/README.markdown b/README.markdown
index 47197122..4108914d 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
@@ -165,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.
@@ -175,48 +175,44 @@ 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)`.
+* `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`.
* `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)`.
* `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`
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.
+* `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`:
-* `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,
- 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.
-* `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`.
-* `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
- repository.
#### Completion
@@ -398,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)`.
@@ -427,6 +425,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/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
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/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/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/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/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/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 <<EOF
-$FUNCNAME: Manage contents of PATH variable
-
-USAGE:
- $FUNCNAME h[elp]
- Print this help message (also done if command not found)
- $FUNCNAME l[ist]
- 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
- Add a directory to the end of PATH, checking for existence and uniqueness
- $FUNCNAME r[emove] DIR
- Remove all instances of a directory from PATH
-
-INTERNALS:
- $FUNCNAME s[et] [DIR1 [DIR2...]]
- Set the PATH to the given directories without checking existence or uniqueness
- $FUNCNAME c[heck] DIR
- Return whether DIR is a component of PATH
-
-EOF
- ;;
-
- # Print the current contents of the path
- list|l)
- local -a patharr
- IFS=: read -a patharr < <(printf '%s\n' "$PATH")
- if ((${#patharr[@]})) ; then
- printf '%s\n' "${patharr[@]}"
- fi
- ;;
-
- # Add a directory to the front of PATH, checking for existence and uniqueness
- insert|i)
- 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 insert\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 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/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/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 '' "$@"
-}
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/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/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/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
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
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/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/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/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 <tom@sanctum.geek.nz>
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 <tom@sanctum.geek.nz>
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/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
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
+)
diff --git a/sh/shrc.d/bd.sh b/sh/shrc.d/bd.sh
new file mode 100644
index 00000000..a5344ae7
--- /dev/null
+++ b/sh/shrc.d/bd.sh
@@ -0,0 +1,64 @@
+# 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 -- "$(
+
+ # 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:-..}
+
+ # 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 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 dd98a422..7bfacd6d 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 we ended up with something to change into
- [ -n "$new" ] || 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
- # 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 printed nothing, return with failure
+ [ -n "$2" ] || return
fi
# Execute the cd command as normal
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"
+}
diff --git a/sh/shrc.d/path.sh b/sh/shrc.d/path.sh
new file mode 100644
index 00000000..a5940dd7
--- /dev/null
+++ b/sh/shrc.d/path.sh
@@ -0,0 +1,95 @@
+# 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(): %s illegal colon\n' "$2"
+ return 2
+ ;;
+ esac
+
+ # Check first argument to figure out operation
+ case $1 in
+
+ # List current directories in PATH
+ list|'') (
+ # shellcheck disable=SC2030
+ 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' "$2"
+ 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' "$2"
+ 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' "$2"
+ 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) (
+ # shellcheck disable=SC2030
+ 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 >&2
+ return 2
+ ;;
+ esac
+}
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 -- "$@"
+}
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)")
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 -- "$@"
+}
diff --git a/sh/shrc.d/ud.sh b/sh/shrc.d/ud.sh
new file mode 100644
index 00000000..259f3167
--- /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 printed nothing, return with failure
+ [ -n "$1" ] || return
+
+ # Try to change into the determined directory
+ command cd -- "$@"
+}
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 -- "$@"
+}
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,
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