diff options
author | Tom Ryder <tom@sanctum.geek.nz> | 2016-08-20 18:22:20 +1200 |
---|---|---|
committer | Tom Ryder <tom@sanctum.geek.nz> | 2016-08-20 18:22:20 +1200 |
commit | 649f001206932c58412a7e37ef4e820fac593fde (patch) | |
tree | b793ae41135a34b556f5dc0a699d9216b06fdd25 | |
parent | Merge branch 'master' into freebsd (diff) | |
parent | Update path() completion (diff) | |
download | dotfiles-649f001206932c58412a7e37ef4e820fac593fde.tar.gz dotfiles-649f001206932c58412a7e37ef4e820fac593fde.zip |
Merge branch 'master' into freebsd
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+)$ @@ -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 -} @@ -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 @@ -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" +) @@ -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 |