## ~/.bashrc: executed by bash(1) for non-login shells.
#
## internal use
# bash specific
# limit history file to ~500KiB, ignore duplicates
HISTCONTROL=ignoreboth
HISTSIZE=20000
HISTFILESIZE=20000
PROMPT_COMMAND=set_prompt
# bash-completion, ignore if not installed
. '/usr/share/bash-completion/bash_completion' 2> /dev/null || :
# color support
export COLOR=1; case $TERM in
*color | linux) ;; # known color terminals
*) [ $(tput colors) -lt 8 ] && unset COLOR
esac
# persist $OLDPWD between sessions
export LASTDIR="${XDG_RUNTIME_DIR:-/tmp}/.oldpwd"
[ -f "$LASTDIR" ] && read -r OLDPWD < "$LASTDIR"
# truncate long prompt pathnames over N characters
export PATH_WIDTH=50
# set window title and terminal prompt
# embed git status information if available
set_prompt() {
if [ ! -z $COLOR ]; then
u='\[\e[1;32m\]' # user/hostname color
p='\[\e[1;34m\]' # path color
r='\[\e[0m\]' # reset
fi
# speed-hack that disables path-gitstatus on extremely slow filesystems
TIME_NOW=${EPOCHREALTIME%.*}${EPOCHREALTIME#*.} # disable at 500ms
if [ ! -z $PROMPT_LATENCY ] && [ $PROMPT_LATENCY -gt 500000 ]; then
alias path-gitstatus='! :'
fi
# set window title with OSC '\e]0;
\a' and prompt
git_path="$(path-gitstatus -pe${COLOR:-n})" \
|| path="$(path-shorthand)" \
|| path="('${PWD##*/}' no longer exists)" # no such file or directory
PS1="\[\e]0;\u@\h: \w\a\]${u}\u@\h${r}:${git_path:-${p}${path}${r}}\$ "
unset u p r path git_path
export PROMPT_LATENCY=$((${EPOCHREALTIME%.*}${EPOCHREALTIME#*.} - TIME_NOW))
# command history persistence across sessions
history -a; history -c; history -r
}
# internal echo function
announce() (
msg="$@";
printf '\e[30;46m%s%s\e[0m\n' "$msg" \
"$(tr '\0' ' ' < /dev/zero \
| dd count=1 bs=$(($(tput cols) - ${#msg})) 2> /dev/null)"
)
# unmap current X window and restore it after background process returns
swallow() (
[ ! -z "$1" ] || exit 1
WINID="$(xdotool getactivewindow)" || exit 1
"$@" 2> /dev/null &
xdotool windowunmap "$WINID" && wait
xdotool windowmap "$WINID"
)
#
## external use aliases/shell functions
# enable terminal swallowing for selected X applications
for f in feh mpv xdiskusage; do
alias "$f"="swallow $f"
done && unset f
# ncurses high contrast fallback colors for TUI applications that make heavy
# use of background colors such as blue or red
for f in nmtui; do
eval "$f() { palette ncurses; command $f \"\$@\"; palette; }"
done && unset f
# convert plaintext for rendering in a web browser
alias markdown='cmark-gfm --smart --hardbreaks \
-e autolink -e table -e strikethrough'
# spawn static web server in the current directory
alias httpd='pkill busybox; busybox httpd -p 8080'
# create parent directories
alias mkdir='mkdir -p'
# prompt before overwrite
# preserve timestamps
alias cp='cp -ip'
alias mv='mv -i'
# keep ANSI colors while paging
alias less='less -R'
# default to git diff
alias diff='git diff --no-index'
# use external overlay for GNU nano
alias nano='nano-overlay'
ls() (
# files with full permissions
export LS_COLORS='ow=107;30;01'
# identify file types regardless of color support
arg='--classify' # fallback
[ ! -z $COLOR ] && arg='--color'
command ls --literal --group-directories-first $arg "$@"
)
cd() {
case "$1" in
...*) # shorthand aliases for referring to parent dirs
_a="${1#??}" && shift # convert ... into ../../ and so on
_e='../'
while [ ! -z "$_a" ]; do
[ "${_a#${_a%?}}" = '.' ] && _e="$_e../"
_a="${_a%?}"
done
set -- "$_e" && unset _a _e;;
-f) # interactive fuzzy find and jump into sub-directory with fzf
shift # usage: cd -f [optional query]
_e="$(find . -type d | sed 's,^./,,g' | fzf ${1:+-q "$1"} \
-1 -0 --no-multi --layout=reverse --height=90%)" \
|| echo 'Not found.'
set -- "$_e" && unset _e;;
esac
# preserve $OLDPWD between sessions
command cd "$@"
echo "$PWD" > "$LASTDIR"
}
# reformat bash online documentation with man pager
help() (
[ -z "$1" ] && command help
for f in "$@"; do # decorate bold text
if page="$(command help -m "$f")"; then
page="$(echo "$page" | sed -E 's/[A-Z]{2,}/\\e[1m&\\e[0m/g')"
printf "%b" "$page" | less -R
fi
done
)
# search and reformat POSIX.1-2017 online documentation with man pager
posix() (
# docs location
docs="$HOME/.local/share/doc/susv4-2018"
abort() {
echo 'usage: posix [ -l ] [ section no. ] "SEARCH TERM"' 1>&2; exit 1;
}
[ "$1" = '-l' ] && list=1 && shift
case "$1" in # list available pages
1) docs="$docs/utilities";; # XCU - posix shell
2) docs="$docs/functions";; # XSH - *NIX syscalls
3) docs="$docs/basedefs";; # XBD - C standard library
*) abort
esac
[ ! -z "$list" ] && { # list available pages in section
echo "Available entries in section $1:"
find "$docs" -type f | while read -r str; do
str="${str##*/}"; echo "${str%.*}"
done | sort | paste -s | fold -s
exit
}
[ "$#" -eq 2 ] || abort
match="$(find "$docs/$2.html" -type f 2> /dev/null)" || {
echo "'$2' not found in section $1, exiting."
exit 1
}
{ pandoc -s -f html -t man | man -l -; } < "$match"
)
# implicitly set git dir to ~/.config/meta if outside a git dir
git() (
iterate_thru() {
command git meta "$@" &
find -L ~/Git -name '*.git' -type d | sed 's,/.git$,,' \
| xargs -I '{}' -P0 git -C '{}' "$@"
wait
}
case "$1" in
-C | init | clone | meta) ;;
sync) # sync dotfiles and ~/Git directory in parallel
ssh-add -l > /dev/null || ssh-add || return
iterate_thru pull -v && return;;
vacuum) # run gc on dotfiles and ~/Git directory in parallel
iterate_thru gc --aggressive --prune=now && return;;
*) command git status > /dev/null 2>&1 || set -- meta "$@"
esac
command git "$@"
)
# fallback to normal color scheme
# allows running macro scripts to mangle the .sc file at runtime
# remove unwanted backup files appended with .sc~
sc() (
palette ncurses
# run executable sc macro scripts in the same dir if they exist
# macro scripts must share the same initial name as .sc file
# eg. sheet1.sc -> ./sheet1.sc*
for f in "$@"; do
for g in "$f"*; do
[ -x "$g" ] && {
# allow for absolute filenames
[ "${g%${g#?}}" = '/' ] && "$g" || ./"$g"
}
done
done
command sc "$@"
for f in "$@"; do rm -rf -- "${f}~"; done
palette
)
#
## accounting/timekeeping routines based around nano-overlay
# calendar reminder function
# sorts a list of upcoming dates and calculates countdowns
upcoming() (
quit() { echo "$@"; exit; }
[ ! -z "$1" ] || quit 'usage: upcoming [file]'
[ -f "$1" ] || quit 'File not found.'
export EXTERN_EDITOR='cat'
today="$(date '+%Y/%m/%d')"
now=$(date -d "$today" '+%s')
# expected format: one or more of 'YYYY/MM/DD\tMSG\n'
nano-overlay -s "$1" | sed 's/#.*$//g' | sort | grep . \
| while read -r date msg; do
# skip if in the past
epoch=$(date -d "$date" '+%s') || exit 1
[ $epoch -gt $((now - 1)) ] || continue
days=$(((epoch - now) / 86400))
case $days in # countdown
0) away='today';;
1) away='1 day';;
*) away="$days days"
esac
printf '%s %s %s\n' "* $date" "($away)" "${msg:-(none)}"
[ $days -lt 90 ] && ncal -b -d "$date" -H "$date"
done || exit 1
)
# reentrant encrypted wrapper for ledger-cli
# similar to gzcat for nano-overlay | ledger -f - except that passing
# no add'l options enables REPL mode in ledger-cli
ledger-enc() (
quit() { echo "$@"; exit; }
[ ! -z "$1" ] || quit 'usage: ledger-enc [file]' 1>&2
[ -f "$1" ] || quit 'File not found.' 1>&2
file="$1" && shift
export EXTERN_EDITOR='ledger -f'
export EXTERN_ARGS="$@"
export LEDGER_ENC_DEPTH="$((LEDGER_ENC_DEPTH + 1))"
# update price history for commodities if 'price.db' exists in the same dir
# and date of last fetch was over 3 hours ago
parent_dir="${file%/*}"
[ "$parent_dir" = "$file" ] && parent_dir='.'
LEDGER_PRICE_DB="$parent_dir/price.db"
[ -f "$LEDGER_PRICE_DB" ] && [ $LEDGER_ENC_DEPTH -lt 2 ] && {
echo "Using '$LEDGER_PRICE_DB'" 1>&2
export LEDGER_PRICE_DB
now="$(date '+%s')"
last_fetch="$(cat "$LEDGER_PRICE_DB" | tail -1 \
| tr ' ' '\t' | cut -f2,3 | xargs -I '{}' date '+%s' -d '{}')"
[ $((now - last_fetch)) -gt $((60 * 60 * 3)) ] && {
ledger-enc "$file" commodities \
| xargs getquote >> "$LEDGER_PRICE_DB"
}
}
nano-overlay -s "$file"
)
# generic plotting wrapper for gnuplot and ledger-cli
# usage: ledger-plot [file] [-j | -J] r [account]
ledger-plot() (
# fall back to terminal output if not on X
term="dumb $COLUMNS $LINES feed"
[ ! -z "$DISPLAY" ] && term="x11 persist title '$@'"
{ cat <<- EOF; ledger-enc "$@" --sort date; } | gnuplot
set terminal $term
set xdata time
set timefmt "%Y-%m-%d"
${DISPLAY:+set grid}
set style fill solid
set format x "%m/%d"
set tics font "Helvetica,16"
unset key
plot "-" using 1:2 with lines linewidth 2
EOF
)
# rename files to a generic filename
mv-generic() (
for f in "$1"; do
[ -f "$1" ] || exit 1
new="$(sha1sum < "$1" | tr ' ' '\t' | cut -f1)"
mv -iv "$f" "$new.${f#*.}"
done
)
#
## atelier dotfile mangling and rice routines
# automatically run ~/.once.d post-install scripts
post-install() (
retries=10
! is-container && [ $(id -u) -eq 0 ] && \
{ echo 'You must not be root.'; exit 1; }
for f in ~/.once.d/*.sh; do
unset iters
while announce ">>> Running '${f##*/}'" && ! $f; do
iters=$((iters + 1))
announce "Retrying... (attempt $iters/$retries)"
[ $iters -lt $retries ] && sleep 1 || exit 1
done
done
)
# check for updates, purge old kernel versions
# if called from a script, run in unattended mode
update() (
env='DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a'
case "$-" in *i*) unset env;; esac
for f in update dist-upgrade autopurge clean; do
announce ">>> $f"
sudo $env apt-get "$f" || exit
done
# semantic versioning sort, zero-pad numbers to 3 digits
pad="$(tr '\0' '0' < /dev/zero | dd bs=3 count=1 2> /dev/null)"
for f in $(dpkg --get-selections | egrep '^linux-image-[0-9]+' | cut -f1 \
| sed -E -e "s/([0-9]+)/${pad}\1/g" -e "s/0*([0-9]{${#pad}})/\1/g" \
| sort -r | sed -E -e "s/0*([0-9]+)/\1/g" -e 's/image/\*/' \
| tail -n +2); do
announce "removing $f..."
sudo $env apt-get autopurge "$f" || exit
done
)
# display ANSI terminal colors
colors() (
for f in 40 100; do
for g in $(seq 0 7); do
code=$((f + g))
unset s; # generate padding
for h in $(seq $((8 - ${#code}))); do s="$s "; done
printf '\e[%dm%s' "$code" "$s$code"
done
printf '\e[0m\n'
done
)
# reload terminal configuration through xrdb
# accepts optional colorscheme name
reload() {
find ~/.local/include/colors -type f | while read -r f; do
if echo "${f##*/}" | fgrep -q "${@:-nightdrive}"; then
sed "/#include