aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2016-09-02 14:40:47 +1200
committerTom Ryder <tom@sanctum.geek.nz>2016-09-02 14:40:47 +1200
commit34e9e23b95aadfd7a1e7d7e990e4347edfe31c02 (patch)
treeb57701d82c8eff7f0dc8e532f98f7507981d38b5
parentPort Git prompt improvements to pdksh (diff)
parentSmall correction to cfr(1df) notes (diff)
downloaddotfiles-34e9e23b95aadfd7a1e7d7e990e4347edfe31c02.tar.gz
dotfiles-34e9e23b95aadfd7a1e7d7e990e4347edfe31c02.zip
Merge branch 'master' into openbsd
-rw-r--r--.gitignore5
-rw-r--r--ISSUES.markdown5
-rw-r--r--Makefile50
-rw-r--r--README.markdown7
-rw-r--r--bash/bashrc6
-rw-r--r--bash/bashrc.d/completion.bash5
-rw-r--r--bash/bashrc.d/prompt.bash60
-rwxr-xr-xbin/cfr8
-rwxr-xr-xbin/dub2
-rw-r--r--bin/mean.awk8
-rw-r--r--bin/med.awk19
-rw-r--r--bin/mode.awk13
-rw-r--r--bin/tot.awk3
-rwxr-xr-xcheck/urxvt5
-rwxr-xr-xcheck/yash6
-rwxr-xr-xlint/urxvt2
-rwxr-xr-xlint/yash2
-rw-r--r--man/man1/cf.1df13
-rw-r--r--man/man1/cfr.1df36
-rw-r--r--man/man1/mean.1df19
-rw-r--r--man/man1/med.1df21
-rw-r--r--man/man1/mode.1df20
-rw-r--r--man/man1/tot.1df19
-rw-r--r--pdksh/pdkshrc.d/prompt.pdksh6
-rw-r--r--readline/inputrc7
-rw-r--r--sh/profile4
-rw-r--r--sh/profile.d/browser.sh3
-rw-r--r--sh/profile.d/welcome.sh67
-rw-r--r--sh/shrc6
-rw-r--r--sh/shrc.d/ls.sh2
-rw-r--r--urxvt/ext/select.pl (renamed from urxvt/ext/select)4
-rw-r--r--vim/after/ftdetect/sh.vim5
-rw-r--r--yash/yash_profile2
-rw-r--r--yash/yashrc3
-rw-r--r--zsh/zshrc7
-rw-r--r--zsh/zshrc.d/prompt.zsh206
36 files changed, 545 insertions, 111 deletions
diff --git a/.gitignore b/.gitignore
index a1bef5fd..6deb9bd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,13 @@
bin/han
+bin/mean
+bin/med
+bin/mode
bin/rfct
bin/rndi
bin/sd2u
bin/slsf
bin/su2d
+bin/tot
bin/unf
games/acq
games/kvlt
@@ -12,3 +16,4 @@ git/gitconfig
gnupg/gpg.conf
man/man7/dotfiles.7df
tmux/tmux.conf
+urxvt/ext/select
diff --git a/ISSUES.markdown b/ISSUES.markdown
index 9adb127b..f58d7572 100644
--- a/ISSUES.markdown
+++ b/ISSUES.markdown
@@ -28,3 +28,8 @@ Known issues
* A key binding for importing sections of the screen and optionally uploading
it would be great, probably using ImageMagick import(1)
* sxhkd(1) might be nicer than xbindkeys; it's in Debian Testing now
+* Maybe I should port some of the prompt functions to POSIX sh so I don't
+ have to maintain parallel versions for bash, pdksh, and zsh
+* The Mutt configuration is not very standalone; relies quite a lot on
+ Debian's /etc/Muttrc and /etc/Muttrc.d, most of which can probably be
+ included in my own configuration files.
diff --git a/Makefile b/Makefile
index b89d5076..c1afe191 100644
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,7 @@
install-vim-pathogen \
install-wyrd \
install-x \
+ install-yash \
install-zsh \
check \
check-bash \
@@ -48,15 +49,17 @@
check-pdksh \
check-sh \
check-urxvt \
+ check-yash \
lint \
lint-bash \
lint-bin \
lint-games \
lint-pdksh \
+ lint-yash \
lint-sh \
lint-urxvt
-.SUFFIXES: .awk .bash .sed
+.SUFFIXES: .awk .bash .pl .sed
NAME := Tom Ryder
EMAIL := tom@sanctum.geek.nz
@@ -64,23 +67,32 @@ KEY := 0xC14286EA77BB8872
SENDMAIL := /usr/bin/msmtp
all : bin/han \
+ bin/mean \
+ bin/med \
+ bin/mode \
bin/rfct \
bin/rndi \
bin/sd2u \
bin/slsf \
bin/su2d \
+ bin/tot \
bin/unf \
git/gitconfig \
- gnupg/gpg.conf
+ gnupg/gpg.conf \
+ urxvt/ext/select
clean distclean :
rm -f \
bin/han \
+ bin/mean \
+ bin/med \
+ bin/mode \
bin/rfct \
bin/rndi \
bin/sd2u \
bin/slsf \
bin/su2d \
+ bin/tot \
bin/unf \
games/acq \
games/kvlt \
@@ -89,7 +101,8 @@ clean distclean :
gnupg/gpg.conf \
man/man7/dotfiles.7df \
mutt/muttrc \
- tmux/tmux.conf
+ tmux/tmux.conf \
+ urxvt/ext/select
git/gitconfig : git/gitconfig.m4
m4 \
@@ -126,6 +139,10 @@ tmux/tmux.conf : tmux/tmux.conf.m4
bin/shb "$<" bash > "$@"
chmod +x "$@"
+.pl :
+ bin/shb "$<" perl > "$@"
+ chmod +x "$@"
+
.sed :
bin/shb "$<" sed -f > "$@"
chmod +x "$@"
@@ -160,7 +177,8 @@ 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/han bin/sd2u bin/su2d bin/unf check-bin install-bin-man
+install-bin : bin/han bin/sd2u bin/su2d bin/mean bin/med bin/mode \
+ bin/tot bin/unf check-bin install-bin-man
install -m 0755 -d -- "$(HOME)"/.local/bin
for name in bin/* ; do \
[ -x "$$name" ] || continue ; \
@@ -294,9 +312,14 @@ install-terminfo :
install-tmux : tmux/tmux.conf install-terminfo
install -pm 0644 -- tmux/tmux.conf "$(HOME)"/.tmux.conf
-install-urxvt : check-urxvt
+install-urxvt : urxvt/ext/select check-urxvt
install -m 0755 -d -- "$(HOME)"/.urxvt/ext
- install -m 0755 -- urxvt/ext/* "$(HOME)"/.urxvt/ext
+ for name in urxvt/ext/* ; do \
+ case $$name in \
+ *.pl) ;; \
+ *) install -m 0644 -- "$$name" "$(HOME)"/.urxvt/ext ;; \
+ esac \
+ done
install-vim : install-vim-config \
install-vim-plugins \
@@ -337,9 +360,15 @@ install-x :
install -pm 0644 -- X/Xresources "$(HOME)"/.Xresources
install -pm 0644 -- X/Xresources.d/* "$(HOME)"/.Xresources.d
+install-yash : check-yash install-sh
+ install -pm 0644 -- yash/yashrc "$(HOME)"/.yashrc
+ install -pm 0644 -- yash/yash_profile "$(HOME)"/.yash_profile
+
install-zsh : install-sh
+ install -m 0755 -d -- "$(HOME)"/.zshrc.d
install -pm 0644 -- zsh/zprofile "$(HOME)"/.zprofile
install -pm 0644 -- zsh/zshrc "$(HOME)"/.zshrc
+ install -pm 0644 -- zsh/zshrc.d/* "$(HOME)"/.zshrc.d
check : check-bash \
check-bin \
@@ -369,13 +398,17 @@ check-sh :
check-urxvt :
check/urxvt
+check-yash :
+ check/yash
+
lint : check \
lint-bash \
lint-bin \
lint-games \
lint-pdksh \
lint-sh \
- lint-urxvt
+ lint-urxvt \
+ lint-yash
lint-bash :
lint/bash
@@ -394,3 +427,6 @@ lint-sh :
lint-urxvt :
lint/urxvt
+
+lint-yash :
+ lint/yash
diff --git a/README.markdown b/README.markdown
index 99883c97..9717cae3 100644
--- a/README.markdown
+++ b/README.markdown
@@ -79,6 +79,8 @@ Configuration is included for:
frontend for [Remind](https://www.roaringpenguin.com/products/remind)
* [X11](https://www.x.org/wiki/) -- Windowing system with network transparency
for Unix
+* [Yash](https://yash.osdn.jp/index.html.en) -- Yet another shell; just
+ enough configuration to make it read the portable POSIX stuff
* [Zsh](https://www.zsh.org/) -- Bourne-style shell designed for interactive
use
@@ -390,6 +392,7 @@ Installed by the `install-bin` target:
it knows of one.
* `ca(1df)` prints a count of its given arguments.
* `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.
* `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
* `dmp(1df)` copies a pass(1) entry selected by `dmenu(1)` to the X CLIPBOARD.
@@ -418,6 +421,9 @@ Installed by the `install-bin` target:
* `jfcd(1df)` watches a directory for changes and runs `jfc(1df)` if it sees any.
* `maybe(1df)` is like `true(1)` or `false(1)`; given a probability of success,
it exits with success or failure. Good for quick tests.
+* `mean(1df)` prints the mean of a list of integers.
+* `med(1df)` prints the median of a list of integers.
+* `mode(1df)` prints the first encountered mode of a list of integers.
* `mkcp(1df)` creates a directory and copies preceding arguments into it.
* `mkmv(1df)` creates a directory and moves preceding arguments into it.
* `motd(1df)` shows the system MOTD.
@@ -438,6 +444,7 @@ Installed by the `install-bin` target:
privileges.
* `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.
+* `tot(1df)` adds up a list of integers.
* `try(1df)` repeats a command up to a given number of times until it succeeds,
only printing error output if all three attempts failed. Good for
tolerating blips or temporary failures in `cron(8)` scripts.
diff --git a/bash/bashrc b/bash/bashrc
index a62f2251..a48a9081 100644
--- a/bash/bashrc
+++ b/bash/bashrc
@@ -19,18 +19,12 @@ shopt -q restricted_shell && return
# Keep around 32K lines of history in file
HISTFILESIZE=$((1 << 15))
-# Keep around 4K lines of history in memory
-HISTSIZE=$((1 << 12))
-
# Ignore duplicate commands and whitespace in history
HISTCONTROL=ignoreboth
# Keep the times of the commands in history
HISTTIMEFORMAT='%F %T '
-# Don't warn me about new mail
-unset -v MAILCHECK
-
# Use a more compact format for the time builtin's output
TIMEFORMAT='real:%lR user:%lU sys:%lS'
diff --git a/bash/bashrc.d/completion.bash b/bash/bashrc.d/completion.bash
index ecdffab8..0ddb1b55 100644
--- a/bash/bashrc.d/completion.bash
+++ b/bash/bashrc.d/completion.bash
@@ -37,11 +37,8 @@ complete -A shopt shopt
# Signal names
complete -A signal trap
-# Variables
-complete -A variable declare export readonly typeset
-
# Both functions and variables
-complete -A function -A variable unset
+complete -A function -A variable declare export readonly typeset unset
# The `mapfile` builtin in Bash >= 4.0
((BASH_VERSINFO[0] >= 4)) && complete -A arrayvar mapfile
diff --git a/bash/bashrc.d/prompt.bash b/bash/bashrc.d/prompt.bash
index a4d07f7e..55c33282 100644
--- a/bash/bashrc.d/prompt.bash
+++ b/bash/bashrc.d/prompt.bash
@@ -17,7 +17,7 @@ prompt() {
# Basic prompt shape depends on whether we're in SSH or not
PS1=
- if [[ -n $SSH_CLIENT ]] || [[ -n $SSH_CONNECTION ]] ; then
+ if [[ -n $SSH_CLIENT || -n $SSH_CONNECTION ]] ; then
PS1=$PS1'\u@\h:'
fi
PS1=$PS1'\w'
@@ -101,20 +101,20 @@ prompt() {
iswt=$(git rev-parse --is-inside-work-tree 2>/dev/null)
[[ $iswt = true ]] || return
- # Find a branch label, or a tag, or just show the short commit ID,
- # in that order of preference; if none of that works, bail out.
- local branch
- branch=$( {
- git symbolic-ref --quiet HEAD ||
- git describe --tags --exact-match HEAD ||
- git rev-parse --short HEAD
- } 2>/dev/null )
- [[ -n $branch ]] || return
- branch=${branch##*/}
-
# Refresh index so e.g. git-diff-files(1) is accurate
git update-index --refresh >/dev/null
+ # Find a local branch, remote branch, or tag (annotated or not), or
+ # failing all of that just show the short commit ID, in that order
+ # of preference; if none of that works, bail out
+ local name
+ name=$( {
+ git symbolic-ref --quiet HEAD ||
+ git describe --all --always --exact-match HEAD
+ } 2>/dev/null) || return
+ name=${name##*/}
+ [[ -n $name ]] || return
+
# Check various files in .git to flag processes
local proc
[[ -d .git/rebase-merge || -d .git/rebase-apply ]] &&
@@ -161,29 +161,23 @@ prompt() {
# Print the status in brackets; add a git: prefix only if there
# might be another VCS prompt (because PROMPT_VCS is set)
printf '(%s%s%s%s)' \
- "${PROMPT_VCS:+git:}" "${branch:-unknown}" \
- "${proc:+:$proc}" "$state"
+ "${PROMPT_VCS:+git:}" "$name" "${proc:+:$proc}" "$state"
;;
# Subversion prompt function
svn)
# Determine the repository URL and root directory
local key value url root
- while IFS=: read -r key value ; do
+ while [[ -z $url || -z $root ]] && IFS=: read -r key value ; do
case $key in
- 'URL')
- url=${value## }
- ;;
- 'Repository Root')
- root=${value## }
- ;;
+ 'URL') url=${value## } ;;
+ 'Repository Root') root=${value## } ;;
esac
done < <(svn info 2>/dev/null)
# Exit if we couldn't get either--or, implicitly, if we don't have
# svn(1).
- [[ -n $url ]] || return
- [[ -n $root ]] || return
+ [[ -n $url && -n $root ]] || return
# Remove the root from the URL to get what's hopefully the branch
# name, removing leading slashes and the 'branches' prefix, and any
@@ -197,22 +191,20 @@ prompt() {
# Parse the output of svn status to determine working copy state
local symbol
local -i modified untracked
- while read -r symbol _ ; do
- if [[ $symbol == *'?'* ]] ; then
- untracked=1
- else
- modified=1
- fi
+ while ((!modified || !untracked)) && read -r symbol _ ; do
+ case $symbol in
+ *\?*) untracked=1 ;;
+ *) modified=1 ;;
+ esac
done < <(svn status 2>/dev/null)
# Add appropriate state flags
- local -a state
- ((modified)) && state[${#state[@]}]='!'
- ((untracked)) && state[${#state[@]}]='?'
+ local state
+ ((modified)) && state=${state}'!'
+ ((untracked)) && state=${state}'?'
# Print the state in brackets with an svn: prefix
- (IFS= ; printf '(svn:%s%s)' \
- "${branch:-unknown}" "${state[*]}")
+ printf '(svn:%s%s)' "${branch:-unknown}" "$state"
;;
# VCS wrapper prompt function; print the first relevant prompt, if any
diff --git a/bin/cfr b/bin/cfr
new file mode 100755
index 00000000..896126f5
--- /dev/null
+++ b/bin/cfr
@@ -0,0 +1,8 @@
+#!/bin/sh
+# Count all descendants of given directories; don't follow symlinks
+for dir in "${@:-.}" ; do
+ tot=$(find "$dir" -type d \
+ -exec sh -c 'cd -- "$1" && cf' _ {} \; |
+ cut -f1 | tot)
+ printf '%u\t%s\n' "$tot" "$dir"
+done
diff --git a/bin/dub b/bin/dub
index 555bed67..36d787a4 100755
--- a/bin/dub
+++ b/bin/dub
@@ -26,7 +26,7 @@ set -- .* "$@"
du -ks -- "$@" |
# Sort the first field (the sizes) numerically, in reverse
-sort -k1nr |
+sort -k1,1nr |
# Limit the output to the given number of lines
sed "$lines"q
diff --git a/bin/mean.awk b/bin/mean.awk
new file mode 100644
index 00000000..4506b3b0
--- /dev/null
+++ b/bin/mean.awk
@@ -0,0 +1,8 @@
+# Get the mean of a list of integers
+{ tot += $1 }
+END {
+ # Error out if we read no values at all
+ if (!NR)
+ exit(1)
+ printf "%u\n", tot / NR
+}
diff --git a/bin/med.awk b/bin/med.awk
new file mode 100644
index 00000000..b4a899a1
--- /dev/null
+++ b/bin/med.awk
@@ -0,0 +1,19 @@
+# Get the median of a list of integers; if it has to average it, it uses the
+# integer floor of the result
+{ vals[NR] = $1 }
+NR > 1 && vals[NR] < vals[NR-1] && !warn++ {
+ printf "med: Input not sorted!\n" > "/dev/stderr"
+}
+END {
+ # Error out if we read no values at all
+ if (!NR)
+ exit(1)
+ if (NR % 2) {
+ med = vals[(NR+1)/2]
+ } else {
+ med = (vals[NR/2] + vals[NR/2+1]) / 2
+ }
+ printf "%u\n", med
+ if (warn)
+ exit(1)
+}
diff --git a/bin/mode.awk b/bin/mode.awk
new file mode 100644
index 00000000..beced1f4
--- /dev/null
+++ b/bin/mode.awk
@@ -0,0 +1,13 @@
+# Get mode of a list of integers
+# If the distribution is multimodal, the first mode is used
+{ vals[$1]++ }
+END {
+ # Error out if we read no values at all
+ if (!NR)
+ exit(1)
+ mode = vals[0]
+ for (val in vals)
+ if (vals[val] > vals[mode])
+ mode = val
+ printf "%u\n", mode
+}
diff --git a/bin/tot.awk b/bin/tot.awk
new file mode 100644
index 00000000..c25c718d
--- /dev/null
+++ b/bin/tot.awk
@@ -0,0 +1,3 @@
+# Total a column of integers
+{ tot += $1 }
+END { printf "%u\n", tot }
diff --git a/check/urxvt b/check/urxvt
index d27d6660..a40f8559 100755
--- a/check/urxvt
+++ b/check/urxvt
@@ -1,6 +1,9 @@
#!/bin/sh
for perl in urxvt/ext/* ; do
[ -f "$perl" ] || continue
- perl -c "$perl" >/dev/null || exit
+ case $perl in
+ *.pl) ;;
+ *) perl -c "$perl" >/dev/null || exit ;;
+ esac
done
printf 'All Perl scripts in urxvt/ext parsed successfully.\n'
diff --git a/check/yash b/check/yash
new file mode 100755
index 00000000..fb737596
--- /dev/null
+++ b/check/yash
@@ -0,0 +1,6 @@
+#!/bin/sh
+for yash in yash/* ; do
+ [ -f "$yash" ] || continue
+ yash -n "$yash" || exit
+done
+printf 'All yash scripts parsed successfully.\n'
diff --git a/lint/urxvt b/lint/urxvt
index 84f08619..9fd6193d 100755
--- a/lint/urxvt
+++ b/lint/urxvt
@@ -1,2 +1,2 @@
#!/bin/sh
-find urxvt/ext -type f -print -exec perlcritic --brutal -- {} \;
+find urxvt/ext -type f ! -name '*.pl' -print -exec perlcritic --brutal -- {} \;
diff --git a/lint/yash b/lint/yash
new file mode 100755
index 00000000..c2eacab3
--- /dev/null
+++ b/lint/yash
@@ -0,0 +1,2 @@
+#!/bin/sh
+find yash -type f -print -exec shellcheck -e SC1090 -s sh -- {} \;
diff --git a/man/man1/cf.1df b/man/man1/cf.1df
index 8d6e3edc..ab1338ba 100644
--- a/man/man1/cf.1df
+++ b/man/man1/cf.1df
@@ -5,13 +5,16 @@
.SH SYNOPSIS
.B cf
.br
-.B cf /path/to/dir
+.B cf
+/path/to/dir
.br
-.B cf dir1 dir2
+.B cf
+dir1 dir2
.SH DESCRIPTION
.B cf
-counts all the entries in the given directories using glob
-expansion and prints the count. It defaults to the current
-directory.
+counts all the entries in the given directories using glob expansion and prints
+the count. It defaults to the current directory.
+.SH SEE ALSO
+cfr(1df)
.SH AUTHOR
Tom Ryder <tom@sanctum.geek.nz>
diff --git a/man/man1/cfr.1df b/man/man1/cfr.1df
new file mode 100644
index 00000000..2ec636bf
--- /dev/null
+++ b/man/man1/cfr.1df
@@ -0,0 +1,36 @@
+.TH CFR 1df "September 2016" "Manual page for cfr"
+.SH NAME
+.B cfr
+\- print a count of entries in a directory tree
+.SH SYNOPSIS
+.B cfr
+.br
+.B cfr
+/path/to/dir
+.br
+.B cfr
+dir1 dir2
+.SH DESCRIPTION
+.B cf
+counts all the entries in the directory trees rooted at the given arguments,
+and prints the total. It defaults to the current directory. It should correctly
+handle corner cases like files with newlines in them. It will count but will
+not follow symbolic links.
+.SH NOTES
+You might think this would be better; it's certainly faster:
+.P
+ $ find . | wc -l
+.P
+However, it's subtly wrong; it will double-count anything with a path that
+contains a newline!
+.P
+cfr(1df) and cf(1df) are POSIX-fearing as far as I can tell (please correct
+me), but there are faster but less compatible ways to do this, while still
+remaining accurate. Here's a method using GNU find(1) adapted from an extremely
+clever suggestion from geirha on Freenode; it's much, much faster:
+.P
+ $ find . -mindepth 1 -printf %.sx | wc -c
+.SH SEE ALSO
+cf(1df), tot(1df)
+.SH AUTHOR
+Tom Ryder <tom@sanctum.geek.nz>
diff --git a/man/man1/mean.1df b/man/man1/mean.1df
new file mode 100644
index 00000000..21f509f2
--- /dev/null
+++ b/man/man1/mean.1df
@@ -0,0 +1,19 @@
+.TH MEAN 1df "September 2016" "Manual page for mean"
+.SH NAME
+.B mean
+\- print the mean of a list of integers
+.SH SYNOPSIS
+printf '%u\\n' 8 1 58 |
+.B mean
+.br
+.B mean
+file
+.br
+.B mean
+file1 file2
+.SH DESCRIPTION
+.B mean
+collects all the newline-delimited integers given as input, and prints the
+mean.
+.SH AUTHOR
+Tom Ryder <tom@sanctum.geek.nz>
diff --git a/man/man1/med.1df b/man/man1/med.1df
new file mode 100644
index 00000000..0fab7db2
--- /dev/null
+++ b/man/man1/med.1df
@@ -0,0 +1,21 @@
+.TH MED 1df "September 2016" "Manual page for med"
+.SH NAME
+.B med
+\- print the med of a list of integers
+.SH SYNOPSIS
+printf '%u\\n' 14 2 10 |
+.B med
+.br
+.B med
+file
+.br
+.B med
+file1 file2
+.SH DESCRIPTION
+.B med
+collects all the newline-delimited integers given as input, and prints the
+median. It uses the floor of the mean of the two median values if the number of
+records is even. The input must be sorted, and a warning will be issued if it
+isn't.
+.SH AUTHOR
+Tom Ryder <tom@sanctum.geek.nz>
diff --git a/man/man1/mode.1df b/man/man1/mode.1df
new file mode 100644
index 00000000..d4727235
--- /dev/null
+++ b/man/man1/mode.1df
@@ -0,0 +1,20 @@
+.TH MODE 1df "September 2016" "Manual page for mode"
+.SH NAME
+.B mode
+\- print the mode of a list of integers
+.SH SYNOPSIS
+printf '%u\\n' 2 35 3 8 |
+.B mode
+.br
+.B mode
+file
+.br
+.B mode
+file1 file2
+.SH DESCRIPTION
+.B mode
+collects all the newline-delimited integers given as input, and prints the
+mode. If two values have the same frequency (i.e. a multimodal distribution),
+it will print the one that reaches that frequency first in the data set.
+.SH AUTHOR
+Tom Ryder <tom@sanctum.geek.nz>
diff --git a/man/man1/tot.1df b/man/man1/tot.1df
new file mode 100644
index 00000000..c098cbe1
--- /dev/null
+++ b/man/man1/tot.1df
@@ -0,0 +1,19 @@
+.TH TOT 1df "September 2016" "Manual page for tot"
+.SH NAME
+.B tot
+\- print a count of entries in a directory
+.SH SYNOPSIS
+printf '%u\\n' 5 9 53 145 |
+.B tot
+.br
+.B tot
+file
+.br
+.B tot
+file1 file2
+.SH DESCRIPTION
+.B tot
+adds up all the newline-delimited integers given as input, and prints the
+total.
+.SH AUTHOR
+Tom Ryder <tom@sanctum.geek.nz>
diff --git a/pdksh/pdkshrc.d/prompt.pdksh b/pdksh/pdkshrc.d/prompt.pdksh
index bac505a4..920e21ae 100644
--- a/pdksh/pdkshrc.d/prompt.pdksh
+++ b/pdksh/pdkshrc.d/prompt.pdksh
@@ -99,8 +99,10 @@ prompt() {
# failing all of that just show the short commit ID, in that order
# of preference; if none of that works, bail out
typeset name
- name=$(git describe --all --always --exact-match \
- HEAD 2>/dev/null) || return
+ name=$( {
+ git symbolic-ref --quiet HEAD ||
+ git describe --all --always --exact-match HEAD
+ } 2>/dev/null) || return
name=${name##*/}
[[ -n $name ]] || return
diff --git a/readline/inputrc b/readline/inputrc
index ce77a5bc..dd284f0b 100644
--- a/readline/inputrc
+++ b/readline/inputrc
@@ -1,3 +1,7 @@
+# Don't mess with the eighth bit of characters
+set input-meta on
+set output-meta on
+
# Never ring any sort of bell during line reading
set bell-style none
@@ -22,7 +26,8 @@ set echo-control-characters off
# Expand tilde to full path on completion
set expand-tilde on
-# Add a trailing slash for completed symlink directories
+# Add a trailing slash for directories and symlinked directories
+set mark-directories on
set mark-symlinked-directories on
# Don't match dotfiles unless there's a dot
diff --git a/sh/profile b/sh/profile
index 5dfe0ef3..afc308ea 100644
--- a/sh/profile
+++ b/sh/profile
@@ -1,7 +1,5 @@
# Add ~/.local/bin to PATH if it exists
-if [ -d "$HOME"/.local/bin ] ; then
- PATH=$HOME/.local/bin:$PATH
-fi
+[ -d "$HOME"/.local/bin ] && PATH=$HOME/.local/bin:$PATH
# Load all supplementary scripts in ~/.profile.d
for sh in "$HOME"/.profile.d/*.sh ; do
diff --git a/sh/profile.d/browser.sh b/sh/profile.d/browser.sh
index 6adbacfe..2c724505 100644
--- a/sh/profile.d/browser.sh
+++ b/sh/profile.d/browser.sh
@@ -1,3 +1,4 @@
-# Browser
+# Set command-line browser to lynx; ~/.xinitrc will change this to something
+# graphical instead
BROWSER=lynx
export BROWSER
diff --git a/sh/profile.d/welcome.sh b/sh/profile.d/welcome.sh
index 26bf678f..ede7a05f 100644
--- a/sh/profile.d/welcome.sh
+++ b/sh/profile.d/welcome.sh
@@ -10,45 +10,36 @@ esac
# Not if ~/.hushlogin exists
[ -e "$HOME"/.hushlogin ] && return
+# Run all of this in a subshell to clear it away afterwards
(
- # Only if ~/.welcome/fortune exists
- [ -e "$HOME"/.welcome/fortune ] || exit
-
- # Only if fortune(6) available
- command -v fortune >/dev/null 2>&1 || exit
+ # Temporary helper function
+ welcome() {
+ [ -e "$HOME"/.welcome/"$1" ] || return
+ command -v "$1" >/dev/null 2>&1 || return
+ }
# Show a fortune
- [ -d "$HOME"/.local/share/games/fortunes ] &&
- : "${FORTUNE_PATH:="$HOME"/.local/share/games/fortunes}"
- fortune -s "$FORTUNE_PATH"
- printf '\n'
-)
-(
- # Only if ~/.welcome/remind exists
- [ -e "$HOME"/.welcome/remind ] || exit
-
- # Only if rem(1) available
- command -v rem >/dev/null 2>&1 || exit
-
- # Print reminders with asterisks
- rem -hq | sed 's/^/* /'
- printf '\n'
-)
-(
- # Only if ~/.welcome/remind verse
- [ -e "$HOME"/.welcome/verse ] || exit
-
- # Only if verse(1) available
- command -v verse >/dev/null 2>&1 || exit
-
- # 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)
- last=0
- [ -f "$HOME"/.verse ] &&
- last=$(cat -- "$HOME"/.verse)
- [ "$now" -gt "$last" ] || exit
- verse
- printf '\n'
- printf '%s\n' "$now" > "$HOME"/.verse
+ if welcome fortune ; then
+ [ -d "$HOME"/.local/share/games/fortunes ] &&
+ : "${FORTUNE_PATH:="$HOME"/.local/share/games/fortunes}"
+ fortune -s "$FORTUNE_PATH"
+ printf '\n'
+ fi
+
+ # Print today's reminders with asterisks
+ if welcome rem ; then
+ rem -hq | sed 's/^/* /'
+ printf '\n'
+ fi
+
+ # Run verse(1) if we haven't seen it already today
+ if welcome verse ; then
+ [ -f "$HOME"/.verse ] && read -r last <"$HOME"/.verse
+ now=$(date +%Y%m%d)
+ if [ "$now" -gt "${last:-0}" ] ; then
+ verse
+ printf '\n'
+ printf '%s\n' "$now" >"$HOME"/.verse
+ fi
+ fi
)
diff --git a/sh/shrc b/sh/shrc
index 63268272..8dab79c6 100644
--- a/sh/shrc
+++ b/sh/shrc
@@ -13,6 +13,12 @@ command -p setterm -bfreq -blength 2>/dev/null
# Turn off flow control and control character echo
command -p stty -ixon -ctlecho 2>/dev/null
+# Keep around 4K lines of history in memory
+HISTSIZE=$((1 << 12))
+
+# Don't warn me about new mail
+unset -v MAILCHECK
+
# Load all the POSIX-compatible functions from ~/.shrc.d; more advanced shells
# like bash will have their own functions
for sh in "$HOME"/.shrc.d/*.sh ; do
diff --git a/sh/shrc.d/ls.sh b/sh/shrc.d/ls.sh
index a7a6e15f..b4e2d072 100644
--- a/sh/shrc.d/ls.sh
+++ b/sh/shrc.d/ls.sh
@@ -7,7 +7,7 @@ ls() {
# Add --block-size=K to always show the filesize in kibibytes
[ -e "$HOME"/.cache/ls/block-size ] &&
- set -- --block-size=K "$@"
+ set -- --block-size=1024 "$@"
# Add --classify to show trailing indicators of the filetype
[ -e "$HOME"/.cache/ls/classify ] &&
diff --git a/urxvt/ext/select b/urxvt/ext/select.pl
index b35862b2..90b13960 100644
--- a/urxvt/ext/select
+++ b/urxvt/ext/select.pl
@@ -1,9 +1,5 @@
-#!/usr/bin/env perl
-
-#
# Tom Ryder's choice of selection behaviours for urxvt, butchered from included
# URxvt extension scripts.
-#
# Force me to write this properly
use strict;
diff --git a/vim/after/ftdetect/sh.vim b/vim/after/ftdetect/sh.vim
index 26a2c14e..72d72d1d 100644
--- a/vim/after/ftdetect/sh.vim
+++ b/vim/after/ftdetect/sh.vim
@@ -1,3 +1,8 @@
+" .shrc is a shell script
+autocmd BufNewFile,BufRead
+ \ .shrc
+ \ setlocal filetype=sh
+
" .xinitrc is a shell script
autocmd BufNewFile,BufRead
\ .xinitrc
diff --git a/yash/yash_profile b/yash/yash_profile
new file mode 100644
index 00000000..d37f35a1
--- /dev/null
+++ b/yash/yash_profile
@@ -0,0 +1,2 @@
+# Load ~/.profile, because Yash won't by itself, no matter how you invoke it
+[ -e "$HOME"/.profile ] && . "$HOME"/.profile
diff --git a/yash/yashrc b/yash/yashrc
new file mode 100644
index 00000000..eef82e54
--- /dev/null
+++ b/yash/yashrc
@@ -0,0 +1,3 @@
+# Load POSIX interactive shell startup files, because Yash won't do it if not
+# invoked as sh(1)
+[ -e "$ENV" ] && . "$ENV"
diff --git a/zsh/zshrc b/zsh/zshrc
index 014ac3fd..2376e568 100644
--- a/zsh/zshrc
+++ b/zsh/zshrc
@@ -7,5 +7,8 @@ HISTFILE=$HOME/.zsh_history
SAVEHIST=$((1 << 12))
HISTSIZE=$((1 << 10))
-# Load POSIX shell functions
-source "$ENV"
+# Load POSIX shell startup files and then Bash-specific ones
+for sh in "$ENV" "$HOME"/.zshrc.d/*.zsh ; do
+ [[ -e $sh ]] && source "$sh"
+done
+unset -v sh
diff --git a/zsh/zshrc.d/prompt.zsh b/zsh/zshrc.d/prompt.zsh
new file mode 100644
index 00000000..d646c05f
--- /dev/null
+++ b/zsh/zshrc.d/prompt.zsh
@@ -0,0 +1,206 @@
+# Frontend to controlling prompt
+prompt() {
+
+ # What's done next depends on the first argument to the function
+ case $1 in
+
+ # Turn complex, colored PS1 and debugging PS4 prompts on
+ on)
+ setopt promptsubst promptpercent
+
+ # Declare the PROMPT_RETURN variable as an integer
+ declare -i PROMPT_RETURN
+
+ # Set up pre-prompt command
+ precmd() {
+ PROMPT_RETURN=$?
+ }
+
+ # Basic prompt shape depends on whether we're in SSH or not
+ PS1=
+ if [[ -n $SSH_CLIENT ]] || [[ -n $SSH_CONNECTION ]] ; then
+ PS1=$PS1'%n@%m:'
+ fi
+ PS1=$PS1'%~'
+
+ # Add sub-commands; VCS, job, and return status checks
+ PS1=$PS1'$(prompt vcs)$(prompt job)$(prompt ret)'
+
+ # Add prefix and suffix
+ PS1='${PROMPT_PREFIX}'$PS1'${PROMPT_SUFFIX}'
+
+ # Add terminating "$" or "#" sign
+ PS1=$PS1'%#'
+
+ # 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'
+ fi
+
+ # Add a space and define the rest of the prompts
+ PS1=$PS1' '
+ PS2='> '
+ PS3='? '
+ PS4='+<$?> $LINENO:'
+ ;;
+
+ # Revert to simple inexpensive prompts
+ off)
+ unset -v precmd PROMPT_RETURN
+ PS1='\$ '
+ PS2='> '
+ PS3='? '
+ PS4='+ '
+ ;;
+
+ # Git prompt function
+ git)
+ # Bail if we're not in a work tree--or, implicitly, if we don't
+ # have git(1).
+ local iswt
+ iswt=$(git rev-parse --is-inside-work-tree 2>/dev/null)
+ [[ $iswt = true ]] || return
+
+ # Refresh index so e.g. git-diff-files(1) is accurate
+ git update-index --refresh >/dev/null
+
+ # Find a local branch, remote branch, or tag (annotated or not), or
+ # failing all of that just show the short commit ID, in that order
+ # of preference; if none of that works, bail out
+ local name
+ name=$( {
+ git symbolic-ref --quiet HEAD ||
+ git describe --all --always --exact-match HEAD
+ } 2>/dev/null) || return
+ name=${name##*/}
+ [[ -n $name ]] || return
+
+ # Check various files in .git to flag processes
+ local proc
+ [[ -d .git/rebase-merge || -d .git/rebase-apply ]] &&
+ proc=${proc:+$proc,}'REBASE'
+ [[ -f .git/MERGE_HEAD ]] &&
+ proc=${proc:+$proc,}'MERGE'
+ [[ -f .git/CHERRY_PICK_HEAD ]] &&
+ proc=${proc:+$proc,}'PICK'
+ [[ -f .git/REVERT_HEAD ]] &&
+ proc=${proc:+$proc,}'REVERT'
+ [[ -f .git/BISECT_LOG ]] &&
+ proc=${proc:+$proc,}'BISECT'
+
+ # Collect symbols representing repository state
+ local state
+
+ # Upstream HEAD has commits after local HEAD; we're "behind"
+ local -i behind
+ behind=$(git rev-list --count 'HEAD..@{u}' 2>/dev/null)
+ ((behind)) && state=${state}'<'
+
+ # Local HEAD has commits after upstream HEAD; we're "ahead"
+ local -i ahead
+ ahead=$(git rev-list --count '@{u}..HEAD' 2>/dev/null)
+ ((ahead)) && state=${state}'>'
+
+ # Tracked files are modified
+ git diff-files --no-ext-diff --quiet ||
+ state=${state}'!'
+
+ # Changes are staged
+ git diff-index --cached --no-ext-diff --quiet HEAD 2>/dev/null ||
+ state=${state}'+'
+
+ # There are some untracked and unignored files
+ git ls-files --directory --error-unmatch --exclude-standard \
+ --no-empty-directory --others -- ':/*' >/dev/null 2>&1 &&
+ state=${state}'?'
+
+ # There are stashed changes
+ git rev-parse --quiet --verify refs/stash >/dev/null &&
+ state=${state}'^'
+
+ # Print the status in brackets; add a git: prefix only if there
+ # might be another VCS prompt (because PROMPT_VCS is set)
+ printf '(%s%s%s%s)' \
+ "${PROMPT_VCS:+git:}" "$name" "${proc:+:$proc}" "$state"
+ ;;
+
+ # Subversion prompt function
+ svn)
+ # Determine the repository URL and root directory
+ local key value url root
+ while [[ -z $url || -z $root ]] && IFS=: read -r key value ; do
+ case $key in
+ 'URL') url=${value## } ;;
+ 'Repository Root') root=${value## } ;;
+ esac
+ done < <(svn info 2>/dev/null)
+
+ # Exit if we couldn't get either--or, implicitly, if we don't have
+ # svn(1).
+ [[ -n $url && -n $root ]] || return
+
+ # Remove the root from the URL to get what's hopefully the branch
+ # name, removing leading slashes and the 'branches' prefix, and any
+ # trailing content after a slash
+ local branch
+ branch=${url/"$root"}
+ branch=${branch#/}
+ branch=${branch#branches/}
+ branch=${branch%%/*}
+
+ # Parse the output of svn status to determine working copy state
+ local symbol
+ local -i modified untracked
+ while ((!modified || !untracked)) && read -r symbol _ ; do
+ case $symbol in
+ *\?*) untracked=1 ;;
+ *) modified=1 ;;
+ esac
+ done < <(svn status 2>/dev/null)
+
+ # Add appropriate state flags
+ local state
+ ((modified)) && state=${state}'!'
+ ((untracked)) && state=${state}'?'
+
+ # Print the state in brackets with an svn: prefix
+ printf '(svn:%s%s)' "${branch:-unknown}" "$state"
+ ;;
+
+ # VCS wrapper prompt function; print the first relevant prompt, if any
+ vcs)
+ local vcs
+ for vcs in "${PROMPT_VCS[@]:-git}" ; do
+ prompt "$vcs" && return
+ done
+ ;;
+
+ # Show return status of previous command in angle brackets, if not zero
+ ret)
+ ((PROMPT_RETURN)) && printf '<%u>' "$PROMPT_RETURN"
+ ;;
+
+ # Show the count of background jobs in curly brackets, if not zero
+ job)
+ local -i jobc
+ while read -r ; do
+ ((jobc++))
+ done < <(jobs -p)
+ ((jobc)) && printf '{%u}' "$jobc"
+ ;;
+
+ # No argument given, print prompt strings and vars
+ '')
+ declare -p PS1 PS2 PS3 PS4
+ ;;
+
+ # Print error
+ *)
+ printf 'prompt: Unknown command %s\n' "$1" >&2
+ return 2
+ ;;
+ esac
+}
+
+# Start with full-fledged prompt
+prompt on