aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2017-01-02 21:12:32 +1300
committerTom Ryder <tom@sanctum.geek.nz>2017-01-02 21:12:32 +1300
commitada18e938861587d17bf5c02dbfcb943c3dfdd15 (patch)
tree4d9e153ea8a7cadef8abef356809139b0dd48b9f
parentMerge branch 'master' into port/bsd/netbsd (diff)
parentAdd comments to loc(1df) (diff)
downloaddotfiles-ada18e938861587d17bf5c02dbfcb943c3dfdd15.tar.gz
dotfiles-ada18e938861587d17bf5c02dbfcb943c3dfdd15.zip
Merge branch 'master' into port/bsd/netbsd
-rw-r--r--.gitignore2
-rw-r--r--IDEAS.markdown9
-rw-r--r--ISSUES.markdown9
-rw-r--r--Makefile21
-rw-r--r--README.markdown42
-rw-r--r--X/xbindkeysrc3
-rw-r--r--bash/bash_completion.d/md.bash2
-rwxr-xr-xbin/loc8
-rw-r--r--bin/max.awk10
-rw-r--r--bin/min.awk10
-rwxr-xr-xbin/pwg2
-rw-r--r--bin/rfct.awk4
-rwxr-xr-xbin/swr65
-rwxr-xr-xcheck/zsh6
-rw-r--r--ksh/kshrc.d/keep.ksh1
-rw-r--r--ksh/kshrc.d/prompt.ksh25
-rwxr-xr-xlint/bash2
-rwxr-xr-xlint/ksh4
-rwxr-xr-xlint/sh2
-rwxr-xr-xlint/yash2
-rw-r--r--man/man1/apf.1df4
-rw-r--r--man/man1/max.1df19
-rw-r--r--man/man1/min.1df19
-rw-r--r--man/man1/pwg.1df6
-rw-r--r--man/man1/swr.1df37
-rw-r--r--sh/profile8
-rw-r--r--sh/profile.d/os.sh3
-rw-r--r--sh/shrc.d/ad.sh9
-rw-r--r--sh/shrc.d/bd.sh7
-rw-r--r--sh/shrc.d/gd.sh18
-rw-r--r--sh/shrc.d/md.sh31
-rw-r--r--sh/shrc.d/pd.sh7
-rw-r--r--sh/shrc.d/pmd.sh8
-rw-r--r--sh/shrc.d/rd.sh7
-rw-r--r--sh/shrc.d/sd.sh7
-rw-r--r--sh/shrc.d/ud.sh7
-rw-r--r--sh/shrc.d/xd.sh24
-rw-r--r--vim/after/ftdetect/sh.vim2
-rw-r--r--zsh/profile.d/zsh.sh28
39 files changed, 424 insertions, 56 deletions
diff --git a/.gitignore b/.gitignore
index d7a435fa..110f8c4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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.
diff --git a/Makefile b/Makefile
index 30183494..60fec019 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/bin/loc b/bin/loc
index 7f4ac1fa..16d355f2 100755
--- a/bin/loc
+++ b/bin/loc
@@ -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
+}
diff --git a/bin/pwg b/bin/pwg
index 219de003..97af3df4 100755
--- a/bin/pwg
+++ b/bin/pwg
@@ -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"
;;
diff --git a/lint/bash b/lint/bash
index 9a9ad758..771f2a89 100755
--- a/lint/bash
+++ b/lint/bash
@@ -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 -- {} +
diff --git a/lint/ksh b/lint/ksh
index 5c5445fc..ee49d178 100755
--- a/lint/ksh
+++ b/lint/ksh
@@ -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 -- {} +
diff --git a/lint/sh b/lint/sh
index 7168a5bc..18f2f551 100755
--- a/lint/sh
+++ b/lint/sh
@@ -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 -- {} +
diff --git a/lint/yash b/lint/yash
index c2eacab3..1397f9f1 100755
--- a/lint/yash
+++ b/lint/yash
@@ -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>
diff --git a/sh/profile b/sh/profile
index 5d9c80bf..dc145d85 100644
--- a/sh/profile
+++ b/sh/profile
@@ -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