Shells
Table of Contents
1. Profile
I don't know how "portable" a .profile
can be, but let's try!
Although I'm not using acme as my go-to text editor anymore, I still like to use it from time to time, and have the rest of the plan9 ports at hand.
I manually fetched and installed the ports in /usr/local/plan9
,
and need to define $PLAN9
in order for the various tooling to work.
PLAN9=/usr/local/plan9 export PLAN9
I also tend to have an abnormal $PATH
:
PATH=$HOME/bin:$HOME/opt/emacs/bin:$HOME/opt/gcc10/bin:$HOME/go/bin:$HOME/opt/unnethack/bin:$HOME/.local/bin:$HOME/.node_modules/bin:/home/ports/infrastructure/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin:/usr/games:/usr/local/jdk-11/bin:$PLAN9/bin export PATH HOME TERM
Let's split it:
$HOME/bin
is for my personal scripts, it needs to take precedence over anything else$HOME/opt/*
contains various stuff I compile from source, like emacs$HOME/.local/bin
is XDG stuff I'm practically forced to use$HOME/.node_modules/bin
is for node/home/ports/infrastructure/bin
holds some handy port tools- the rest is the usual
$PATH
on OpenBSD, with the addition of java and plan9 at the end.
Just in case I want to play with lua again:
if which luarocks-5.3 2>/dev/null >/dev/null; then eval "$(luarocks-5.3 path --bin)" fi
Tell npm to install things globally in the right directory:
export npm_config_prefix=~/.node_modules
ksh doesn't have a "deafult" configuration file (like ~/.zshrc
for zsh or ~/.bashrc
for bash); instead, if called interactively
it loads the file pointed by $ENV
. Tell ksh to load ~/.kshrc
then:
export ENV=$HOME/.kshrc
An UTF-8 locale is almost mandatory, and since we're there use
en_US
as the language, even if it's not my main tongue
export LANG=en_US.UTF-8
Got is quickly becoming my favourite version control system. It should be able to load the author data from a config file, but I still keep this variable, just in case
export GOT_AUTHOR="Omar Polo <op@omarpolo.com>"
Sometimes I need to do stuff with Docker. I have a virtual machine running alpine with docker configured, and this bit here will allow docker-cli to transparently talk to the VM:
export DOCKER_HOST=ssh://op@100.64.2.3:22
I like to use mg
as my default editor, but under vterm
is a bit
of a pain in the ass to use, since the various C-c
and C-x
command are intercepted by Emacs. Thus, use vi
as the editor when
INSIDE_EMACS
(which sound strange, but for programs like got
that spawns an editor it's the best choice I found)
if [ -z "$INSIDE_EMACS" ]; then VISUAL=mg EDITOR=mg else VISUAL=vi EDITOR=ed fi export VISUAL EDITOR
less should be the default pager pretty most everywhere, but ensure that!
export MANPAGER=less
I've found a cool pager for PostgreSQL, pspg. It's designed explicitly for tabular data. Extra points for having some cool light themes! Tao light theme (the number 20) is my favourite.
export PSQL_PAGER='pspg -s20'
I'm using reposync to manage my local clone of the OpenBSD source
tree. Technically this isn't needed, because now /home/ports
is a
checkout from /home/cvs/...
, but anyway
export CVSROOT=/home/cvs
This is just to make some command outputs a bit nicer:
export BLOCKSIZE=1m
I don't particularly like colored outputs when I'm in front of a terminal, so I tend to disable them:
export NO_COLOR='yes, please' export CMAKE_COLOR_MAKEFILE='OFF' export WG_COLOR_MODE=never
As an exception to the "no colors" rules, I'm trying to enabling
some colors in tog
, but cautiously, and see how it goes:
export TOG_COLORS=yes export TOG_COLOR_DIFF_MINUS=magenta export TOG_COLOR_DIFF_PLUS=blue export TOG_COLOR_DIFF_CHUNK_HEADER=green export TOG_COLOR_DIFF_META=default export TOG_COLOR_COMMIT=default export TOG_COLOR_AUTHOR=default export TOG_COLOR_DATE=default export TOG_COLOR_REFS_REMOTES=red
Then disable the colors in boot (a clojure build tool I'm not using
anymore) and fix its gpg
command:
export BOOT_COLOR=no export BOOT_GPG_COMMAND=gpg2
At some point I had a problem with leiningen that required this
workaround. I've mostly switched to deps.edn
now, but it's useful
to keep this around:
export SHASUM_CMD='cksum -a sha256'
At least on OpenBSD, automake and autoconf requires these variables to be set to work
export AUTOCONF_VERSION=2.69 export AUTOMAKE_VERSION=1.16
Some time ago I played with gerbil, a scheme dialect, and I should still have it installed.
GERBIL_HOME=/usr/local/gerbil PATH=$PATH:$GERBIL_HOME/bin export GERBIL_HOME PATH
I also played a bit with chicken. Don't remember why I set all these variables, but for the time being, keep 'em
#export CHICKEN_INSTALL_REPOSITORY=$HOME/.local/lib/chicken chicken=$HOME/.chicken/11/ CHICKEN_REPOSITORY_PATH=$chicken:/usr/local/lib/chicken/11 CHICKEN_INSTALL_REPOSITORY=$chicken CHICKEN_INCLUDE_PATH=$chicken CHICKEN_DOC_REPOSITORY=$chicken/chicken-doc export CHICKEN_REPOSITORY_PATH CHICKEN_INSTALL_REPOSITORY export CHICKEN_INCLUDE_PATH CHICKEN_DOC_REPOSITORY
I don't use ripgrep, grep is fine for me, but I remember I was particularly annoyed by the format of its output. Just in case I need to use it again, here's what I did: first define an env variable that points to a configuration file:
export RIPGREP_CONFIG_PATH=$HOME/.ripgreprc
then put the following in ~/.ripgreprc
:
# disable colors --color=never # decent output format, like grep -Hn --vimgrep # use smart case --smart-case
Finally, load the specific profile for this machine, if it exists:
if [ -f "$HOME/.profile-local" ]; then . $HOME/.profile-local fi
2. OpenBSD ksh
OpenBSD ksh (sometimes called opdksh or oksh) is the default shell on OpenBSD, and is generally my go-to choice on other systems too. It has a good ratio of feature and simplicity.
if [ "$TERM" = dumb ]; then PS1='$ ' return fi
Enable emacs-like command editing and csh-like history expansion
with !
set -o emacs set -o csh-history
Talking about history, by default ksh won't store any. I don't know how I could live without it, so please enable it!
HISTCONTROL=ignoredups:ignorespace HISTFILE=$HOME/.history HISTSIZE=10000
reset
doesn't work as expected inside tmux, the old output can
still be consulted when scrolling. If I bother to type reset
I
want to be sure that the history was cleared, otherwise I'd use
clear
.
if [ -n "$TMUX" ]; then alias reset='reset && tmux clear-history' fi
CDPATH
is super-useful! I wrote a post about it, also.
export CDPATH=.:$HOME/w:/home/ports:/home/ports/mystuff:$HOME/quicklisp/local-projects
I love to hate gpg! It needs some special treatments to work, and this should also fix pinentry over ssh. I'm not sure it works though, it's been a while since I connected remotely to my desktop:
export GPG_TTY=$(tty) if [ -n "$SSH_CONNECTION" ]; then export PINENTRY_USER_DATA="USE_CURSES=1" fi
The BSDs have this incredibly useful signal available, SIGINFO
,
that it's a shame not to use it!
stty status ^T
I really like my prompt to be as minimal as possible. For some time
I used a single colon ;
as prompt, it's really nice! At the
moment though, I'm using a plan9-esque %
:
PS1='% '
2.1. Gemini client
I got tired of trying to remember the set of flags for nc
to talk
to Gemini serves, so here we are
# gemini host [port] # "post" stdin to the given gemini server gemini() { host=${1:?missing host} port=${2:-1965} nc -c -Tnoverify "${host}" "${port}" }
2.2. vterm integration
vterm
can recognize special escape sequence to pass information
(like the current directory) back to Emacs.
This is an utility function to print things for vterm:
vterm_printf() { if [ -n "$TMUX" ]; then printf '\ePtmux;\e\e]%s\007\e\\' "$1" elif [ "${TERM%%-*}" = "screen" ]; then printf '\eP\e]%s\007\e\\' "$1" else printf '\e]%s\e\\' "$1" fi }
I like to improve the default vterm experience. The following will
set the hostname and path every time the $PS1
is printed, so the
vterm buffer name can stay in sync, and also overrides the cd
command:
clear() { vterm_printf '51;Evterm-clear-scrollback' tput clear } vterm_set_title() { printf '\033]0;%s\007' "$(hostname):$PWD" } vterm_prompt_end() { vterm_printf "51;A$USER@$(hostname):$PWD"; } function cd { builtin cd "$@" vterm_set_title } vterm_set_title PS1=${PS1%% }'$(vterm_prompt_end) '
but do this only when $INSIDE_EMACS
is equal to vterm
!
if [[ "$INSIDE_EMACS" = 'vterm' ]]; then fi
2.3. completions
OpenBSD ksh has a limited support for programmed completions! The
idea is that completions are provided via a complete_$programname
array. It's possible to provide specific completion for the nth
argument via the array complete_$progname_$nth
.
I mean, it's not zsh
or fish
, but it's more than enough!
Here's a completion for ssh and scp:
HOST_LIST=$(awk '/Host /{print $2}' ~/.ssh/config | xargs echo) set -A complete_ssh -- $HOST_LIST set -A complete_scp -- $HOST_LIST
and another simple one for kill and pkill
set -A complete_kill_1 -- -9 -HUP -INFO -KILL -TERM set -A complete_pkill_2 -- -SIGHUP -SIGUSR1 -SIGUSR2 -SIGTERM -SIGKILL
If we're on a machine with vmd(8)
, the following will add
completions for the subcommands and for the virtual machines:
if pgrep -fq /usr/sbin/vmd; then set -A complete_vmctl_1 -- console load reload start stop reset \ status send receive set -A complete_vmctl -- \ $(vmctl status | awk '!/NAME/ { printf "%s ", $NF }') fi
Completions for ifconfig are also nice:
set -A complete_ifconfig_1 -- $(ifconfig | grep ^[a-z] | cut -d: -f1)
Add some for Got and Git:
set -A complete_got_1 -- \ bl blame \ bo backout \ br branch \ ci commit \ co checkout \ cy cherrypick \ di diff \ he histedit \ im import \ in init \ log \ rb rebase \ ref \ rm remove \ rv revert \ sg stage \ st status \ tr tree \ ug unstage \ up update set -A complete_git_1 -- \ checkout cherry-pick clean clone commit config \ mpull mpush \ pull push \ status
2.4. Aliases
Some misc aliases:
alias ls="ls -F" alias serve="python3 -m http.server" alias ec='emacsclient -nw -c' # colors ain't welcome here! alias nim="nim --colors=off"
2.5. misc functions
What follows are functions that aren't big enough to be worth a whole file.
I think I stealed this two from someone. They make a backup copy
of the file and then launch an editor on that, super useful when
porting. The first uses mg
and elevates the privileges with doas
mgdiff() { if [ -z "$1" ]; then printf "%s\n" "USAGE: mgdiff <file>" >&2 return fi doas cp -p "$1" "$1.orig" doas mg "$1" }
The second one uses vi
without doas
:
vdiff() { if [ -z "$1" ]; then printf "%s\n" "USAGE: vdiff <file>" >&2 return fi cp -p "$1" "$1.orig" vi "$1" }
hist
is a quick wrapper around history
and grep
, to quickly
search for a previous command:
hist() { if [ -z "$1" ]; then printf "%s\n" "USAGE: hist <pattern>" >&2 return 1 fi history 0 | grep "$1" }
nnn
is a quick and useful file manager for the terminal. One
useful feature is "auto-cd", where one can navigate the filesystem
with nnn
and upon exit, the shell will change directory to the
last visited. It's pretty simple to setup, albeit probably prone
to races. While there, also define some bookmarks:
export NNN_BMS="h:$HOME;t:/tmp" export NNN_USE_EDITOR=1 bind -m '^O'='^U ncd^J^Y' ncd() { # block nesting of nnn in subshells if [ "${NNNLVL:-0}" -ge 1 ]; then echo nnn is aready running return fi export NNN_TMPFILE=$HOME/.config/nnn/.lastd nnn "$@" if [ -f "$NNN_TMPFILE" ]; then . "$NNN_TMPFILE" rm "$NNN_TMPFILE" fi }
goman
is a small wrapper to invoke go doc
with a pager, which
is useful when reading documentation on xterm:
goman() { if [ -z "$1" ]; then echo "USAGE: goman terms..." >&2 return 1 fi go doc "$@" 2>&1 | ${MANPAGER:-less} }
rebuild_gerbil_doc
rebuilds the website with the gerbil
documentation from the source shipped with the package into
/var/www/cons.local
rebuild_gerbil_doc() { rm -rf /tmp/build_gerbil_doc mkdir /tmp/build_gerbil_doc || return 1 cp -R /usr/local/gerbil/doc /tmp/build_gerbil_doc/ || return 1 cd /tmp/build_gerbil_doc/doc/ ./build.sh || return 1 rm -rf /var/www/cons.local/* cp -R .vuepress/dist/* /var/www/cons.local/ }
2.6. porting-related
One of these days I'll spend some time to split and document each bit, and maybe drop unused stuff
# ports stuff alias portsql='sqlite3 /usr/local/share/sqlports' alias portslol='make 2>&1 | /home/ports/infrastructure/bin/portslogger .' alias portspldc='make port-lib-depends-check' alias portsldc='make lib-depends-check' alias portsplif='diff -up pkg/PLIST.orig pkg/PLIST' alias portstsilp='mv pkg/PLIST.orig pkg/PLIST' alias portspy3plist='FLAVOR=python3 make plist' alias portsrc='cd `make show=WRKSRC`' alias portsfast='MAKE_JOBS=6 make' portsdiff() { cvs diff > /home/ports/mystuff/${PWD##*/}.diff ; less /home/ports/mystuff/${PWD##*/}.diff ;} portslessdiff() { less /home/ports/mystuff/${PWD##*/}.diff ; } # portscp() { scp /home/ports/mystuff/${PWD##*/}.diff virtie:/var/www/iota/ports/ && echo https://chown.me/iota/ports/${PWD##*/}.diff ;} portspy3() { FLAVOR="python3" make "$@" ;} portspy3and2() { make "$@" ; FLAVOR="python3" make "$@" ;} portspygrep() { (cd /home/ports && grep "$@" */py-*/Makefile ) ;} portslib() { nm -g "$1" | cut -c10- | grep -e^T > /tmp/"$(pwd |xargs basename)" ;} portsfind() { find /home/ports -iname "${1}" -exec grep -iH ${2} {} \; ;} portsgrep() { ( cd /home/ports && grep "$@" */*/Makefile */*/*/Makefile ) ;} alias mup="make update-patches" alias pfast="MAKE_JOBS=7 make" alias m="make" alias mpldc="make port-lib-depends-check" pclear() { doas find /home/ports/packages/ -iname "*${1:?}*" -delete doas find /home/ports/plist/ -iname "*${1:?}*" -delete }
3. rc
Although it's not my interactive shell, I do like plan9' rc.
My configuration file is pretty small:
prompt=('% ' '') user=$USER home=$HOME fn % { $* } fn git { env git --no-pager $* }
I use the following for the plumber, although it probably can be improved:
addr=':(#?[0-9]+)' protocol='(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais)' domain='[a-zA-Z0-9_@]+([.:][a-zA-Z0-9_@]+)*/?[a-zA-Z0-9_?,%#~&/\-]+' file='([:.][a-zA-Z0-9_?,%#~&/\-]+)*' # open http urls. data regexps is the same for file plus : type is text data matches $protocol://$domain$file plumb to web plumb start web $0 # RFC's from one of the nicer-looking repositories. type is text data matches 'RFC:([0-9]+)' plumb to web plumb start browser https://tools.ietf.org/html/rfc$1 # open python error message type is text data matches ' *File "([a-zA-Z0-9_\.\/]*)", line ([0-9]*).*' plumb to edit arg isfile $1 data set $file attr add addr=$2 plumb client $editor # open pdf with xdg-open type is text data matches '[a-zA-Z¡-0-9_\-./]+' data matches '([a-zA-Z¡-0-9_\-./]+)\.(ps|PS|eps|EPS|pdf|PDF|dvi|DVI)' arg isfile $0 plumb to postscript plumb start xdg-open $file # show git log type is text data matches 'commit ([a-z0-9]*)' arg isdir . data set $dir plumb start sh -c 'cd '$dir'; git show '$1' | 9p write acme/new/body' # show git log type is text data matches 'commit ([a-z0-9]*)' arg isdir . data set $dir plumb start sh -c 'cd '$dir'; git show '$1' | 9p write acme/new/body' # git pull type is text data matches '.*[pP][uU][lL][lL].*#([0-9]*)' arg isdir . data set $dir plumb start sh -c 'cd '$dir'; browser $(git remote get-url origin | sed "s/\.git//")/pull/'$1 # git issue type is text data matches '[iI][sS][sS][uU][eE] #([0-9]*)' arg isdir . data set $dir plumb start sh -c 'cd '$dir'; browser $(git remote get-url origin | sed "s/\.git//")/issues/'$1 # git issue type is text data matches '.*fix.*#([0-9]*)' arg isdir . data set $dir plumb start sh -c 'cd '$dir'; browser $(git remote get-url origin | sed "s/\.git//")/issues/'$1
4. SQLite
SQLite has a configuration file that gets executed every time is
launched. I like to change the default glyph for the NULL
value
.nullvalue '⊥'
and enable the box
mode. This is kinda new, so it may not work in
some older version
.mode box
It looks like this:
.mode box select 42 as response;
┌──────────┐ │ response │ ├──────────┤ │ 42 │ └──────────┘
5. psql
By default psql renders NULL
values as empty strings. This makes
it harder to "see" if a column is NULL
or an empty string, so
change the default NULL
glyph:
\pset null '⊥'
I also use to connect to databases to different hosts, so to be
extra sure I made psql
print the connection info right away:
\conninfo
6. Scripts
6.1. acmerc
I use the following script to launch acme in all its glory.
#!/usr/bin/env rc . $home/lib/profile if (~ $PLAN9 '') { echo '$PLAN9 is not defined!' exit 1 } NAMESPACE=/tmp/ns.$user.$pid SHELL=rc PAGER=nobs MANPAGER=nobs EDITOR=editinacme VISUAL=editinacme mkdir -p $"NAMESPACE plumber fontsrv & fontsrvpid=$apid font=/mnt/font/GoMono/10a/font FONT=/mnt/font/InputSans-Regular/10a/font $PLAN9/bin/acme -a -f $font -F $FONT $* & acmepid=$apid { sleep 1 winid=1 exec acmeeval 'autoacme '$home'/bin/acmeconfig' } & acmeevalpid=$apid wait $acmepid kill $acmeevalpid kill $fontsrvpid wait # just in case rm -rf $"NAMESPACE
6.2. browser
The browser
script is my default browser. It launches the
correct browser depending on what is currently running
#!/bin/sh if pgrep firefox >/dev/null 2>&1; then exec firefox "$1" fi if pgrep iridium >/dev/null 2>&1; then exec iridium "$1" fi exec firefox "$1"
6.3. clbin
Posts its input to clbin
#!/bin/sh exec curl -F 'clbin=<-' https://clbin.com
6.4. menu
This generates a menu for a dmenu
like program. In particular,
it uses my own mymenu.
#!/bin/ksh a-menu() { mymenu -f 'Go Mono-11' -l vertical -p '% ' \ -W 50% -H 30% -P 10 -x center -y center \ -C '#ffffea' -c '#000' -T '#ffffea' \ -t '#000' -S '#000' -s '#fff' -b 3 \ -a } # pass p() { prefix=${PASSWORD_STORE_DIR:-~/.password-store} typeit=${1:-no} sleep 1 p=$(find "$prefix" -type f -iname '*.gpg' | \ sort | \ sed -e 's/\.gpg$//' -e "s,^$prefix/,," | \ a-menu) if [ $? -eq 0 ]; then if [ "$typeit" = yes ]; then pass show "$p" | { IFS= read -r pass; printf %s "$pass"; } | xdotool type --clearmodifiers --file - else pass show --clip "$password" fi fi } # exec e() { if ! x=$(a-menu); then return elif [ "$x" = "pass" ]; then p yes elif [ "$x" = "pass copy" ]; then p nope elif [ "$x" = "keep" ]; then exec keepassxc else exec $x fi } ( echo audacity echo blender echo chrome echo dino echo emacs echo emacsclient -c echo firefox echo gajim echo gimp echo godot echo inkscape echo iridium echo keep echo lagrange echo libreoffice echo links -g -mode 800x600 echo lmms echo luakit echo lxappearance echo mumble echo netsurf-gtk3 echo obs echo pass echo pass copy # not "copy pass" so it's after pass echo pixelorama echo poedit echo spectral echo tor-browser echo xfe echo zathura ) | e
6.5. record
Record, as the name suggest, records a portion of the screen to a file.
#!/bin/ksh if ! s=$(slop -f "%x %y %w %h"); then exit 1 fi set -A s -- $s x=${s[0]} y=${s[1]} w=${s[2]} h=${s[3]} exec ffmpeg -y \ -f x11grab \ -s ${w}x${h} \ -framerate 30 \ -i $DISPLAY+${x},${y} \ ${1:?missing output file}
6.6. stumpwm-wrapper
I like to jump between stumpwm and cwm, but I haven't found a way
to do exec cwm
from lisp, hence I'm using this script from cwm
to switch to stumpwm
.
#!/bin/sh stumpwm exec cwm
6.7. xdg-open
Time ago I decided to just stop even trying to tame xdg-open
and
fix the problem at the root, that is, by getting rid of it.
I have an xdg-open
scripts that implements the rules that I
want, not some coincidences decided by the order in which the
package were installed.
pdf-tools
, so there isn't any
need for zathura.
#!/bin/sh case "$@" in *://*) exec browser "$@" ;; *jpg|*jpeg) exec gpicview "$@" ;; *mp4|*mkv) exec mpv "$@" ;; *m4a) exec mpv --force-window --lavfi-complex='[aid1] asplit [ao] [v] ; [v] showwaves=mode=line:split_channels=1 [vo]' "$@" ;; *svg) exec inkscape "$@" ;; *core) ;; # do nothing *png) exec gpicview "$@" ;; *gif) exec gpicview "$@" ;; *webp) exec gpicview "$@" ;; *) exec emacsclient -c "$@" ;; esac