diff options
author | Tom Ryder <tom@sanctum.geek.nz> | 2016-12-14 11:41:35 +1300 |
---|---|---|
committer | Tom Ryder <tom@sanctum.geek.nz> | 2016-12-14 11:41:35 +1300 |
commit | cd7a151621ec072238882d8379950eb99eddac2d (patch) | |
tree | e6aaa337de415bc9bee766ee2fb93d00a47cb174 | |
parent | Add gwp(1) (diff) | |
parent | Add completion for ad() (diff) | |
download | dotfiles-cd7a151621ec072238882d8379950eb99eddac2d.tar.gz dotfiles-cd7a151621ec072238882d8379950eb99eddac2d.zip |
Merge branch 'master' into freebsd
49 files changed, 577 insertions, 188 deletions
@@ -9,15 +9,18 @@ bin/mode bin/rfct bin/rndi bin/sd2u +bin/sec bin/slsf bin/su2d bin/tot bin/unf +bin/uts games/acq games/aesth games/drakon games/kvlt games/rot13 +games/strik games/zs git/gitconfig gnupg/gpg.conf @@ -77,10 +77,12 @@ all : bin/csmw \ bin/rfct \ bin/rndi \ bin/sd2u \ + bin/sec \ bin/slsf \ bin/su2d \ bin/tot \ bin/unf \ + bin/uts \ git/gitconfig \ gnupg/gpg.conf @@ -97,15 +99,18 @@ clean distclean : bin/rfct \ bin/rndi \ bin/sd2u \ + bin/sec \ bin/slsf \ bin/su2d \ bin/tot \ bin/unf \ + bin/uts \ games/acq \ games/aesth \ games/drakon \ games/kvlt \ games/rot13 \ + games/strik \ games/zs \ git/gitconfig \ gnupg/gpg.conf \ @@ -188,8 +193,8 @@ install-bash-completion : install-bash 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/slsf bin/su2d bin/tot bin/unf \ - install-bin-man + 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 ; \ @@ -219,8 +224,8 @@ install-finger : install -pm 0644 -- finger/project "$(HOME)"/.project install -pm 0644 -- finger/pgpkey "$(HOME)"/.pgpkey -install-games : games/acq games/aesth games/drakon games/kvlt games/rot13 games/zs \ - check-games install-games-man +install-games : games/acq games/aesth games/drakon games/kvlt games/rot13 \ + games/strik games/zs check-games install-games-man install -m 0755 -d -- "$(HOME)"/.local/games for name in games/* ; do \ [ -x "$$name" ] || continue ; \ diff --git a/README.markdown b/README.markdown index 5eae69ac..f4c73817 100644 --- a/README.markdown +++ b/README.markdown @@ -114,7 +114,7 @@ older syntax for certain things such as appending items to arrays: array[${#array[@]}]=$item Compare this to the much nicer syntax available since 3.1-alpha1, which -actually works for arrays with sparse indexes, unlike the above syntax: +actually works for arrays with sparse indices, unlike the above syntax: array+=("$item") @@ -167,6 +167,9 @@ 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, emulating a feature of the + Zsh `cd` builtin that I like. * `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)`. @@ -419,6 +422,8 @@ Installed by the `install-bin` target: * `cf(1df)` prints a count of entries in a given directory. * `cfr(1df)` does the same as `cf(1df)`, but recurses into subdirectories as well. +* `chc(1df)` caches the output of a command. +* `clog(1df)` is a tiny timestamped log system. * `clrd(1df)` sets up a per-line file read, clearing the screen first. * `clwr(1df)` sets up a per-line file write, clearing the screen before each line @@ -454,6 +459,7 @@ Installed by the `install-bin` target: * `jfc(1df)` adds and commits lazily to a Git repository. * `jfcd(1df)` watches a directory for changes and runs `jfc(1df)` if it sees any. +* `loc(1df)` is a quick-search wrapped around `find(1)`. * `maybe(1df)` is like `true(1)` or `false(1)`; given a probability of success, it exits with success or failure. Good for quick tests. @@ -472,6 +478,7 @@ Installed by the `install-bin` target: * `rgl(1df)` is a very crude interactive `grep(1)` loop. * `shb(1df)` attempts to build shebang lines for scripts from the system paths. +* `sec(1df)` converts `hh:mm:ss` or `mm:ss` timestamps to seconds * `spr(1df)` posts its input to the sprunge.us pastebin. * `sqs(1df)` chops off query strings from filenames, usually downloads. * `sshi(1df)` prints human-readable SSH connection details. @@ -486,6 +493,8 @@ Installed by the `install-bin` target: tolerating blips or temporary failures in `cron(8)` scripts. * `umake(1df)` iterates upwards through the directory tree from `$PWD` until it finds a Makefile for which to run `make(1)` with the given arguments. +* `uts(1df)` gets the current UNIX timestamp in an unorthodox way that should + work on all POSIX-compliant operating systems. There's some silly stuff in `install-games`: @@ -497,6 +506,7 @@ There's some silly stuff in `install-games`: * `kvlt(6df)` translates input to emulate a style of typing unique to black metal communities on the internet. * `rndn(6df)` implements an esoteric random number generation algorithm. +* `strik(6df)` outputs s̶t̶r̶i̶k̶e̶d̶ ̶o̶u̶t̶ struck out text. * `rot13(6df)` rotates the Latin letters in its input. * `xyzzy(6df)` teleports to a marked location on the filesystem. * `zs(6df)` prepends "z" case-appropriately to every occurrence of "s" in the diff --git a/bash/bash_completion.d/_ssh_config_hosts.bash b/bash/bash_completion.d/_ssh_config_hosts.bash index 02e9af06..8f45c412 100644 --- a/bash/bash_completion.d/_ssh_config_hosts.bash +++ b/bash/bash_completion.d/_ssh_config_hosts.bash @@ -14,6 +14,7 @@ _ssh_config_hosts() { done # Generate completion reply + local host for host in "${hosts[@]}" ; do [[ $host == "${COMP_WORDS[COMP_CWORD]}"* ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$host diff --git a/bash/bash_completion.d/_text_filenames.bash b/bash/bash_completion.d/_text_filenames.bash index 577d082a..09d831a5 100644 --- a/bash/bash_completion.d/_text_filenames.bash +++ b/bash/bash_completion.d/_text_filenames.bash @@ -8,6 +8,7 @@ # the thing I want, and I want it to stay fast. # _text_filenames() { + local item while IFS= read -r item ; do # Exclude blanks @@ -66,8 +67,8 @@ _text_filenames() { # Other known binary extensions # (I haven't included .com; on UNIX, that's more likely to be # something I saved from a website and named after the domain) - *.a|*.dat|*.drv|*.exe|*.o|*.torrent|*.wad|*.rom) ;; - *.A|*.DAT|*.DRV|*.EXE|*.O|*.TORRENT|*.WAD|*.ROM) ;; + *.a|*.drv|*.exe|*.o|*.torrent|*.wad|*.rom) ;; + *.A|*.DRV|*.EXE|*.O|*.TORRENT|*.WAD|*.ROM) ;; # Complete everything else; some of it will still be binary *) COMPREPLY[${#COMPREPLY[@]}]=$item ;; diff --git a/bash/bash_completion.d/ad.bash b/bash/bash_completion.d/ad.bash new file mode 100644 index 00000000..390fcb00 --- /dev/null +++ b/bash/bash_completion.d/ad.bash @@ -0,0 +1,2 @@ +# Completion function for ad(); just directories +complete -A directory ad diff --git a/bash/bash_completion.d/eds.bash b/bash/bash_completion.d/eds.bash index 3829e157..58ecf402 100644 --- a/bash/bash_completion.d/eds.bash +++ b/bash/bash_completion.d/eds.bash @@ -4,6 +4,7 @@ _eds() { local edspath edspath=${EDSPATH:-"$HOME"/.local/bin} [[ -d $edspath ]] || return + local executable while IFS= read -rd '' executable ; do [[ -n $executable ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$executable diff --git a/bash/bash_completion.d/find.bash b/bash/bash_completion.d/find.bash index 74dc17ad..007a83bd 100644 --- a/bash/bash_completion.d/find.bash +++ b/bash/bash_completion.d/find.bash @@ -7,6 +7,7 @@ _find() { # Backtrack through words so far; if none of them look like options, we're # still completing directory names + local i local -i opts for ((i = COMP_CWORD; i >= 0; i--)) ; do case ${COMP_WORDS[i]} in @@ -26,6 +27,7 @@ _find() { compopt -o bashdefault -o default # Iterate through whatever the subshell gives us; don't add blank items, though + local item while read -r item ; do [[ -n $item ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$item diff --git a/bash/bash_completion.d/keep.bash b/bash/bash_completion.d/keep.bash index 41423af0..7148ad2b 100644 --- a/bash/bash_completion.d/keep.bash +++ b/bash/bash_completion.d/keep.bash @@ -25,6 +25,7 @@ _keep() { # Complete with appropriate mode case $mode in names) + local word while IFS= read -r word ; do [[ -n $word ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$word @@ -32,6 +33,7 @@ _keep() { -- "${COMP_WORDS[COMP_CWORD]}") ;; kept) + local word while IFS= read -r word ; do [[ -n $word ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$word diff --git a/bash/bash_completion.d/kill.bash b/bash/bash_completion.d/kill.bash index 0aca041f..dccc926b 100644 --- a/bash/bash_completion.d/kill.bash +++ b/bash/bash_completion.d/kill.bash @@ -1,6 +1,7 @@ # Complete kill builtin with jobspecs (prefixed with % so it will accept them) # and this user's PIDs (requires pgrep(1)) _kill() { + local pid while read -r pid ; do case $pid in "${COMP_WORDS[COMP_CWORD]}"*) diff --git a/bash/bash_completion.d/make.bash b/bash/bash_completion.d/make.bash index 67c577f3..b3314e21 100644 --- a/bash/bash_completion.d/make.bash +++ b/bash/bash_completion.d/make.bash @@ -10,6 +10,7 @@ _make() { [[ -n $mf ]] || return 1 # Iterate through the Makefile, line by line + local line while IFS= read -r line ; do case $line in diff --git a/bash/bash_completion.d/man.bash b/bash/bash_completion.d/man.bash index 9861e9dd..efc93153 100644 --- a/bash/bash_completion.d/man.bash +++ b/bash/bash_completion.d/man.bash @@ -8,6 +8,12 @@ _man() { local word word=${COMP_WORDS[COMP_CWORD]} + # Don't bother if the word has slashes in it, the user is probably trying + # to complete an actual path + case $word in + */*) return 1 ;; + esac + # If this is the second word, and the previous word started with a number, # we'll assume that's the section to search local section subdir @@ -18,6 +24,7 @@ _man() { # Read completion results from a subshell and add them to the COMPREPLY # array individually + local page while IFS= read -rd '' page ; do [[ -n $page ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$page diff --git a/bash/bash_completion.d/sd.bash b/bash/bash_completion.d/sd.bash index 0c59f544..aeee1615 100644 --- a/bash/bash_completion.d/sd.bash +++ b/bash/bash_completion.d/sd.bash @@ -8,6 +8,7 @@ _sd() { [[ $PWD != / ]] || return 1 # Build list of matching sibiling directories + local dirname while IFS= read -rd '' dirname ; do [[ -n $dirname ]] || continue COMPREPLY[${#COMPREPLY[@]}]=$dirname diff --git a/bash/bash_completion.d/td.bash b/bash/bash_completion.d/td.bash index 6d995d0e..eb29992b 100644 --- a/bash/bash_completion.d/td.bash +++ b/bash/bash_completion.d/td.bash @@ -2,6 +2,7 @@ _td() { local dir dir=${TODO_DIR:-"$HOME"/Todo} + local fn while IFS= read -rd '' fn ; do COMPREPLY[${#COMPREPLY[@]}]=$fn done < <( diff --git a/bin/chc b/bin/chc new file mode 100755 index 00000000..b1e4000d --- /dev/null +++ b/bin/chc @@ -0,0 +1,31 @@ +#!/bin/sh +# Cache the output of a command and emit it straight from the cache if not +# expired on each run + +# First argument is the cache path, second is the duration in seconds +cac=$1 dur=$2 +shift 2 + +# Get the current timestamp with uts(1df) +uts=$(uts) || exit + +# Function checks cache exists, is readable, and not expired +fresh() { + [ -f "$cac" ] || return + [ -r "$cac" ] || return + exp=$(sed 1q -- "$cac") || return + [ "$((exp > uts))" -eq 1 ] +} + +# Write runs the command and writes it to the cache +write() { + exp=$((uts + dur)) + printf '%u\n' "$exp" + "$@" +} + +# If the cache isn't fresh, try to write a new one, or bail out +fresh "$cac" || write "$@" > "$cac" || exit + +# Emit the content (exclude the first line, which is the timestamp) +sed 1d -- "$cac" diff --git a/bin/clog b/bin/clog new file mode 100755 index 00000000..d52ab4c5 --- /dev/null +++ b/bin/clog @@ -0,0 +1,7 @@ +#!/bin/sh +# Record a timestamped message to a logfile, defaulting to ~/.clog +{ + date + cat - + printf '%s\n' -- +} >>"${CLOG:-"$HOME"/.clog}" diff --git a/bin/ddup.awk b/bin/ddup.awk index 28b4d135..2dec1d00 100644 --- a/bin/ddup.awk +++ b/bin/ddup.awk @@ -1,2 +1,2 @@ -#!/usr/bin/awk -f +# Skip duplicate lines (without requiring sorted input) !seen[$0]++ diff --git a/bin/gwp.awk b/bin/gwp.awk index db2764ec..32fe97f2 100644 --- a/bin/gwp.awk +++ b/bin/gwp.awk @@ -1,12 +1,11 @@ -#!/usr/bin/awk -f # Search for alphanumeric words in a file BEGIN { # Name self self = "gwp" - # Words are separated by any non-alphanumeric character - FS = "[^[:alnum:]]" + # Words are separated by any non-alphanumeric characters + FS = "[^a-zA-Z0-9]+" # First argument is the word required; push its case downward so we can # match case-insensitively @@ -52,6 +51,4 @@ function fnpr() { } # Exit zero if we found at least one match, non-zero otherwise -END { - exit(!found) -} +END { exit(!found) } diff --git a/bin/han.bash b/bin/han.bash index 8536b61b..97dc3a19 100644 --- a/bin/han.bash +++ b/bin/han.bash @@ -21,7 +21,7 @@ fi # the script exits td= cleanup() { - [[ -n "$td" ]] && rm -fr -- "$td" + [[ -n $td ]] && rm -fr -- "$td" } trap cleanup EXIT td=$(mktd "$self") || exit diff --git a/bin/loc b/bin/loc new file mode 100755 index 00000000..7f4ac1fa --- /dev/null +++ b/bin/loc @@ -0,0 +1,10 @@ +#!/bin/sh +if [ "$#" -eq 0 ] ; then + printf >&2 'loc: Need a search term\n' + exit 2 +fi +for pat ; do + find . \ + -name .\* ! -name . -prune -o \ + -name \*"$pat"\* -prune -print +done diff --git a/bin/med.awk b/bin/med.awk index 83f0eb74..8167f8dd 100644 --- a/bin/med.awk +++ b/bin/med.awk @@ -7,11 +7,10 @@ END { # Error out if we read no values at all if (!NR) exit(1) - if (NR % 2) { + if (NR % 2) med = vals[(NR+1)/2] - } else { + else med = (vals[NR/2] + vals[NR/2+1]) / 2 - } print med if (warn) exit(1) diff --git a/bin/mftl.awk b/bin/mftl.awk index f210de33..21976337 100644 --- a/bin/mftl.awk +++ b/bin/mftl.awk @@ -2,9 +2,7 @@ # could be reasonably expected to call directly # Separators are space, tab, or colon -BEGIN { - FS = "[ \t:]" -} +BEGIN { FS = "[ \t:]" } # Skip comments /^#/ { next } @@ -28,7 +26,7 @@ BEGIN { # unique; this probably needs refinement for (i = 1; i < NF; i++) if ($i ~ /^[a-zA-Z0-9][a-zA-Z0-9.\/_-]*$/) - ats[$i] + ats[$i]++ } # Print unique determined targets, sorted diff --git a/bin/rfct.awk b/bin/rfct.awk index 53e948b2..2f0cc42d 100644 --- a/bin/rfct.awk +++ b/bin/rfct.awk @@ -1,15 +1,8 @@ # Format an RFC in text format for terminal reading # A record is a paragraph -BEGIN { - RS="" -} +BEGIN { RS = "" } -# Skip any block without at least one alphanumeric char -!/[[:alnum:]]/ { next } - -# Skip any block with a page break marker in it -/\x0c/ { next } - -# Print the block followed by two newlines -{ printf "%s\n\n", $0 } +# 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 } diff --git a/bin/rndi.awk b/bin/rndi.awk index 337498cb..49df4398 100644 --- a/bin/rndi.awk +++ b/bin/rndi.awk @@ -5,17 +5,15 @@ BEGIN { # Seed with the third argument if given - if (ARGV[3]) { + if (ARGV[3]) srand(ARGV[3]) - } # If not, just seed with what is probably a date/time-derived value - else { + else srand() - } # Print a random integer bounded by the first and second arguments - print int(ARGV[1]+rand()*(ARGV[2]-ARGV[1]+1)) + print int(ARGV[1] + rand() * (ARGV[2] - ARGV[1] + 1)) # Bail before processing any lines exit diff --git a/bin/sec.awk b/bin/sec.awk new file mode 100644 index 00000000..3ebf02b6 --- /dev/null +++ b/bin/sec.awk @@ -0,0 +1,24 @@ +# Convert [[[hh:]mm:]ss] timestamps to seconds + +# Separator is :, strip out leading zeroes +BEGIN { FS = ":0*" } + +# If no fields, too many fields, or illegal characters, warn, skip line, accrue +# errors +!NF || NF > 3 || /[^0-9:]/ { + print "sec: Bad format" > "/dev/stderr" + err = 1 + next +} + +# Match hh:mm:ss +NF == 3 { printf "%u\n", $1 * 3600 + $2 * 60 + $3 } + +# Match mm:ss +NF == 2 { printf "%u\n", $1 * 60 + $2 } + +# Match ss (in which case all we've done is strip zeroes) +NF == 1 { printf "%u\n", $1 } + +# Done, exit 1 if we had any errors on the way +END { exit(err > 0) } diff --git a/bin/slsf.awk b/bin/slsf.awk index 9d12225d..3d5c190f 100644 --- a/bin/slsf.awk +++ b/bin/slsf.awk @@ -6,5 +6,4 @@ FNR == 1 || /### sls/ { sls = 1 } # If processing flag set, directive is "Host", and hostname has no wildcards, # then print it -!sls { next } -$1 == "Host" && $2 !~ /\*/ { print $2 } +sls && $1 == "Host" && $2 !~ /\*/ { print $2 } diff --git a/bin/unf.awk b/bin/unf.awk index 22a10aa8..a9837a8a 100644 --- a/bin/unf.awk +++ b/bin/unf.awk @@ -18,9 +18,7 @@ body { } # Write any buffer contents once we hit a line not starting with a space -/^[^ \t]/ { - wrbuf() -} +/^[^ \t]/ { wrbuf() } # Append the current line to the buffer { @@ -29,6 +27,4 @@ body { } # Write the buffer out again when we hit the end -END { - wrbuf() -} +END { wrbuf() } diff --git a/bin/uts.awk b/bin/uts.awk new file mode 100644 index 00000000..3aaec2ab --- /dev/null +++ b/bin/uts.awk @@ -0,0 +1,6 @@ +# Print the current UNIX epoch timestamp in a POSIX compatible fashion +BEGIN { + srand() + print srand() + exit +} diff --git a/games/drakon.awk b/games/drakon.awk index e960a6c0..39bb3732 100644 --- a/games/drakon.awk +++ b/games/drakon.awk @@ -1,20 +1,13 @@ # TyPe lIkE AnDoR DrAkOn fRoM AnCiEnT DoMaInS Of mYsTeRy # <http://www.adomgb.info/adomgb-4.html> { - s = "" - u = 0 - for (i = 1; i <= length($0); i++) { - c = substr($0, i, 1) - if (c ~ /[a-zA-Z]/) { - if (u) { - c = toupper(c) - } - else { - c = tolower(c) - } - u = !u - } - s = s c + line = "" + case = 0 + for (i = 1; i <= length; i++) { + char = substr($0, i, 1) + if (char ~ /[a-zA-Z]/) + char = (case = !case) ? tolower(char) : toupper(char) + line = line char } - print s + print line } diff --git a/games/kvlt.sed b/games/kvlt.sed index 29a92227..e2905a35 100644 --- a/games/kvlt.sed +++ b/games/kvlt.sed @@ -33,8 +33,9 @@ s,\([^A-Z]\)GOOD\([^A-Z]\),\1TRUE\2,g s,\([^A-Z]\)GREAT\([^A-Z]\),\1TRUE\2,g s,\([^A-Z]\)NICE\([^A-Z]\),\1TRUE\2,g -# WAR -> KRIEG +# WAR/COOL -> KRIEG s,\([^A-Z]\)WAR\([^A-Z]\),\1KRIEG\2,g +s,\([^A-Z]\)COOL\([^A-Z]\),\1KRIEG\2,g # Double-letters are easy s,CC,KK,g diff --git a/games/strik.sed b/games/strik.sed new file mode 100644 index 00000000..bc1cbdc5 --- /dev/null +++ b/games/strik.sed @@ -0,0 +1,64 @@ +# Strike out text +s/a/a̶/g +s/b/b̶/g +s/c/c̶/g +s/d/d̶/g +s/e/e̶/g +s/f/f̶/g +s/g/g̶/g +s/h/h̶/g +s/i/i̶/g +s/j/j̶/g +s/k/k̶/g +s/l/l̶/g +s/m/m̶/g +s/n/n̶/g +s/o/o̶/g +s/p/p̶/g +s/q/q̶/g +s/r/r̶/g +s/s/s̶/g +s/t/t̶/g +s/u/u̶/g +s/v/v̶/g +s/w/w̶/g +s/x/x̶/g +s/y/y̶/g +s/z/z̶/g +s/A/A̶/g +s/B/B̶/g +s/C/C̶/g +s/D/D̶/g +s/E/E̶/g +s/F/F̶/g +s/G/G̶/g +s/H/H̶/g +s/I/I̶/g +s/J/J̶/g +s/K/K̶/g +s/L/L̶/g +s/M/M̶/g +s/N/N̶/g +s/O/O̶/g +s/P/P̶/g +s/Q/Q̶/g +s/R/R̶/g +s/S/S̶/g +s/T/T̶/g +s/U/U̶/g +s/V/V̶/g +s/W/W̶/g +s/X/X̶/g +s/Y/Y̶/g +s/Z/Z̶/g +s/0/0̶/g +s/1/1̶/g +s/2/2̶/g +s/3/3̶/g +s/4/4̶/g +s/5/5̶/g +s/6/6̶/g +s/7/7̶/g +s/8/8̶/g +s/9/9̶/g +s/ / ̶/g diff --git a/man/man1/chc.1df b/man/man1/chc.1df new file mode 100644 index 00000000..e447d7a7 --- /dev/null +++ b/man/man1/chc.1df @@ -0,0 +1,35 @@ +.TH CHC 1df "December 2016" "Manual page for chc" +.SH NAME +.B chc +\- cache the output of a command to avoid running it too often +.SH USAGE +.B chc +CACHE_PATH DURATION COMMAND [ARG1...] +.SH SYNOPSIS +.B chc +/tmp/example.chc 20 curl http://www.example.com/ +.SH DESCRIPTION +.B chc +runs the command given in its third argument onwards, and saves the output in +the file with path given in the first argument, and on each subsequent request +before the duration in the second argument expires, it emits the content +directly, rather than running the command. If it's run after the expiry date, +it runs the command again, and refreshes the cache. +.P +This is intended as a quick way to just add three words in front of any given +expensive command to prevent it running too often. This might be particularly +useful if a script is called to get data far more often than it actually needs +to poll to get that data. +.P +No file locking is implemented. If you need it, you're probably already at the +point that you need to write a proper solution, but you could always use Linux +flock(1) or daemontool's setlock(1) in the command if you're stubborn: +.P + flock -x /var/lock/example.chc chc /var/cache/example.chc 20 curl http://www.example.com/ +.P +If you want to express the duration in human-readable terms, sec(1df) might be +useful too. +.SH SEE ALSO +sec(1df), uts(1df), flock(1), setlock(1) +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man1/clog.1df b/man/man1/clog.1df new file mode 100644 index 00000000..bb03765d --- /dev/null +++ b/man/man1/clog.1df @@ -0,0 +1,15 @@ +.TH CLOG 1df "December 2016" "Manual page for clog" +.SH NAME +.B clog +\- record timestamped logs in a file +.SH SYNOPSIS +.B clog +getting real tired of all this overengineering +^D +.SH DESCRIPTION +.B clog +receives a message on stdin, timestamps it with a leading date(1), and writes +it to the file with path in environment variable CLOG, defaulting to ~/.clog, +terminating each entry with two hyphens. +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man1/loc.1df b/man/man1/loc.1df new file mode 100644 index 00000000..f288402c --- /dev/null +++ b/man/man1/loc.1df @@ -0,0 +1,14 @@ +.TH LOC 1df "December 2016" "Manual page for loc" +.SH NAME +.B loc +\- find files matching a pattern in the current directory +.SH SYNOPSIS +.B loc PATTERN1 [PATTERN2...] +.SH DESCRIPTION +.B loc +is a simple wrapper around find(1) which searches in the current directory tree +for filenames matching a pattern, and prints them to stdout, newline-separated. +It skips dotfiles, and doesn't interate further into a directory if it matches +the terms. It is intended only for interactive use as a shortcut. +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man1/sec.1df b/man/man1/sec.1df new file mode 100644 index 00000000..589b6a74 --- /dev/null +++ b/man/man1/sec.1df @@ -0,0 +1,21 @@ +.TH SEC 1df "December 2016" "Manual page for sec" +.SH NAME +.B sec +\- convert colon-delimited durations to seconds +.SH USAGE +.B sec +FILE1 [FILE2 ...] +.br +.B sec +< FILE +.br +printf '1:02:54\\n' | +.br +sec=$(printf '%s\n' "$timestamp" | sec) +.B sec +.SH DESCRIPTION +Applies awk(1) to convert hh:mm:ss or mm:ss timestamps into a count of seconds. +Exits zero if all lines were successfully recognised and converted, non-zero +otherwise. +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man1/uts.1df b/man/man1/uts.1df new file mode 100644 index 00000000..8b821fe4 --- /dev/null +++ b/man/man1/uts.1df @@ -0,0 +1,16 @@ +.TH UTS 1df "December 2016" "Manual page for uts" +.SH NAME +.B uts +\- prints the current UNIX timestamp +.SH SYNOPSIS +.B uts +.SH DESCRIPTION +.B uts +applies POSIX's specifications for the behaviour of awk's srand() function to +print the current UNIX timestamp. +.SH SEE ALSO +awk(1), date(1) +.br +<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html#tag_20_06_13_12> +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/man/man6/aesth.6df b/man/man6/aesth.6df index 1febe86c..ba2176da 100644 --- a/man/man6/aesth.6df +++ b/man/man6/aesth.6df @@ -10,9 +10,9 @@ lynx -dump https://sanctum.geek.nz/ | .B aesth .SH DESCRIPTION .B aesth -converts the 26 letters of the English alphabet, both upper and lower case, to -their full-width text equivalents for the CJK environment from the Basic -Multilingual Plane. +converts the 26 letters of the English alphabet, both upper and lower case, the +arabic numerals, and the space character to their full-width text equivalents +for the CJK environment from the Basic Multilingual Plane. .P The results are printed in UTF-8; they're hard-coded within the script. .SH AUTHOR diff --git a/man/man6/strik.6df b/man/man6/strik.6df new file mode 100644 index 00000000..3d5840a7 --- /dev/null +++ b/man/man6/strik.6df @@ -0,0 +1,19 @@ +.TH STRIK 6df "August 2016" "Manual page for strik" +.SH NAME +.B strik +\- strike out text +.SH USAGE +.B strik +/usr/share/dict/words +.br +lynx -dump https://sanctum.geek.nz/ | +.B strik +.SH DESCRIPTION +.B strik +converts the 26 letters of the English alphabet, both upper and lower case, the +Arabic numerals, and the space character to their equivalents with a Unicode +strikethrough. +.P +The results are printed in UTF-8; they're hard-coded within the script. +.SH AUTHOR +Tom Ryder <tom@sanctum.geek.nz> diff --git a/pdksh/pdkshrc.d/prompt.pdksh b/pdksh/pdkshrc.d/prompt.pdksh index fd99c5f7..63e965f9 100644 --- a/pdksh/pdkshrc.d/prompt.pdksh +++ b/pdksh/pdkshrc.d/prompt.pdksh @@ -23,7 +23,7 @@ prompt() { PS1=$PS1'\w' # Add sub-commands; VCS, job, and return status checks - PS1=$PS1'$(prompt vcs)$(prompt job)' + PS1=$PS1'$(prompt vcs)$(prompt job)$(prompt ret "$?")' # Add prefix and suffix PS1='${PROMPT_PREFIX}'$PS1'${PROMPT_SUFFIX}' @@ -43,7 +43,7 @@ prompt() { # Prepare reset code reset=$(tput sgr0 || tput me) - # Check if we have non-bold bright green available + # Check if we have non-bold bright yellow available if ((colors >= 16)) ; then format=$( : "${PROMPT_COLOR:=11}" @@ -53,7 +53,7 @@ prompt() { tput AF "$PROMPT_COLOR" 0 0 ) - # If we have only eight colors, use bold green + # If we have only eight colors, use bold yellow elif ((colors >= 8)) ; then format=$( : "${PROMPT_COLOR:=3}" @@ -167,6 +167,12 @@ prompt() { done ;; + # Show return status of previous command in angle brackets, if not zero + ret) + local ret=$2 + ((ret)) && printf '<%u>' "$ret" + ;; + # Show the count of background jobs in curly brackets, if not zero job) typeset -i jobc diff --git a/sh/shrc.d/ad.sh b/sh/shrc.d/ad.sh new file mode 100644 index 00000000..d3e5a90a --- /dev/null +++ b/sh/shrc.d/ad.sh @@ -0,0 +1,77 @@ +# Find an abbreviated path +ad() { + + # Check argument count + if [ "$#" -ne 1 ] ; then + printf >&2 'ad(): Need just one argument\n' + return 2 + fi + + # Change the positional parameters from the abbreviated request + # to any matched directory + set -- "$( + + # Clean up and anchor the request + req=${1%/}/ + case $req in + (/*) ;; + (*) req=${PWD%/}/${req#/}/ ;; + esac + + # Start building the target directory; go through the request piece by + # piece until it is used up + dir= + while [ -n "$req" ] ; do + + # Chop the next front bit off the request and add it to the dir + dir=${dir%/}/${req%%/*} + req=${req#*/} + + # If that exists, all is well and we can keep iterating + [ -d "$dir" ] && continue + + # Set the positional parameters to a glob expansion of the + # abbreviated directory given + set -- "$dir"* + + # Iterate through the positional parameters filtering out + # directories; we need to run right through the whole list to check + # that we have at most one match + entd= + for ent ; do + [ -d "$ent" ] || continue + + # If we already found a match and have found another one, bail + # out + if [ -n "$entd" ] ; then + printf >&2 'ad(): More than one matching dir for %s*:\n' \ + "$dir" + printf >&2 '%s\n' "$@" + exit 1 + fi + + # Otherwise, this can be our first one + entd=$ent + done + + # If we found no match, bail out + if [ -z "$entd" ] ; then + printf >&2 'ad(): No matching dirs: %s*\n' "$dir" + exit 1 + fi + + # All is well, tack on what we have found and keep going + dir=$entd + + done + + # Print the target + printf '%s\n' "$dir" + )" + + # 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/bd.sh b/sh/shrc.d/bd.sh index 14d65c3b..1043a8cb 100644 --- a/sh/shrc.d/bd.sh +++ b/sh/shrc.d/bd.sh @@ -22,28 +22,28 @@ bd() { 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='' ;; + ("$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 ;; + (*/"$req") break ;; esac done ;; diff --git a/sh/shrc.d/sd.sh b/sh/shrc.d/sd.sh index 80cb7e39..ce59bf99 100644 --- a/sh/shrc.d/sd.sh +++ b/sh/shrc.d/sd.sh @@ -53,7 +53,7 @@ sd() { else for sib in ../.* ../* ; do case ${sib#../} in - .|..|"${PWD##*/}") continue ;; + (.|..|"${PWD##*/}") continue ;; esac set -- "$@" "$sib" done @@ -61,12 +61,12 @@ sd() { # We should have exactly one sibling case $# in - 1) ;; - 0) + (1) ;; + (0) printf >&2 'sd(): No siblings\n' exit 1 ;; - *) + (*) printf >&2 'sd(): More than one sibling\n' exit 1 ;; diff --git a/sh/shrc.d/tree.sh b/sh/shrc.d/tree.sh index dd78f8ee..b4f91df8 100644 --- a/sh/shrc.d/tree.sh +++ b/sh/shrc.d/tree.sh @@ -7,7 +7,8 @@ tree() { if ( # Not if -n is in the arguments and -C isn't - while getopts 'nC' opt ; do + # Don't tell me about missing options, either + while getopts 'nC' opt 2>/dev/null ; do case $opt in n) n=1 ;; C) C=1 ;; diff --git a/vim/after/ftdetect/diff.vim b/vim/after/ftdetect/diff.vim deleted file mode 100644 index c9df9184..00000000 --- a/vim/after/ftdetect/diff.vim +++ /dev/null @@ -1,4 +0,0 @@ -" nwatch.diff.* is a diff -autocmd BufNewFile,BufRead - \ nwatch.diff.* - \ setlocal filetype=diff diff --git a/vim/after/ftdetect/sh.vim b/vim/after/ftdetect/sh.vim index 72d72d1d..3bc10ba7 100644 --- a/vim/after/ftdetect/sh.vim +++ b/vim/after/ftdetect/sh.vim @@ -1,31 +1,31 @@ -" .shrc is a shell script -autocmd BufNewFile,BufRead - \ .shrc - \ setlocal filetype=sh +" Add automatic commands to +augroup dfsh -" .xinitrc is a shell script -autocmd BufNewFile,BufRead - \ .xinitrc - \ setlocal filetype=sh + " Names/paths of things that are Bash shell script + autocmd BufNewFile,BufRead + \ **/.dotfiles/bash/**,bash-fc-* + \ let b:is_bash = 1 | + \ setlocal filetype=sh -" Files in /etc/default are shell script -autocmd BufNewFile,BufRead - \ /etc/default/* - \ setlocal filetype=sh + " Names/paths of things that are Korn shell script + autocmd BufNewFile,BufRead + \ **/.dotfiles/pdksh/**,.pdkshrc,*.pdksh + \ let b:is_kornshell = 1 | + \ setlocal filetype=sh -" Files in **/.dotfiles/sh/** are shell script -autocmd BufNewFile,BufRead - \ **/.dotfiles/sh/** - \ setlocal filetype=sh + " Names/paths of things that are POSIX shell script + autocmd BufNewFile,BufRead + \ **/.dotfiles/sh/**,.shinit,.shrc,.xinitrc,/etc/default/* + \ let b:is_posix = 1 | + \ setlocal filetype=sh -" Edited bash command lines are Bash script -autocmd BufNewFile,BufRead - \ bash-fc-* - \ let g:is_bash = 1 | - \ setlocal filetype=sh + " If we determined something is b:is_kornshell, tack on b:is_ksh as well so + " we can still tease out what is actually a kornshell script after sh.vim is + " done changing our options for us; it conflates POSIX with Korn shell. + autocmd BufNewFile,BufRead + \ * + \ if exists('b:is_kornshell') | + \ let b:is_ksh = 1 | + \ endif -" Files in **/.dotfiles/bash/** are Bash script -autocmd BufNewFile,BufRead - \ **/.dotfiles/bash/** - \ let g:is_bash = 1 | - \ setlocal filetype=sh +augroup END diff --git a/vim/after/ftplugin/sh.vim b/vim/after/ftplugin/sh.vim index 96ad8e6d..2b73611e 100644 --- a/vim/after/ftplugin/sh.vim +++ b/vim/after/ftplugin/sh.vim @@ -1,9 +1,9 @@ -" Include slashes as part of 'isk' so that e.g. 'local' in '/usr/local/mysql' -" doesn't highlight -let g:sh_isk='@,48-57,_,192-255,.,/' - -" Assume POSIX, I never write Bourne -let g:is_posix=1 +" Default to POSIX shell, as I never write Bourne, and if I write Bash or Ksh +" it'll be denoted with either a shebang or an appropriate extension. At the +" time of writing, changing this also prompts sh.vim to set g:is_kornshell, +" which is absurd, and requires a bit more massaging in after/syntax/sh.vim to +" turn off some unwanted stuff. +let g:is_posix = 1 " Use han(1df) as a man(1) wrapper for Bash files if available if exists('b:is_bash') && executable('han') diff --git a/vim/after/syntax/sh.vim b/vim/after/syntax/sh.vim index 37ad8518..e8b41ba4 100644 --- a/vim/after/syntax/sh.vim +++ b/vim/after/syntax/sh.vim @@ -1,77 +1,98 @@ -" shDerefSimple in sh.vim is not quite right, so let's fix it up -syntax clear shDerefSimple +" If g:is_posix is set, g:is_kornshell is probably set too, a strange decision +" by sh.vim. No matter; we can tease out whether this is actually a Korn shell +" script using our own b:is_ksh flag set at the end of +" ~/.vim/after/ftdetect/sh.vim, and if it isn't, we'll throw away the +" highlighting groups for ksh. +if exists('g:is_posix') && exists('g:is_kornshell') && !exists('b:is_ksh') + syntax clear kshSpecialVariables + syntax clear kshStatement +endif -" $var, $VAR, $var_new, $_var, $var1 ... -syntax match shDerefSimple '\$\h[a-zA-Z0-9_]*' -" $0, $1, $2 ... -syntax match shDerefSimple '\$\d' -" $-, $#, $* ... -syntax match shDerefSimple '\$[-#*@!?$]' +" Some corrections for highlighting if we have any of POSIX, Bash, or Ksh +if exists('g:is_posix') || exists('b:is_bash') || exists('b:is_ksh') -" Trust me to get my dereferencing right -syntax clear shDerefWordError + " The syntax highlighter seems to flag '/baz' in '"${foo:-"$bar"/baz}"' as an + " error, and I'm pretty sure it's not, at least in POSIX sh, Bash, and Ksh. + syntax clear shDerefWordError -" I don't like having 'restart', 'start" etc highlighted -syntax clear bashAdminStatement + " The syntax highlighter doesn't match parens for subshells for 'if' tests + " correctly if they're on separate lines. This happens enough that it's + " probably not worth keeping the error. + syntax clear shParenError -" Limit bashStatement only to alphanumeric shell builtins, except for the ones -" that declare variables (declare, typeset, local, export, unset) as they're -" used in shSetList -syntax clear bashStatement -syntax keyword bashStatement - \ alias - \ bg - \ bind - \ break - \ builtin - \ caller - \ cd - \ command - \ compgen - \ complete - \ compopt - \ continue - \ coproc - \ dirs - \ disown - \ echo - \ enable - \ eval - \ exec - \ exit - \ false - \ fc - \ fg - \ function - \ getopts - \ hash - \ help - \ history - \ jobs - \ kill - \ let - \ logout - \ mapfile - \ popd - \ printf - \ pushd - \ pwd - \ read - \ readarray - \ readonly - \ return - \ set - \ shift - \ shopt - \ source - \ suspend - \ test - \ time - \ times - \ trap - \ true - \ type - \ ulimit - \ umask - \ unalias - \ wait +endif + +" Some corrections for highlighting specific to the Bash mode +if exists('b:is_bash') + + " I don't like bashAdminStatement; these are not keywords, they're just + " strings for init scripts. + syntax clear bashAdminStatement + + " Reduce bashStatement down to just builtins; highlighting 'grep' is not + " very useful. This list was taken from `compgen -A helptopic` on Bash + " 4.4.5. + syntax clear bashStatement + syntax keyword bashStatement + \ . + \ : + \ alias + \ bg + \ bind + \ break + \ builtin + \ caller + \ cd + \ command + \ compgen + \ complete + \ compopt + \ continue + \ coproc + \ dirs + \ disown + \ echo + \ enable + \ eval + \ exec + \ exit + \ false + \ fc + \ fg + \ function + \ getopts + \ hash + \ help + \ history + \ jobs + \ kill + \ let + \ logout + \ mapfile + \ popd + \ printf + \ pushd + \ pwd + \ read + \ readarray + \ readonly + \ return + \ select + \ set + \ shift + \ shopt + \ source + \ suspend + \ test + \ time + \ times + \ trap + \ true + \ type + \ ulimit + \ umask + \ unalias + \ until + \ variables + \ wait +endif @@ -7,6 +7,13 @@ HISTFILE=$HOME/.zsh_history SAVEHIST=$((1 << 12)) HISTSIZE=$((1 << 10)) +# Set a SHLVL-derived value that takes tmux into account. This is used to show +# the current SHLVL in the prompt +if [[ -n $TMUX && -z $TMUX_SHLVL ]] ; then + TMUX_SHLVL=$((SHLVL - 1)) + export TMUX_SHLVL +fi + # Load POSIX shell startup files and then Bash-specific ones for sh in "$ENV" "$HOME"/.zshrc.d/*.zsh ; do [[ -e $sh ]] && source "$sh" diff --git a/zsh/zshrc.d/prompt.zsh b/zsh/zshrc.d/prompt.zsh index ca5ecf3e..0ac2be55 100644 --- a/zsh/zshrc.d/prompt.zsh +++ b/zsh/zshrc.d/prompt.zsh @@ -32,6 +32,13 @@ prompt() { # Add terminating "$" or "#" sign PS1=$PS1'%#' + # Add > signs at the front of the prompt to show the current shell + # level, taking tmux sessions into account + local shlvl + for ((shlvl = SHLVL - TMUX_SHLVL; shlvl > 1; shlvl--)) ; do + PS1='>'$PS1 + done + # Bold and color the prompt if it looks like we can if (( $({ tput colors || tput Co ; } 2>/dev/null) >= 8 )) ; then PS1='%B%F{green}'$PS1'%f%b' |