diff options
author | Tom Ryder <tom@sanctum.geek.nz> | 2017-01-02 21:12:32 +1300 |
---|---|---|
committer | Tom Ryder <tom@sanctum.geek.nz> | 2017-01-02 21:12:32 +1300 |
commit | ada18e938861587d17bf5c02dbfcb943c3dfdd15 (patch) | |
tree | 4d9e153ea8a7cadef8abef356809139b0dd48b9f | |
parent | Merge branch 'master' into port/bsd/netbsd (diff) | |
parent | Add comments to loc(1df) (diff) | |
download | dotfiles-ada18e938861587d17bf5c02dbfcb943c3dfdd15.tar.gz dotfiles-ada18e938861587d17bf5c02dbfcb943c3dfdd15.zip |
Merge branch 'master' into port/bsd/netbsd
39 files changed, 424 insertions, 56 deletions
@@ -2,9 +2,11 @@ bin/csmw bin/ddup bin/gwp bin/han +bin/max bin/mean bin/med bin/mftl +bin/min bin/mode bin/rfct bin/rndi diff --git a/IDEAS.markdown b/IDEAS.markdown new file mode 100644 index 00000000..0a256473 --- /dev/null +++ b/IDEAS.markdown @@ -0,0 +1,9 @@ +Ideas +===== + +* I can probably share my psql() completions/shortcuts after sanitizing them + a bit +* sxhkd(1) might be nicer than xbindkeys; it's in Debian Testing now +* Wouldn't be too hard to add some HTTP BASIC auth to ix(1df) to make pastes + manageable +* Have eds(1df) accept stdin with the "starting content" for the script diff --git a/ISSUES.markdown b/ISSUES.markdown index 5d9d43d3..0b31661f 100644 --- a/ISSUES.markdown +++ b/ISSUES.markdown @@ -10,17 +10,12 @@ Known issues * The checks gscr(1df) makes to determine where it is are a bit naive (don't work with bare repos) and could probably be improved with some appropriate git-reflog(1) calls -* I can probably share my psql() completions/shortcuts after sanitizing them - a bit -* sxhkd(1) might be nicer than xbindkeys; it's in Debian Testing now * dr(1df) is probably more practical in awk * How come commands I fix with the fc builtin always seem to exit 1 even if they succeed? Did I do that or is it Bash? -* Wouldn't be too hard to add some HTTP BASIC auth to ix(1df) to make pastes - manageable -* On non-OBSD pdksh and mksh, !! comes out as literal !! after subshell - expansion; a version switch might be necessary * Running the block of git(1) commands in the prompt leaves five "stale" jobspecs around that flee after a jobs builtin run; only saw this manifest after 90dcadf; either I understand job specs really poorly or this may be a bug in bash +* md() does not handle e.g. "../..". If there's a tidy way of making it do so + that would probably be worhwhile. @@ -50,6 +50,7 @@ check-sh \ check-urxvt \ check-yash \ + check-zsh \ lint \ lint-bash \ lint-bin \ @@ -70,9 +71,11 @@ all : bin/csmw \ bin/ddup \ bin/gwp \ bin/han \ + bin/max \ bin/mean \ bin/med \ bin/mftl \ + bin/min \ bin/mode \ bin/rfct \ bin/rndi \ @@ -92,9 +95,11 @@ clean distclean : bin/ddup \ bin/gwp \ bin/han \ + bin/max \ bin/mean \ bin/med \ bin/mftl \ + bin/min \ bin/mode \ bin/rfct \ bin/rndi \ @@ -192,9 +197,9 @@ install-bash-completion : install-bash install -pm 0644 -- bash/bash_completion "$(HOME)"/.config/bash_completion install -pm 0644 -- bash/bash_completion.d/* "$(HOME)"/.bash_completion.d -install-bin : bin/csmw bin/ddup bin/gwp bin/han bin/mean bin/med bin/mftl \ - bin/mode bin/rfct bin/rndi bin/sd2u bin/sec bin/slsf bin/su2d bin/tot \ - bin/unf bin/uts install-bin-man +install-bin : bin/csmw bin/ddup bin/gwp bin/han bin/max bin/mean bin/med \ + bin/mftl bin/min bin/mode bin/rfct bin/rndi bin/sd2u bin/sec bin/slsf \ + bin/su2d bin/tot bin/unf bin/uts install-bin-man install -m 0755 -d -- "$(HOME)"/.local/bin for name in bin/* ; do \ [ -x "$$name" ] || continue ; \ @@ -388,8 +393,11 @@ install-yash : check-yash install-sh install -pm 0644 -- yash/yashrc "$(HOME)"/.yashrc install -pm 0644 -- yash/yashrc.d/* "$(HOME)"/.yashrc.d -install-zsh : install-sh - install -m 0755 -d -- "$(HOME)"/.zshrc.d +install-zsh : check-zsh install-sh + install -m 0755 -d -- \ + "$(HOME)"/.profile.d \ + "$(HOME)"/.zshrc.d + install -pm 0644 -- zsh/profile.d/* "$(HOME)"/.profile.d install -pm 0644 -- zsh/zprofile "$(HOME)"/.zprofile install -pm 0644 -- zsh/zshrc "$(HOME)"/.zshrc install -pm 0644 -- zsh/zshrc.d/* "$(HOME)"/.zshrc.d @@ -425,6 +433,9 @@ check-urxvt : check-yash : check/yash +check-zsh : + check/zsh + lint : check \ lint-bash \ lint-bin \ diff --git a/README.markdown b/README.markdown index 109af073..39bc3666 100644 --- a/README.markdown +++ b/README.markdown @@ -164,10 +164,27 @@ terminals. 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: -* `ad()` is a `cd` shortcut accepting targets like `/u/l/b` for - `/usr/local/bin`, as long as they are unique. +* Four functions for using a "marked" directory, which I find a more + manageable concept than the `pushd`/`popd` directory stack: + * `md()` marks a given (or the current) directory. + * `gd()` goes to the marked directory. + * `pmd()` prints the marked directory. + * `xd()` swaps the current and marked directories. +* Nine other directory management and navigation functions: + * `ad()` is a `cd` shortcut accepting targets like `/u/l/b` for + `/usr/local/bin`, as long as they are unique. + * `bd()` changes into a named ancestor of the current directory. + * `mkcd()` creates a directory and changes into it. + * `pd()` changes to the argument's parent directory. + * `rd()` replaces the first instance of its first argument with its + second argument in `$PWD`, emulating a feature of the Zsh `cd` builtin + that I like. + * `scr()` creates a temporary directory and changes into it. + * `sd()` changes into a sibling of the current directory. + * `ud()` changes into an indexed ancestor of a directory. + * `vr()` tries to change to the root directory of a source control + repository. * `bc()` silences startup messages from GNU `bc(1)`. -* `bd()` changes into a named ancestor of the current directory. * `ed()` tries to get verbose error messages, a prompt, and a Readline environment for `ed(1)`. * `env()` sorts the output of `env(1)` if it was invoked with no arguments, @@ -181,31 +198,22 @@ in `sh/shrc.d` to be loaded by any POSIX interactive shell. Those include: * `ls()` tries to apply color to `ls(1)` for interactive use if available. * `la()` runs `ls -A` if it can, or `ls -a` otherwise. * `ll()` runs `ls -Al` if it can, or `ls -al` otherwise. -* `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. -* `rd()` replaces the first instance of its first argument with its second - argument in `$PWD`, emulating a feature of the Zsh `cd` builtin that I - like. * `scp()` tries to detect forgotten hostnames in `scp(1)` command calls. -* `scr()` creates a temporary directory and changes into it. -* `sd()` changes into a sibling of the current directory. * `sudo()` forces `-H` for `sudo(8)` calls so that `$HOME` is never preserved; I hate having `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. * `tree()` colorizes GNU `tree(1)` output if possible (without having `LS_COLORS` set). -* `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. * `x()` is a one-key shortcut for `exec startx`. -There are a few other little tricks defined for other shells: +There are a few other little tricks defined for other shells providing +non-POSIX features, as compatibility allows: * `keep()` stores ad-hoc shell functions and variables (Bash, Korn Shell 93, Z shell). @@ -394,9 +402,11 @@ Installed by the `install-bin` target: * `unf(1df)` joins lines with leading spaces to the previous line. Intended for unfolding HTTP headers, but it should work for most RFC 822 formats. -* Four simple aggregators for numbers: +* Six simple aggregators for numbers: + * `max(1df)` prints the maximum. * `mean(1df)` prints the mean. * `med(1df)` prints the median. + * `min(1df)` prints the minimum. * `mode(1df)` prints the first encountered mode. * `tot(1df)` totals the set. * `ap(1df)` reads arguments for a given command from the standard input, @@ -479,6 +489,8 @@ Installed by the `install-bin` target: * `sue(8df)` execs `sudoedit(8)` as the owner of all the file arguments given, perhaps in cases where you may not necessarily have `root` `sudo(8)` privileges. +* `swr(1df)` allows you to run commands locally specifying remote files in + `scp(1)`'s HOST:PATH format. * `td(1df)` manages a to-do file for you with `$EDITOR` and `git(1)`; I used to use Taskwarrior, but found it too complex and buggy. * `try(1df)` repeats a command up to a given number of times until it diff --git a/X/xbindkeysrc b/X/xbindkeysrc index ee58eb35..22f1ce98 100644 --- a/X/xbindkeysrc +++ b/X/xbindkeysrc @@ -27,3 +27,6 @@ "exec amixer -q sset Master 5%- unmute" XF86AudioLowerVolume + +"exec urxvtcd -e bc" + XF86Calculator diff --git a/bash/bash_completion.d/md.bash b/bash/bash_completion.d/md.bash new file mode 100644 index 00000000..6193efe5 --- /dev/null +++ b/bash/bash_completion.d/md.bash @@ -0,0 +1,2 @@ +# Completion for md() +complete -A directory md @@ -1,9 +1,17 @@ #!/bin/sh +# Convenience find(1) wrapper for path substrings + +# Require at least one search term if [ "$#" -eq 0 ] ; then printf >&2 'loc: Need a search term\n' exit 2 fi + +# Iterate through each search term and run an appropriate find(1) command for pat ; do + + # Skip dotfiles and dotdirs, print anything that matches the term as a + # substring (and stop iterating through it) find . \ -name .\* ! -name . -prune -o \ -name \*"$pat"\* -prune -print diff --git a/bin/max.awk b/bin/max.awk new file mode 100644 index 00000000..11d4efd9 --- /dev/null +++ b/bin/max.awk @@ -0,0 +1,10 @@ +# Get the maximum of a list of numbers +{ + if (NR == 1 || $1 > max) + max = $1 +} +END { + if (!NR) + exit(1) + print max +} diff --git a/bin/min.awk b/bin/min.awk new file mode 100644 index 00000000..c58a3a8f --- /dev/null +++ b/bin/min.awk @@ -0,0 +1,10 @@ +# Get the minimum of a list of numbers +{ + if (NR == 1 || $1 < min) + min = $1 +} +END { + if (!NR) + exit(1) + print min +} @@ -2,6 +2,6 @@ # Shortcut to generate just one strong password with pwgen(1) # If any arguments are provided, those are used instead if [ "$#" -eq 0 ] ; then - set -- --secure -- "${PWGEN_LENGTH:-15}" "${PWGEN_COUNT:-1}" + set -- --secure -- "${PWGEN_LENGTH:-16}" "${PWGEN_COUNT:-1}" fi pwgen "$@" diff --git a/bin/rfct.awk b/bin/rfct.awk index 2f0cc42d..ac8a2a87 100644 --- a/bin/rfct.awk +++ b/bin/rfct.awk @@ -4,5 +4,5 @@ BEGIN { RS = "" } # Print the block followed by two newlines, as long as it has at least one -# alphanumeric character and no pagebreak characters -/[a-zA-Z0-9]/ && !/\x0c/ { printf "%s\n\n", $0 } +# alphanumeric character and no pagebreak (^L, 0x0C) characters +/[a-zA-Z0-9]/ && !// { printf "%s\n\n", $0 } diff --git a/bin/swr b/bin/swr new file mode 100755 index 00000000..56ab5919 --- /dev/null +++ b/bin/swr @@ -0,0 +1,65 @@ +#!/bin/sh +# Transparently wrap scp(1) targets on the command line +self=swr + +# Create a temporary directory with name in $td, and handle POSIX-ish traps to +# remove it when the script exits. +td= +cleanup() { + [ -n "$td" ] && rm -fr -- "$td" + if [ "$1" != EXIT ] ; then + trap - "$1" + kill "-$1" "$$" + fi +} +for sig in EXIT HUP INT TERM ; do + # shellcheck disable=SC2064 + trap "cleanup $sig" "$sig" +done +td=$(mktd "$self") || exit + +# Set a flag to manage resetting the positional parameters at the start of the +# loop +n=1 +for arg ; do + + # If this is our first iteration, reset the shell parameters + case $n in + 1) set -- ;; + esac + + # Test whether this argument looks like a remote file + if ( + + # Test it contains a colon + case $arg in + *:*) ;; + *) exit 1 ;; + esac + + # Test the part before the first colon has at least one character and + # only hostname characters + case ${arg%%:*} in + '') exit 1 ;; + *[!a-zA-Z0-9-.]*) exit 1 ;; + esac + + ) ; then + + # Looks like a remote file request; try to copy it into the temporary + # directory, bail out completely if we can't + dst=$td/$n + scp -q -- "$arg" "$dst" || exit + set -- "$@" "$dst" + + else + # Just a plain old argument; stack it up + set -- "$@" "$arg" + fi + + # Bump n + n=$((n+1)) +done + +# Run the command with the processed arguments +exec "$@" diff --git a/check/zsh b/check/zsh new file mode 100755 index 00000000..39a6c1e9 --- /dev/null +++ b/check/zsh @@ -0,0 +1,6 @@ +#!/bin/sh +for zsh in zsh/* zsh/zshrc.d/* ; do + [ -f "$zsh" ] || continue + zsh -n "$zsh" || exit +done +printf 'All zsh(1) scripts parsed successfully.\n' diff --git a/ksh/kshrc.d/keep.ksh b/ksh/kshrc.d/keep.ksh index c57c41ce..0451fa68 100644 --- a/ksh/kshrc.d/keep.ksh +++ b/ksh/kshrc.d/keep.ksh @@ -142,7 +142,6 @@ EOF # Otherwise the user must want us to print all the NAMEs kept ( - typeset keep for keep in "$kshkeep"/*.ksh ; do [[ -f "$keep" ]] || break keep=${keep##*/} diff --git a/ksh/kshrc.d/prompt.ksh b/ksh/kshrc.d/prompt.ksh index add96b2a..84129efc 100644 --- a/ksh/kshrc.d/prompt.ksh +++ b/ksh/kshrc.d/prompt.ksh @@ -136,8 +136,28 @@ function prompt { state=${state}'>' # Tracked files are modified - git diff-files --no-ext-diff --quiet || - state=${state}'!!' + if ! git diff-files --no-ext-diff --quiet ; then + + # Different ksh flavours process a bang in PS1 after prompt + # parameter expansion in different ways + case $KSH_VERSION in + + # ksh93 requires a double-bang to escape it + (*'93'*) state=${state}'!!' ;; + + # OpenBSD's pdksh requires a double-bang too, but its + # upstream does not + (*'PD KSH'*) + case $OS in + ('OpenBSD') state=${state}'!!' ;; + (*) state=${state}'!' ;; + esac + ;; + + # Everything else should need only one bang + (*) state=${state}'!' ;; + esac + fi # Changes are staged git diff-index --cached --no-ext-diff --quiet HEAD || @@ -192,6 +212,7 @@ function prompt { # Show the count of background jobs in curly brackets, if not zero job) + # shellcheck disable=SC2154 ((jobc)) && printf '{%u}' "$jobc" ;; @@ -1,2 +1,2 @@ #!/bin/sh -find bash -type f -print -exec shellcheck -e SC1090 -s bash -- {} \; +find bash -type f -print -exec shellcheck -e SC1090 -s bash -- {} + @@ -1,2 +1,4 @@ #!/bin/sh -find ksh -type f -print -exec shellcheck -e SC1090 -s ksh -- {} \; +find ksh \ + -type f -name '*.sh' -exec shellcheck -e SC1090 -s sh -- {} + -o \ + -type f -exec shellcheck -e SC1090 -s ksh -- {} + @@ -1,2 +1,2 @@ #!/bin/sh -find sh -type f -print -exec shellcheck -e SC1090 -s sh -- {} \; +find sh -type f -print -exec shellcheck -e SC1090 -s sh -- {} + @@ -1,2 +1,2 @@ #!/bin/sh -find yash -type f -print -exec shellcheck -e SC1090 -s sh -- {} \; +find yash -type f -print -exec shellcheck -e SC1090 -s sh -- {} + diff --git a/man/man1/apf.1df b/man/man1/apf.1df index 6f8804e0..025af010 100644 --- a/man/man1/apf.1df +++ b/man/man1/apf.1df @@ -44,7 +44,7 @@ We could do this: b c .P -We could then make a permanent wrapper script in two line: +We could then make a permanent wrapper script in two lines: .P $ cat >~/.local/bin/printargs #!/bin/sh @@ -52,7 +52,7 @@ We could then make a permanent wrapper script in two line: ^D $ chmod +x ~/.local/bin/printargs .P -Or just a shell function, if it's needed interactively: +Or just a shell function, if it's only wanted for interactive shells: .P $ printargs() { apf "$HOME"/.printargsrc printargs "$@" ; } .P diff --git a/man/man1/max.1df b/man/man1/max.1df new file mode 100644 index 00000000..28431da4 --- /dev/null +++ b/man/man1/max.1df @@ -0,0 +1,19 @@ +.TH MAX 1df "September 2016" "Manual page for max" +.SH NAME +.B max +\- print the maximum of a list of numbers +.SH SYNOPSIS +printf '%u\\n' 3 55 17 | +.B max +.br +.B max +file +.br +.B max +file1 file2 +.SH DESCRIPTION +.B max +collects all the newline-delimited numbers given as input, and prints the +maximum. +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man1/min.1df b/man/man1/min.1df new file mode 100644 index 00000000..e3279d3b --- /dev/null +++ b/man/man1/min.1df @@ -0,0 +1,19 @@ +.TH MIN 1df "September 2016" "Manual page for min" +.SH NAME +.B min +\- print the minimum of a list of numbers +.SH SYNOPSIS +printf '%u\\n' 182 2 22 | +.B min +.br +.B min +file +.br +.B min +file1 file2 +.SH DESCRIPTION +.B min +collects all the newline-delimited numbers given as input, and prints the +minimum. +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man1/pwg.1df b/man/man1/pwg.1df index 3bc2e777..0d1c243c 100644 --- a/man/man1/pwg.1df +++ b/man/man1/pwg.1df @@ -7,9 +7,9 @@ .SH DESCRIPTION .B pwg with no arguments runs pwgen(1) with the --secure options and arguments for one -password of fifteen alphanumeric characters, to avoid pwgen(1)'s unusual -default of generating very many relatively low-quality passwords, which is -never what the author wants. +password of 16 alphanumeric characters, to avoid pwgen(1)'s unusual default of +generating very many relatively low-quality passwords, which is never what the +author wants. .P If any arguments are provided, it simply passes these to pwgen(1) untouched. .SH AUTHOR diff --git a/man/man1/swr.1df b/man/man1/swr.1df new file mode 100644 index 00000000..8792b0ed --- /dev/null +++ b/man/man1/swr.1df @@ -0,0 +1,37 @@ +.TH SWR 1df "January 2017" "Manual page for swr" +.SH NAME +.B swr +\- run a command including remote file arguments for scp(1) retrieval +.SH SYNOPSIS +.B swr +cat remote:.ssh/authorized_keys +.br +.B swr +diff .shrc remote:.shrc +.SH DESCRIPTION +.B swr +runs the command given in its arguments, first replacing any arguments in the +form HOST:PATH with copies of the specified files as retrieved with scp(1), +copied into a temporary directory that should be removed on exit under most +circumstances. +.P +This even works for the first argument (i.e. the command), provided that it +will run on the local system once copied in. +.SH CAVEATS +This only works for simple commands; you can't put shell syntax into any of the +arguments. +.P +The whole script will stop if even one of its arguments can't be copied in, as +there's no way to tell whether it's safe to proceed without some of the data. +.P +Don't even think about using this for mission-critical cases or situations +requiring high security. It's a convenience wrapper. +.P +You may not need this at all if your shell has working command substitution and +you find its syntax clearer: +.P + diff .shrc <(ssh remote 'cat .shrc') +.SH SEE ALSO +scp(1), mktd(1df) +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> @@ -12,3 +12,11 @@ if [ -f "$HOME"/.shinit ] ; then ENV=$HOME/.shinit export ENV fi + +# If ENV_FORCE is set and we're interactive, source ENV explicitly +# At the moment this is just for zsh-as-ksh/sh +if [ -n "$ENV_FORCE" ] ; then + case $- in *i*) + [ -f "$ENV" ] && . "$ENV" ;; + esac +fi diff --git a/sh/profile.d/os.sh b/sh/profile.d/os.sh new file mode 100644 index 00000000..f9d5a79b --- /dev/null +++ b/sh/profile.d/os.sh @@ -0,0 +1,3 @@ +# Store the operating system in an environment variable +OS=$(uname) +export OS diff --git a/sh/shrc.d/ad.sh b/sh/shrc.d/ad.sh index d3e5a90a..55866683 100644 --- a/sh/shrc.d/ad.sh +++ b/sh/shrc.d/ad.sh @@ -15,7 +15,7 @@ ad() { req=${1%/}/ case $req in (/*) ;; - (*) req=${PWD%/}/${req#/}/ ;; + (*) req=${PWD%/}/${req#/} ;; esac # Start building the target directory; go through the request piece by @@ -65,10 +65,13 @@ ad() { done - # Print the target - printf '%s\n' "$dir" + # Print the target with trailing slash to work around newline stripping + printf '%s/' "${dir%/}" )" + # Remove trailing slash + set -- "${1%/}" + # If the subshell printed nothing, return with failure [ -n "$1" ] || return diff --git a/sh/shrc.d/bd.sh b/sh/shrc.d/bd.sh index 1043a8cb..bf64a9aa 100644 --- a/sh/shrc.d/bd.sh +++ b/sh/shrc.d/bd.sh @@ -55,10 +55,13 @@ bd() { exit 1 fi - # Print the target - printf '%s\n' "$dirname" + # Print the target with trailing slash to work around newline stripping + printf '%s/' "${dirname%/}" )" + # Remove trailing slash + set -- "${1%/}" + # If the subshell printed nothing, return with failure [ -n "$1" ] || return diff --git a/sh/shrc.d/gd.sh b/sh/shrc.d/gd.sh new file mode 100644 index 00000000..5a3f54b0 --- /dev/null +++ b/sh/shrc.d/gd.sh @@ -0,0 +1,18 @@ +# Go to marked directory +gd() { + + # Refuse to deal with unwanted arguments + if [ "$#" -gt 0 ] ; then + printf >&2 'gd(): Unspecified argument\n' + return 2 + fi + + # Complain if mark not actually set yet + if ! [ -n "$PMD" ] ; then + printf >&2 'gd(): Mark not set\n' + return 2 + fi + + # Go to the marked directory + cd -- "$PMD" || return +} diff --git a/sh/shrc.d/md.sh b/sh/shrc.d/md.sh new file mode 100644 index 00000000..7085d258 --- /dev/null +++ b/sh/shrc.d/md.sh @@ -0,0 +1,31 @@ +# Set marked directory to given dir or current dir +md() { + + # Accept up to one argument + if [ "$#" -gt 1 ] ; then + printf >&2 'md(): Too many arguments\n' + return 2 + fi + + # If first arg unset or empty, assume the user means the current dir + [ -n "$1" ] || set -- "$PWD" + + # If specified path is . or .., quietly expand it + case $1 in + .) set -- "${PWD%/}" ;; + ..) + set -- "${PWD%/}" + set -- "${1%/*}" + ;; + esac + + # If specified path not a directory, refuse to mark it + if ! [ -d "$1" ] ; then + printf >&2 'md(): Not a directory\n' + return 2 + fi + + # Save the specified path in the marked directory var + # shellcheck disable=SC2034 + PMD=$1 +} diff --git a/sh/shrc.d/pd.sh b/sh/shrc.d/pd.sh index de4ea23b..ce43837b 100644 --- a/sh/shrc.d/pd.sh +++ b/sh/shrc.d/pd.sh @@ -25,10 +25,13 @@ pd() { exit 1 fi - # Print the target - printf '%s\n' "$dirname" + # Print the target with trailing slash to work around newline stripping + printf '%s/' "${dirname%/}" )" + # Remove trailing slash + set -- "${1%/}" + # If the subshell printed nothing, return with failure [ -n "$1" ] || return diff --git a/sh/shrc.d/pmd.sh b/sh/shrc.d/pmd.sh new file mode 100644 index 00000000..03f18b7b --- /dev/null +++ b/sh/shrc.d/pmd.sh @@ -0,0 +1,8 @@ +# Print the marked directory +pmd() { + if ! [ -n "$PMD" ] ; then + printf >&2 'pmd(): Mark not set\n' + return 2 + fi + printf '%s\n' "$PMD" +} diff --git a/sh/shrc.d/rd.sh b/sh/shrc.d/rd.sh index 9fd99a55..3b699c0d 100644 --- a/sh/shrc.d/rd.sh +++ b/sh/shrc.d/rd.sh @@ -51,10 +51,13 @@ rd() { exit 1 fi - # Print the target - printf '%s\n' "$new" + # Print the target with trailing slash to work around newline stripping + printf '%s/' "${new%/}" )" + # Remove trailing slash + set -- "${1%/}" + # If the subshell printed nothing, return with failure [ -n "$1" ] || return diff --git a/sh/shrc.d/sd.sh b/sh/shrc.d/sd.sh index ce59bf99..4d63b7d6 100644 --- a/sh/shrc.d/sd.sh +++ b/sh/shrc.d/sd.sh @@ -72,10 +72,13 @@ sd() { ;; esac - # Print the target - printf '%s\n' "$1" + # Print the target with trailing slash to work around newline stripping + printf '%s/' "${1%/}" )" + # Remove trailing slash + set -- "${1%/}" + # If the subshell printed nothing, return with failure [ -n "$1" ] || return diff --git a/sh/shrc.d/ud.sh b/sh/shrc.d/ud.sh index 44a3a81d..79f4b5e7 100644 --- a/sh/shrc.d/ud.sh +++ b/sh/shrc.d/ud.sh @@ -34,10 +34,13 @@ ud() { exit 1 fi - # Print the target - printf '%s\n' "$dirname" + # Print the target with trailing slash to work around newline stripping + printf '%s/' "${dirname%/}" )" + # Remove trailing slash + set -- "${1%/}" + # If the subshell printed nothing, return with failure [ -n "$1" ] || return diff --git a/sh/shrc.d/xd.sh b/sh/shrc.d/xd.sh new file mode 100644 index 00000000..01b8fd3a --- /dev/null +++ b/sh/shrc.d/xd.sh @@ -0,0 +1,24 @@ +# Swap current directory with marked directory +xd() { + + # Refuse to deal with unwanted arguments + if [ "$#" -gt 0 ] ; then + printf >&2 'gd(): Unspecified argument\n' + return 2 + fi + + # Complain if mark not actually set yet + if ! [ -n "$PMD" ] ; then + printf >&2 'gd(): Mark not set\n' + return 2 + fi + + # Put the current and marked directories into positional params + set -- "$PMD" "$PWD" + + # Try to change into the marked directory + cd -- "$1" || return + + # If that worked, we can swap the mark, and we're done + PMD=$2 +} diff --git a/vim/after/ftdetect/sh.vim b/vim/after/ftdetect/sh.vim index c974e293..f2bc0df2 100644 --- a/vim/after/ftdetect/sh.vim +++ b/vim/after/ftdetect/sh.vim @@ -1,4 +1,4 @@ -" Add automatic commands to +" Add automatic commands to choose shell flavours based on filename pattern augroup dfsh " Names/paths of things that are Bash shell script diff --git a/zsh/profile.d/zsh.sh b/zsh/profile.d/zsh.sh new file mode 100644 index 00000000..47de6d4d --- /dev/null +++ b/zsh/profile.d/zsh.sh @@ -0,0 +1,28 @@ +# Zsh before version 5.3.0 emulating POSIX sh(1) or Korn shell only sources the +# interactive shell startup file described in ENV if it's set after +# /etc/profile is sourced, but before ~/.profile is. The other shells I have +# tried (including modern shells emulating POSIX sh(1)) wait until after +# ~/.profile is read. This seems to have been fixed in Zsh commit ID fde365e, +# which was followed by release 5.3.0. + +# Is this zsh masquerading as sh/ksh? +[ -n "$ZSH_VERSION" ] || return +case $ZSH_NAME in + sh|ksh) ;; + *) return ;; +esac + +# Iterate through the zsh version number to see if it's at least 5.3.0; if not, +# we'll have ~/.profile force sourcing $ENV +if ! ( + zvs=$ZSH_VERSION + for fv in 5 3 0 ; do + zv=${zvs%%[!0-9]*} + [ "$((zv > fv))" -eq 1 ] && exit 0 + [ "$((zv < fv))" -eq 1 ] && exit 1 + zvs=${zvs#*.} + [ -n "$zvs" ] || exit 0 + done +) ; then + ENV_FORCE=1 +fi |