This commit is contained in:
Ray Elliott 2026-10-27 13:13:37 +00:00
commit ea76cc9ef2
7 changed files with 208 additions and 173 deletions

View file

@ -16,6 +16,7 @@ or Nushell.
- Load environment setup that is portable at login, such as Cargo's `env` file. - Load environment setup that is portable at login, such as Cargo's `env` file.
- Provide common aliases and small interactive functions that are useful - Provide common aliases and small interactive functions that are useful
across supported Bourne-family shells. across supported Bourne-family shells.
- Isolate Arch Linux commands from otherwise portable shell configuration.
## Boundaries ## Boundaries
@ -23,7 +24,7 @@ The `profile` file must remain quiet, idempotent and safe for a POSIX shell to
source during login. It must not print an alias catalogue, configure a prompt, source during login. It must not print an alias catalogue, configure a prompt,
perform interactive terminal operations or start background work. perform interactive terminal operations or start background work.
Interactive shells should source `aliases.sh` from their own RC file. The Interactive shells should source `interactive.sh` from their own RC file. The
login profile deliberately does not source it, because login profiles may also login profile deliberately does not source it, because login profiles may also
run in non-interactive sessions. run in non-interactive sessions.
@ -34,18 +35,26 @@ belong in the separate `systemd` repository.
## Files ## Files
- `profile`: portable login environment and PATH construction. - `profile`: portable login environment and PATH construction.
- `aliases.sh`: common interactive aliases and helper functions. - `interactive.sh`: POSIX loader and manually curated alias reminder.
- `commands.tsv`: curated command descriptions and startup visibility. - `aliases.sh`: common POSIX interactive aliases and helper functions.
- `commands.sh`: command-catalogue display and validation functions.
- `hosts.sh`: SSH destination listing function.
- `platform/arch.sh`: Arch Linux applications, GNU options and Pacman/systemd
integration, written with POSIX shell syntax.
- `commands.tsv`: curated command descriptions and reminder visibility.
- `commands.README.md`: format and maintenance instructions for `commands.tsv`. - `commands.README.md`: format and maintenance instructions for `commands.tsv`.
- `install.sh`: installs the `~/.profile` symlink. - `install.sh`: installs the `~/.profile` symlink.
## Alias portability ## Alias portability
`aliases.sh` is intended to become POSIX-compatible for Bourne-family shells, The shared interactive files use POSIX shell syntax. Bash-only compatibility
but it currently contains some Bash-oriented constructs. Before another shell aliases—including names that are deliberately being retired—live in the Bash
sources it, replace or isolate uses of `source`, `disown`, `local`, `echo -e`, repository's `aliases.bash` instead.
Bash-style `cd --`, and the transitional `debug.log` alias name.
External commands remain platform-specific by design: aliases for Pacman, External commands remain platform-specific by design: aliases for Pacman,
systemd, GNU utilities and local applications target this Arch Linux setup. systemd, GNU utilities and local applications target this Arch Linux setup.
Syntax portability does not imply operating-system portability. Syntax portability does not imply operating-system portability.
The Arch `update` function runs the local `archnews_wrap` Pacman workflow and,
after a successful upgrade, requests a non-blocking refresh from the systemd
user service `package-status-refresh.service`.

View file

@ -1,27 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
# shellcheck shell=bash
ALIASES_GREEN='\033[0;32m' # Green
ALIASES_BLUE='\033[0;34m' # Blue
ALIASES_WHITE='\033[1;37m' # White
ALIASES_NC='\033[0m' # No Color
echo "Remember aliases:" # Common interactive aliases and helpers using POSIX shell syntax.
echo -e "${ALIASES_WHITE} et en ens ee"
echo " dusort debuglog mkcd"
echo -e "${ALIASES_NC}"
echo ""
alias backup='sudo borgmatic create --verbosity 1 --files' alias venv='. ./venv/bin/activate'
alias disk-usage='printf "%s\n" "disk-usage is deprecated; use dusort instead." >&2'
alias cs='lxc list -c ns4b,user.comment:comment'
alias pc='pcmanfm . & disown'
# enable aliases for commands inside sudo
# TOOD check this is safe
alias sudo='sudo '
alias venv='source ./venv/bin/activate'
alias e='nvim' alias e='nvim'
alias er='nvim README.md' alias er='nvim README.md'
@ -31,182 +12,68 @@ alias ens='nvim NEXT_STEPS.md'
alias ea='nvim .autonote.md' alias ea='nvim .autonote.md'
alias ee='nvim .' alias ee='nvim .'
alias speak='/usr/local/src/kokora/speak'
debuglog() { debuglog() {
clear clear
: > debug.log : > debug.log
tail -f debug.log tail -f debug.log
} }
alias debug.log='printf "%s\n" "debug.log is deprecated; use debuglog instead." >&2' alias g='git status'
dusort() { gg() {
if [ "$#" -gt 1 ]; then _gg_message=${*:-update}
printf '%s\n' 'Usage: dusort [path]' >&2 if git add . && git commit -m "$_gg_message" && git push; then
return 2 unset _gg_message
fi
_dusort_path=${1:-.}
if [ ! -e "$_dusort_path" ]; then
printf 'dusort: path does not exist: %s\n' "$_dusort_path" >&2
unset _dusort_path
return 1
fi
if sudo du -h -d 2 -- "$_dusort_path" | sort -h; then
unset _dusort_path
return 0 return 0
fi fi
unset _dusort_path unset _gg_message
return 1 return 1
} }
alias g='git status' wget() {
alias gg='git add . && git commit -m "update" && git push' printf 'wget wrapper: using HSTS file %s\n' "$XDG_DATA_HOME/wget-hsts" >&2
command wget --hsts-file="$XDG_DATA_HOME/wget-hsts" "$@"
}
alias ls='LC_COLLATE=C ls --color=auto -h --group-directories-first --time-style=+"%d.%m.%Y %H:%M" -F'
alias cp='cp -irv'
alias mkdir='mkdir -vp'
alias mv='mv -iv'
alias rm='rm -dv'
alias wget='wget --hsts-file="$XDG_DATA_HOME/wget-hsts"'
alias grep='grep --colour=auto'
alias egrep='egrep --colour=auto'
alias fgrep='fgrep --colour=auto'
alias bc='bc -q -l' alias bc='bc -q -l'
alias ytdl='yt-dlp --write-info-json --write-description --no-playlist' alias ytdl='yt-dlp --write-info-json --write-description --no-playlist'
alias ytdlb='yt-dlp --write-info-json --write-description --no-playlist -a' alias ytdlb='yt-dlp --write-info-json --write-description --no-playlist -a'
alias ytdl-mp3='yt-dlp -f bestaudio --extract-audio --audio-format mp3 --embed-thumbnail --add-metadata' alias ytdl-mp3='yt-dlp -f bestaudio --extract-audio --audio-format mp3 --embed-thumbnail --add-metadata'
# Transitional aliases for destinations now managed by ~/.ssh/config. # lf gets confused by TERMINFO when running in tmux.
alias mac='printf "%s\n" "mac is deprecated; use ssh mac instead." >&2'
alias pi4='printf "%s\n" "pi4 is deprecated; use ssh pi4 instead." >&2'
alias red='printf "%s\n" "red is deprecated; use ssh red instead." >&2'
alias alice='printf "%s\n" "alice is deprecated; use ssh alice instead." >&2'
alias phthalo='printf "%s\n" "phthalo is deprecated; use ssh phthalo instead." >&2'
alias zirconia='printf "%s\n" "zirconia is deprecated; use ssh zirconia instead." >&2'
alias zirconiarc='printf "%s\n" "zirconiarc is deprecated; use ssh zirconiarc instead." >&2'
alias datapasa='printf "%s\n" "datapasa is deprecated; use ssh datapasa instead." >&2'
alias ultra='printf "%s\n" "ultra is deprecated; use ssh ultra instead." >&2'
alias singsay='printf "%s\n" "singsay is deprecated; use ssh singsay instead." >&2'
alias kermit='printf "%s\n" "kermit is deprecated; use ssh kermit instead." >&2'
alias crafty='printf "%s\n" "crafty is deprecated; use ssh crafty instead." >&2'
alias lv='printf "%s\n" "lv is deprecated; use ssh lv instead." >&2'
commands() {
awk -F '\t' '
NF == 0 { next }
NF != 3 || ($1 != "yes" && $1 != "no") || $2 == "" || $3 == "" {
printf "Invalid command reminder on line %d.\n", NR > "/dev/stderr"
invalid = 1
next
}
seen[$2]++ {
printf "Duplicate command reminder for %s on line %d.\n", $2, NR > "/dev/stderr"
invalid = 1
next
}
{
count++
reminder[count] = $1
command[count] = $2
description[count] = $3
if (length($2) > command_width) command_width = length($2)
if (length($3) > description_width) description_width = length($3)
}
END {
if (invalid) exit 1
if (length("COMMAND") > command_width) command_width = length("COMMAND")
if (length("DESCRIPTION") > description_width) description_width = length("DESCRIPTION")
printf "%-*s %-*s %s\n", command_width, "COMMAND", description_width, "DESCRIPTION", "REMINDER"
for (row = 1; row <= count; row++) {
printf "%-*s %-*s %s\n", command_width, command[row], description_width, description[row], reminder[row]
}
}
' "${XDG_CONFIG_HOME:-$HOME/.config}/profile/commands.tsv"
}
_command_reminder() {
awk -F '\t' -v width="${COLUMNS:-80}" '
NF == 0 { next }
NF != 3 || ($1 != "yes" && $1 != "no") || $2 == "" || $3 == "" {
printf "Invalid command reminder on line %d.\n", NR > "/dev/stderr"
invalid = 1
next
}
seen[$2]++ {
printf "Duplicate command reminder for %s on line %d.\n", $2, NR > "/dev/stderr"
invalid = 1
next
}
$1 == "yes" { command[++count] = $2 }
END {
if (invalid) exit 1
if (width < 40) width = 80
line = " "
for (row = 1; row <= count; row++) {
separator = (line == " " ? "" : " ")
if (length(line) + length(separator) + length(command[row]) > width) {
print line
line = " " command[row]
} else {
line = line separator command[row]
}
}
if (line != " ") print line
}
' "${XDG_CONFIG_HOME:-$HOME/.config}/profile/commands.tsv"
}
# lf gets confused by TERMINFO env var when running in tmux
# https://github.com/gokcehan/lf/issues/138#issuecomment-464477665 # https://github.com/gokcehan/lf/issues/138#issuecomment-464477665
if [ -n "$TERMINFO" ] ; then if [ -n "${TERMINFO:-}" ]; then
alias lf="TERMINFO= lf -last-dir-path=~/.cache/lf-last-dir" alias lf='TERMINFO= lf -last-dir-path="$HOME/.cache/lf-last-dir"'
else else
alias lf="lf -last-dir-path=~/.cache/lf-last-dir" alias lf='lf -last-dir-path="$HOME/.cache/lf-last-dir"'
fi fi
cdlf() { cdlf() {
local last_dir
[ -r "$HOME/.cache/lf-last-dir" ] || return 1 [ -r "$HOME/.cache/lf-last-dir" ] || return 1
IFS= read -r last_dir < "$HOME/.cache/lf-last-dir" IFS= read -r _cdlf_last_dir < "$HOME/.cache/lf-last-dir"
[ -n "$last_dir" ] || return 1 [ -n "$_cdlf_last_dir" ] || return 1
cd -- "$last_dir" || return
if cd "$_cdlf_last_dir"; then
unset _cdlf_last_dir
return 0
fi
unset _cdlf_last_dir
return 1
} }
lfcd() { lfcd() {
lf -last-dir-path="$HOME"/.cache/lf-last-dir && cdlf lf -last-dir-path="$HOME/.cache/lf-last-dir" && cdlf
}
hosts() {
if [ ! -r "$HOME/.ssh/config.d/hosts.conf" ]; then
printf '%s\n' 'SSH host configuration is unavailable.' >&2
return 1
fi
awk '
tolower($1) == "host" {
for (field = 2; field <= NF; field++) {
if ($field !~ /[*?!]/) print $field
}
}
' "$HOME/.ssh/config.d/hosts.conf"
}
update() {
echo -e "Executing \"${ALIASES_GREEN}pacman -Syu${ALIASES_NC}\" via \"${ALIASES_BLUE}archnews_wrap${ALIASES_NC}\" wrapper"
sudo ~/.src/archnews/archnews_wrap -Syu || return
systemctl --user start --no-block package-status-refresh.service >/dev/null 2>&1 || true
} }
mkcd() { mkcd() {
mkdir "$1" || return if [ "$#" -ne 1 ]; then
printf '%s\n' 'Usage: mkcd <directory>' >&2
return 2
fi
mkdir -p "$1" || return
cd "$1" || return cd "$1" || return
} }

68
commands.sh Normal file
View file

@ -0,0 +1,68 @@
#!/bin/sh
# Display and validate the curated command catalogue.
commands() {
awk -F '\t' '
NF == 0 { next }
NF != 3 || ($1 != "yes" && $1 != "no") || $2 == "" || $3 == "" {
printf "Invalid command reminder on line %d.\n", NR > "/dev/stderr"
invalid = 1
next
}
seen[$2]++ {
printf "Duplicate command reminder for %s on line %d.\n", $2, NR > "/dev/stderr"
invalid = 1
next
}
{
count++
reminder[count] = $1
command[count] = $2
description[count] = $3
if (length($2) > command_width) command_width = length($2)
if (length($3) > description_width) description_width = length($3)
}
END {
if (invalid) exit 1
if (length("COMMAND") > command_width) command_width = length("COMMAND")
if (length("DESCRIPTION") > description_width) description_width = length("DESCRIPTION")
printf "%-*s %-*s %s\n", command_width, "COMMAND", description_width, "DESCRIPTION", "REMINDER"
for (row = 1; row <= count; row++) {
printf "%-*s %-*s %s\n", command_width, command[row], description_width, description[row], reminder[row]
}
}
' "${XDG_CONFIG_HOME:-$HOME/.config}/profile/commands.tsv"
}
_command_reminder() {
awk -F '\t' -v width="${COLUMNS:-80}" '
NF == 0 { next }
NF != 3 || ($1 != "yes" && $1 != "no") || $2 == "" || $3 == "" {
printf "Invalid command reminder on line %d.\n", NR > "/dev/stderr"
invalid = 1
next
}
seen[$2]++ {
printf "Duplicate command reminder for %s on line %d.\n", $2, NR > "/dev/stderr"
invalid = 1
next
}
$1 == "yes" { command[++count] = $2 }
END {
if (invalid) exit 1
if (width < 40) width = 80
line = " "
for (row = 1; row <= count; row++) {
separator = (line == " " ? "" : " ")
if (length(line) + length(separator) + length(command[row]) > width) {
print line
line = " " command[row]
} else {
line = line separator command[row]
}
}
if (line != " ") print line
}
' "${XDG_CONFIG_HOME:-$HOME/.config}/profile/commands.tsv"
}

View file

@ -7,7 +7,6 @@ yes qv4l2 Video4Linux control panel
no cheat command-line cheatsheets no cheat command-line cheatsheets
no vimv batch rename files in Vim no vimv batch rename files in Vim
yes rclone sync files with cloud storage yes rclone sync files with cloud storage
yes mid3v2 edit ID3 audio metadata
yes zathura document viewer yes zathura document viewer
yes tesseract optical character recognition yes tesseract optical character recognition
yes remmina remote desktop client yes remmina remote desktop client

1 yes nap snippet manager
7 no cheat command-line cheatsheets
8 no vimv batch rename files in Vim
9 yes rclone sync files with cloud storage
yes mid3v2 edit ID3 audio metadata
10 yes zathura document viewer
11 yes tesseract optical character recognition
12 yes remmina remote desktop client

16
hosts.sh Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
hosts() {
if [ ! -r "$HOME/.ssh/config.d/hosts.conf" ]; then
printf '%s\n' 'SSH host configuration is unavailable.' >&2
return 1
fi
awk '
tolower($1) == "host" {
for (field = 2; field <= NF; field++) {
if ($field !~ /[*?!]/) print $field
}
}
' "$HOME/.ssh/config.d/hosts.conf"
}

20
interactive.sh Normal file
View file

@ -0,0 +1,20 @@
#!/bin/sh
# Shared interactive setup for POSIX-compatible shells.
_profile_interactive_dir=${XDG_CONFIG_HOME:-$HOME/.config}/profile
. "$_profile_interactive_dir/aliases.sh"
. "$_profile_interactive_dir/commands.sh"
. "$_profile_interactive_dir/hosts.sh"
if [ -r /etc/arch-release ]; then
. "$_profile_interactive_dir/platform/arch.sh"
fi
printf '%s\n' 'Remember aliases:'
printf '\033[1;37m%s\n' ' et en ens ee'
printf '%s\n' ' dusort debuglog mkcd'
printf '\033[0m\n\n'
unset _profile_interactive_dir

56
platform/arch.sh Normal file
View file

@ -0,0 +1,56 @@
#!/bin/sh
# Interactive commands specific to this Arch Linux workstation.
_ARCH_GREEN='\033[0;32m'
_ARCH_BLUE='\033[0;34m'
_ARCH_NC='\033[0m'
alias backup='sudo borgmatic create --verbosity 1 --files'
alias cs='lxc list -c ns4b,user.comment:comment'
pc() {
nohup pcmanfm . >/dev/null 2>&1 &
}
# Expand aliases for commands following sudo.
alias sudo='sudo '
alias speak='/usr/local/src/kokora/speak'
alias ls='LC_COLLATE=C ls --color=auto -h --group-directories-first --time-style=+"%d.%m.%Y %H:%M" -F'
alias cp='cp -irv'
alias mkdir='mkdir -vp'
alias mv='mv -iv'
alias rm='rm -dv'
alias grep='grep --colour=auto'
dusort() {
if [ "$#" -gt 1 ]; then
printf '%s\n' 'Usage: dusort [path]' >&2
return 2
fi
_dusort_path=${1:-.}
if [ ! -e "$_dusort_path" ]; then
printf 'dusort: path does not exist: %s\n' "$_dusort_path" >&2
unset _dusort_path
return 1
fi
if sudo du -h -d 2 -- "$_dusort_path" | sort -h; then
unset _dusort_path
return 0
fi
unset _dusort_path
return 1
}
update() {
printf 'Executing "%bpacman -Syu%b" via "%barchnews_wrap%b" wrapper\n' \
"$_ARCH_GREEN" "$_ARCH_NC" "$_ARCH_BLUE" "$_ARCH_NC"
sudo "$HOME/.src/archnews/archnews_wrap" -Syu || return
systemctl --user start --no-block package-status-refresh.service >/dev/null 2>&1 || true
}