File: sourcery.sh
   1 #!/bin/sh
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright (c) 2026 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the "Software"), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 # sourcery
  27 #
  28 #
  29 # Add `magic` live-shell shortcuts/commands by sourcing this script.
  30 #
  31 # This is a collection of arguably useful shell functions and shortcuts:
  32 # some of these extra commands can be real time/effort savers, ideally
  33 # letting you concentrate on getting things done.
  34 #
  35 # Some of these commands depend on my other scripts from the `pac-tools`,
  36 # others either rely on widely-preinstalled command-line apps, or ones
  37 # which are available on most of the major command-line `package` managers.
  38 #
  39 # To use this script, you're supposed to `source` it, so its definitions
  40 # stay for your whole shell session: for that, you can run `source sourcery`
  41 # or `. sourcery` (no quotes either way) directly or at shell startup.
  42 #
  43 # Almost all commands defined in this script work with `bash`, `zsh`, and
  44 # even `dash`, which is debian linux's default non-interactive shell. Many
  45 # of its commands even seem to work on busybox's shell.
  46 
  47 
  48 case "$1" in
  49     -h|--h|-help|--help)
  50         awk '
  51             /^case / { exit }
  52             /^# +sourcery$/, /^$/ { gsub(/^# ?/, ""); print }
  53         ' "$0"
  54     ;;
  55 esac
  56 
  57 
  58 # dash doesn't support regex-matching syntax, forcing to use case statements
  59 case "$0" in
  60     -bash|-dash|-sh|bash|dash|sh|/bin/sh)
  61         # script is being sourced with bash, dash, or ash, which is good
  62         :
  63     ;;
  64 
  65     *)
  66         case "$ZSH_EVAL_CONTEXT" in
  67             *:file)
  68                 # script is being sourced with zsh, which is good
  69                 :
  70             ;;
  71 
  72             *)
  73                 # script is being run normally, which is a waste of time
  74 printf "\033[7mDon't run this script directly: instead source it\033[0m\n"
  75 printf "\033[7mby running '. sourcery' (without the single quotes).\033[0m\n"
  76 printf "\n"
  77 printf "\033[7mBefore doing that, you may want to see the help,\033[0m\n"
  78 printf "\033[7mby running 'sourcery -h' (without the single quotes).\033[0m\n"
  79                 # exiting during shell-startup may deny shell access, even if
  80                 # the script is being run, instead of being sourced directly
  81             ;;
  82         esac
  83     ;;
  84 esac
  85 
  86 
  87 # stty -ixon
  88 
  89 # use a bash-specific simple prompt, showing the current folder in the tab's
  90 # title, instead of before each command
  91 # case "$0" in
  92 #     -bash) export PS1="\[\e[0m\e]0;\w\a\]\$ " ;;
  93 # esac
  94 
  95 # Time And Memory format-string for command `/usr/bin/time -f`
  96 # export TAM='real %e    user %U    sys %S    mem %M    exit %x'
  97 
  98 # use a single-line output for keyword `time`
  99 # export TIMEFORMAT='real %2R    user %2U    sys %2S'
 100 
 101 # use a single-line output for keyword `time`
 102 # export TIMEFORMAT='real %2lR    user %2lU    sys %2lS'
 103 
 104 # prevent `less` from saving commands
 105 # export LESSHISTFILE="-"
 106 
 107 # command-history settings; prevent the shell from saving commands
 108 # export HISTTIMEFORMAT="%T  "
 109 # export HISTFILE="/dev/null"
 110 # unset HISTFILE
 111 
 112 # tell `go` to mind its own business
 113 # export GOTELEMETRY=off
 114 
 115 
 116 command -v c > /dev/null || alias c='cat'
 117 command -v e > /dev/null || alias e='echo'
 118 command -v man > /dev/null && alias h='man' # H for help (manual)
 119 command -v r > /dev/null || alias r='reset'
 120 
 121 # alias b='bar' # `bar` is defined later in this script
 122 # alias h='naman' # `naman` is defined later in this script
 123 # alias w='wat' # `wat` is defined later in this script
 124 
 125 # quick single-digit shortcuts to my `side-by-side` tools
 126 alias 0='sbs 0'
 127 alias 1='bsbs 1'
 128 alias 2='bsbs 2'
 129 alias 3='bsbs 3'
 130 alias 4='bsbs 4'
 131 alias 5='bsbs 5'
 132 alias 6='bsbs 6'
 133 alias 7='bsbs 7'
 134 alias 8='bsbs 8'
 135 alias 9='bsbs 9'
 136 
 137 alias goto='cf' # `cf` (Change Folder) is defined later in this script
 138 
 139 # alias goto='gotobf' # `gotobf` is defined later in this script
 140 
 141 # Less with Header n runs `less` with line numbers, ANSI styles, without
 142 # line-wraps, always showing the first n lines as a sticky-header on top
 143 alias lh1='less --header=1 -MKNiCRS'
 144 alias lh2='less --header=2 -MKNiCRS'
 145 alias lh3='less --header=3 -MKNiCRS'
 146 alias lh4='less --header=4 -MKNiCRS'
 147 alias lh5='less --header=5 -MKNiCRS'
 148 alias lh6='less --header=6 -MKNiCRS'
 149 alias lh7='less --header=7 -MKNiCRS'
 150 alias lh8='less --header=8 -MKNiCRS'
 151 alias lh9='less --header=9 -MKNiCRS'
 152 
 153 # View with Header n runs `less` without line numbers, ANSI styles, without
 154 # line-wraps, always showing the first n lines as a sticky-header on top
 155 alias vh1='less --header=1 -MKiCRS'
 156 alias vh2='less --header=2 -MKiCRS'
 157 alias vh3='less --header=3 -MKiCRS'
 158 alias vh4='less --header=4 -MKiCRS'
 159 alias vh5='less --header=5 -MKiCRS'
 160 alias vh6='less --header=6 -MKiCRS'
 161 alias vh7='less --header=7 -MKiCRS'
 162 alias vh8='less --header=8 -MKiCRS'
 163 alias vh9='less --header=9 -MKiCRS'
 164 
 165 
 166 # Awk Begin Print each expression given, without reading any input
 167 command -v abp > /dev/null || abp() {
 168     [ $# -eq 0 ] || awk "$(printf "BEGIN { print(%s) }\n" "$@")"
 169 }
 170 
 171 # Awk Print each expression given
 172 command -v ap > /dev/null || ap() {
 173     [ $# -eq 0 ] || awk "$(printf "BEGIN { print(%s) }\n" "$@")"
 174 }
 175 
 176 # APK UPdate/grade
 177 apkup() { apk update && sudo apk upgrade "$@"; }
 178 
 179 # APT UPdate/grade
 180 aptup() { sudo apt update && sudo apt upgrade "$@"; sudo -k; }
 181 
 182 # emit each argument given as its own line of output
 183 command -v args > /dev/null || args() { [ $# -eq 0 ] || printf "%s\n" "$@"; }
 184 
 185 # emit a styled BAR, with an optional `echo`-like message
 186 command -v bar > /dev/null || bar() { printf "\033[7m%-80s\033[0m\n" "$*"; }
 187 
 188 # CAlculate Nice, using my tools `ca` and `nn`
 189 can() { ca "$@" | nn; }
 190 
 191 # Change Folder using the path given, or using my `bf` tool to pick a folder
 192 command -v bf > /dev/null && cf() {
 193     local where
 194     local code
 195 
 196     if [ $# -gt 1 ]; then
 197         printf "multiple arguments/folders not supported\n" >&2
 198         return 1
 199     fi
 200 
 201     if [ $# -eq 0 ]; then
 202         where="$(bf .)"
 203         code=$?
 204     else
 205         where="$1"
 206         code=0
 207     fi
 208 
 209     if [ "${code}" -ne 0 ]; then
 210         return "${code}"
 211     fi
 212 
 213     if [ ! -d "${where}" ]; then
 214         where="$(dirname "${where}")"
 215     fi
 216 
 217     cd "${where}"
 218     return $?
 219 }
 220 
 221 # CLear Screen
 222 command -v cls > /dev/null || alias cls='reset'
 223 
 224 # Colored RipGrep
 225 command -v crg > /dev/null || alias crg='rg --color=always'
 226 
 227 # CURL Silent avoids the progress bar, but still shows errors
 228 command -v curl > /dev/null && alias curls='curl --silent --show-error'
 229 
 230 # DEDUPlicate lines, emitting each unique line only the first time
 231 command -v dedup > /dev/null || dedup() {
 232     awk '
 233         BEGIN { for (i = 1; i < ARGC; i++) if (s[ARGV[i]]++) delete ARGV[i] }
 234         !c[$0]++
 235     ' "$@"
 236 }
 237 
 238 # dictionary-DEFINE the word given, using the local-first remote-second `dict`
 239 command -v define > /dev/null || define() {
 240     local word
 241     local gap=0
 242     local options='-MKiCRS'
 243 
 244     if [ $# -eq 0 ]; then
 245         printf "no words given\n" >&2
 246         return 1
 247     fi
 248 
 249     if [ $# -eq 1 ]; then
 250         options='--header=1 -MKiCRS'
 251     fi
 252 
 253     for word in "$@"; do
 254         [ "${gap}" -gt 0 ] && printf "\n"
 255         gap=1
 256         printf "%s\n" "${word}"
 257         dict "${word}"
 258     done | { less ${options} 2> /dev/null || less -RIMS 2> /dev/null || cat; }
 259 }
 260 
 261 # DIFF-Y compares/shows 2 files side-by-side
 262 command -v diffy > /dev/null || alias diffy='diff -y'
 263 
 264 # DIVide 2 numbers 2 ways, also showing the complement
 265 command -v div > /dev/null || div() {
 266     awk -v x="${1:-1}" -v y="${2:-1}" '
 267         BEGIN {
 268             gsub(/_/, "", x)
 269             gsub(/_/, "", y)
 270 
 271             failed = 0
 272             if (x ~ /^[+-]? *$/) {
 273                 printf("invalid number \"%s\"\n", x) > "/dev/stderr"
 274                 failed = 1
 275             }
 276             if (y ~ /^[+-]? *$/) {
 277                 printf("invalid number \"%s\"\n", y) > "/dev/stderr"
 278                 failed = 1
 279             }
 280             if ((x == 0) || (y == 0)) {
 281                 print "can'"'"'t divide by zero" > "/dev/stderr"
 282                 failed = 1
 283             }
 284             if (failed) exit 1
 285 
 286             a = x / y
 287             b = y / x
 288             c = (a < b) ? 1 - a : 1 - b
 289             printf "%f\n", (a < b) ? a : b
 290             printf "%f\n", (a < b) ? b : a
 291             printf "%f\n", c
 292             exit
 293         }
 294     '
 295 }
 296 
 297 # emit a line with a repeating dot-like symbol in it
 298 dots() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -·-g'; }
 299 
 300 # show the current Date and Time on separate lines
 301 dt() { printf "%s\n%s\n" "$(date +'%a %b %d')" "$(date +%T)"; }
 302 
 303 # EDit RUN shell commands, using an interactive editor
 304 edrun() {
 305     local leak='{ printf("\x1b[7m%s\x1b[0m\n", $0) > "/dev/stderr"; print }'
 306     # dash doesn't support the process-sub syntax
 307     # . <( micro -readonly true -filetype shell | awk "${leak}" )
 308     micro -readonly true -filetype shell | awk "${leak}" | . /dev/fd/0
 309 }
 310 
 311 # Extended-mode INteractive GREP runs `ugrep` in live/TUI mode
 312 command -v ugrep > /dev/null && alias eingrep='ugrep -E -Q'
 313 
 314 # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings
 315 command -v ff > /dev/null || ff() {
 316     local keys='ctrl-a:select-all,ctrl-space:toggle,F10:abort,F12:abort'
 317     fzf --reverse -m --bind "${keys}" "$@"
 318 }
 319 
 320 # convert FeeT into meters
 321 ft() {
 322     echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' \
 323     | awk '/./ { printf "%.2f\n", 0.3048 * $0 }'
 324 }
 325 
 326 # convert a mix of FeeT and INches into meters
 327 ftin() {
 328     local ft="${1:-0}"
 329     ft="$(echo "${ft}" | sed 's-_--g')"
 330     local in="${2:-0}"
 331     in="$(echo "${in}" | sed 's-_--g')"
 332     awk "BEGIN { print 0.3048 * ${ft} + 0.0254 * ${in}; exit }"
 333 }
 334 
 335 # convert GALlons into liters
 336 gal() {
 337     echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' \
 338     | awk '/./ { printf "%.2f\n", 3.785411784 * $0 }'
 339 }
 340 
 341 # GO Build Stripped: a common use-case for the go compiler
 342 alias gobs='go build -ldflags "-s -w" -trimpath'
 343 
 344 # GO DEPendencieS: show all dependencies in a go project
 345 alias godeps="go list -f '{{ join .Deps \"\\n\" }}"
 346 
 347 # GO IMPortS: show all imports in a go project
 348 alias goimps="go list -f '{{ join .Imports \"\\n\" }}'"
 349 
 350 # go to the folder picked using an interactive TUI; uses my tool `bf`
 351 command -v bf > /dev/null && gotobf() {
 352     local where
 353     local code
 354     where="$(bf "${1:-.}")"
 355     code=$?
 356     [ "${code}" -ne 0 ] && return "${code}"
 357 
 358     where="$(realpath "${where}")"
 359     if [ ! -d "${where}" ]; then
 360         where="$(dirname "${where}")"
 361     fi
 362     cd "${where}" || return
 363 }
 364 
 365 # GO TO FuZzy Finder, goes to the folder picked using an interactive TUI
 366 command -v fzf > /dev/null && gotofzf() {
 367     local where
 368     local code
 369     local msg='Pick Folder to go into'
 370     local keys='ctrl-a:select-all,ctrl-space:toggle,F10:abort,F12:abort'
 371     local picker="fzf --reverse --header-first --bind ${keys}"
 372 
 373     where="$(find "${1:-.}" -type f | ${picker} --header="${msg}")"
 374     code=$?
 375     [ "${code}" -ne 0 ] && return "${code}"
 376 
 377     where="$(realpath "${where}")"
 378     if [ ! -d "${where}" ]; then
 379         where="$(dirname "${where}")"
 380     fi
 381 
 382     cd "${where}" || return
 383 }
 384 
 385 # History Fuzzy-finder runs the command picked from the shell history
 386 command -v fzf > /dev/null && hf() {
 387     local keys='ctrl-a:select-all,ctrl-space:toggle,F10:abort,F12:abort'
 388     local options='--reverse --height 80% --tmux center,80% --header-first'
 389     local picker="fzf ${options} --bind ${keys}"
 390 
 391     eval "$(
 392         history \
 393         | ${picker} --header='Pick previous shell command to re-run' \
 394         | awk -v htf="${HISTTIMEFORMAT}" '
 395             {
 396                 $1 = ""
 397                 if (htf ~ /[^ ]/) $2 = ""
 398                 gsub(/^ */, "")
 399                 printf("\x1b[7m%s\x1b[0m\n", $0) > "/dev/stderr"
 400                 print
 401             }
 402         '
 403     )"
 404 }
 405 
 406 # convert seconds into a colon-separated Hours-Minutes-Seconds triple
 407 hms() {
 408     echo "${@:-0}" \
 409     | sed -E 's-_--g; s- +-\n-g' \
 410     | awk '
 411         /./ {
 412             x = $0
 413             h = (x - x % 3600) / 3600
 414             m = (x % 3600) / 60
 415             s = x % 60
 416             printf "%02d:%02d:%05.2f\n", h, m, s
 417         }
 418     '
 419 }
 420 
 421 # find all hyperlinks inside HREF attributes in the input text
 422 href() {
 423     local arg
 424     for arg in "${@:--}"; do
 425         grep -i -E -o 'href="[^"]+"' "${arg}"
 426     done | awk '{ gsub(/^href="|"\r?$/, ""); print }'
 427 }
 428 
 429 # ignore the command given; useful to turn pipe-steps into passthrus
 430 ignore() { cat -; }
 431 
 432 # INSTall APT packages
 433 instapt() { sudo apt install "$@"; sudo -k; }
 434 
 435 # show a `dad` JOKE from the web, sometimes even a very funny one
 436 command -v joke > /dev/null || joke() {
 437     curl --silent --show-error https://icanhazdadjoke.com \
 438     | fold -s \
 439     | awk '{ gsub(/ *\r?$/, ""); print }'
 440 }
 441 
 442 # Less, showing line-numbers by default
 443 command -v l > /dev/null || l() {
 444     # busybox's `less` doesn't support options `-C` and `-K`
 445     if [ -L /usr/bin/less ]; then
 446         less -NRIMS "$@"
 447     else
 448         less -MKNiCRS "$@" 2> /dev/null
 449     fi
 450 }
 451 
 452 # Less Header-mode, showing line-numbers and (always) showing the first line
 453 command -v lh > /dev/null || lh() {
 454     # busybox's `less` doesn't support options `-C` and `-K`
 455     if [ -L /usr/bin/less ]; then
 456         less -NRIMS "$@"
 457     else
 458         less -MKNiCRS --header=1 "$@" 2> /dev/null
 459     fi
 460 }
 461 
 462 # convert pounds (LB) into kilograms
 463 lb() {
 464     echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' \
 465     | awk '/./ { printf "%.2f\n", 0.45359237 * $0 }'
 466 }
 467 
 468 # Live Extended-mode Grep runs `ugrep` in live/TUI mode
 469 command -v ugrep > /dev/null && alias leg='ugrep -E -Q'
 470 
 471 # MARK all web/hyperLINKS (both https:// and http://) across text lines
 472 # as usable/clickable items on modern terminals
 473 marklinks() {
 474     local re='https?://[A-Za-z0-9+_.:%-]+(/[A-Za-z0-9+_.%/,#?&=-]*)*'
 475     awk 1 "$@" | sed -E 's-('"${re}"')-\x1b]8;;\1\x1b\\\1\x1b]8;;\x1b\\-g'
 476 }
 477 
 478 # merge stderr into stdout, which is useful for piped commands
 479 command -v merrge > /dev/null || merrge() { [ $# -eq 0 ] || "$@" 2>&1; }
 480 
 481 # convert MIles into kilometers
 482 mi() {
 483     echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' \
 484     | awk '/./ { printf "%.2f\n", 1.609344 * $0 }'
 485 }
 486 
 487 # Make In Folder, also showing time and max memory used
 488 command -v mif > /dev/null || mif() {
 489     local f='real %e    user %U    sys %S    mem %M    exit %x'
 490     local folder="${1:-.}"
 491     [ $# -gt 0 ] && shift
 492     # busybox `env` doesn't support option `-C`
 493     ( cd "${folder}" && /usr/bin/time -f "$f" make "$@"; )
 494 }
 495 
 496 # NArrow MANual, keeps `man` narrow, even if the window/tab is wide when run
 497 command -v naman > /dev/null || naman() {
 498     local w
 499     w="$(tput -T xterm cols 2> /dev/null || echo 168)"
 500     w="$((w / 2 - 4))"
 501     [ "${w}" -lt 80 ] && w=80
 502     MANWIDTH="${w}" man "$@"
 503 }
 504 
 505 # NIL output: run command, discarding its output, or just read/discard stdin
 506 command -v nil > /dev/null || nil() {
 507     if [ $# -gt 0 ]; then
 508         "$@" > /dev/null
 509     elif [ -p /dev/stdin ]; then
 510         cat - > /dev/null
 511     fi
 512 }
 513 
 514 # Nice Processes shows/lists all current processes shown by `ps`
 515 np() {
 516     local res
 517     local code
 518     res="$(ps "${@:-aux}")"
 519     code=$?
 520     [ "${code}" -ne 0 ] && return "${code}"
 521 
 522     echo "${res}" | awk '
 523         BEGIN {
 524             d = strftime("%a %b %d")
 525             t = strftime("%H:%M:%S")
 526             fmt = "\x1b[38;2;128;128;128m\x1b[7m%30s%s  %s%30s\x1b[0m\n\n"
 527             zrepl = "\x1b[38;2;135;135;175m&\x1b[0m"
 528             printf fmt, "", d, t, ""
 529         }
 530 
 531         (NR - 1) % 5 == 1 { print "" }
 532 
 533         /^root / {
 534             gsub(/^/, "\x1b[38;2;52;101;164m")
 535             gsub(/ +/, "&\x1b[0m\x1b[38;2;52;101;164m")
 536             gsub(/$/, "\x1b[0m")
 537         }
 538 
 539         {
 540             gsub(/ \? /, zrepl)
 541             gsub(/0\.0/, zrepl)
 542             gsub(/0:00/, zrepl)
 543             printf "%3d  %s\n", NR - 1, $0
 544         }
 545     '
 546 }
 547 
 548 # Nice Size(s), using my tool `nn`
 549 command -v ns > /dev/null || ns() { wc -c "$@" | nn; }
 550 
 551 # (Nice) What Are These (?) shows what the names given to it are/do, coloring
 552 # the syntax of shell functions using `bat`
 553 command -v bat > /dev/null && nwat() {
 554     local arg
 555     local gap=0
 556 
 557     if [ $# -eq 0 ]; then
 558         echo "$0"
 559         return 0
 560     fi
 561 
 562     for arg in "$@"; do
 563         [ "${gap}" -gt 0 ] && printf "\n"
 564         gap=1
 565         printf "\033[7m%-80s\033[0m\n" "${arg}"
 566 
 567         while alias "${arg}" > /dev/null 2> /dev/null; do
 568             arg="$(alias "${arg}" | sed -E "s-^[^=]+=['\"](.+)['\"]\$-\\1-")"
 569         done
 570 
 571         if echo "${arg}" | grep -q ' '; then
 572             printf "%s\n" "${arg}"
 573             continue
 574         fi
 575 
 576         # busybox `sh` doesn't support `declare`
 577 
 578         if type -f "${arg}" 2> /dev/null; then
 579             continue
 580         fi
 581 
 582         if type "${arg}" > /dev/null 2> /dev/null; then
 583             type "${arg}" | awk '
 584                 { l[NR] = $0 }
 585                 END { for (i = (NR == 1) ? 1 : 2; i <= NR; i++) print l[i] }
 586             ' \
 587             | bat -l sh \
 588                 --style=plain --theme='Monokai Extended Light' \
 589                 --wrap=never --color=always
 590             continue
 591         fi
 592 
 593         if which "${arg}" > /dev/null 2> /dev/null; then
 594             which "${arg}"
 595             continue
 596         fi
 597     done
 598 }
 599 
 600 # PasteBoard COPY, (almsot) like on macos
 601 command -v pbcopy > /dev/null || alias pbcopy='wl-copy'
 602 
 603 # PasteBoard PASTE, (almost) like on macos
 604 command -v pbpaste > /dev/null || alias pbpaste='wl-paste'
 605 
 606 # Podman Container List
 607 command -v pcl > /dev/null || alias pcl='podman container list'
 608 
 609 # Podman Image List
 610 command -v pil > /dev/null || alias pil='podman image list'
 611 
 612 # Podman OFF
 613 command -v poff > /dev/null || alias poff='podman container kill'
 614 
 615 # POdman OFf
 616 command -v poof > /dev/null || alias poof='podman container kill'
 617 
 618 # Python Print each expression given
 619 command -v pp > /dev/null || pp() {
 620     [ $# -eq 0 ] || python -c "$(printf "print(%s)\n" "$@")"
 621 }
 622 
 623 # QUIET runs the command given, discarding its standard error
 624 command -v quiet > /dev/null || quiet() { [ $# -eq 0 ] || "$@" 2> /dev/null; }
 625 
 626 # keep only lines between the 2 line numbers given, inclusively
 627 rangelines() {
 628     { [ $# -eq 2 ] || [ $# -eq 3 ]; } && [ "${1}" -le "${2}" ] && {
 629         tail -n +"${1}" "${3:--}" | head -n $(("${2}" - "${1}" + 1))
 630     }
 631 }
 632 
 633 # RANdom MANual page
 634 ranman() {
 635     find "/usr/share/man/man${1:-1}" -type f \
 636     | shuf -n 1 \
 637     | xargs basename \
 638     | sed 's-\.gz$--' \
 639     | xargs man
 640 }
 641 
 642 # RANdomly PICK n lines, or 1 line by default
 643 ranpick() {
 644     local n="${1:-1}"
 645     [ $# -gt 0 ] && shift
 646     shuf -n "${n}" "$@"
 647 }
 648 
 649 # Read-Only Micro (text editor)
 650 command -v rom > /dev/null || alias rom='micro -readonly true'
 651 
 652 # Read-Only Nano (text editor)
 653 command -v ron > /dev/null || alias ron='nano --view'
 654 
 655 # Read-Only Top
 656 command -v htop > /dev/null && alias rot='htop --readonly'
 657 
 658 # Run In Folder
 659 command -v rif > /dev/null || rif() {
 660     local folder="${1:-.}"
 661     [ $# -gt 0 ] && shift
 662     # busybox `env` doesn't support option `-C`
 663     [ $# -eq 0 ] || ( cd "${folder}" && "$@"; )
 664 }
 665 
 666 # emit a line with a RULER-like pattern, using an optional count given
 667 command -v ruler > /dev/null || ruler() {
 668     [ "${1:-80}" -gt 0 ] && {
 669         printf "%${1:-80}s\n" "" \
 670         | sed -E 's- {10}-····╵····│-g; s- {5}-····╵-1; s- -·-g'
 671     }
 672 }
 673 
 674 # SystemCTL
 675 command -v sctl > /dev/null || alias sctl='systemctl'
 676 
 677 # Silent CURL spares you the progress bar, but still tells you about errors
 678 command -v curl > /dev/null && alias scurl='curl --silent --show-error'
 679 
 680 # emit a line with the number of spaces given in it
 681 spaces() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" ""; }
 682 
 683 # turn SUDo privileges OFF right away: arguments also cause `sudo` to run with
 684 # what's given, before relinquishing existing privileges
 685 command -v sudoff > /dev/null || sudoff() {
 686     local code=0
 687     if [ $# -gt 0 ]; then
 688         sudo "$@"
 689         code=$?
 690     fi
 691     sudo -k
 692     return "${code}"
 693 }
 694 
 695 # Time And Memory (and exit) runs the command given, and shows info on stderr
 696 command -v tam > /dev/null || tam() {
 697     /usr/bin/time -f 'real %e    user %U    sys %S    mem %M    exit %x' "$@"
 698 }
 699 
 700 # go UP n folders, or go up 1 folder by default
 701 up() {
 702     if [ "${1:-1}" -le 0 ]; then
 703         cd .
 704     else
 705         cd "$(printf "%${1:-1}s" "" | sed 's- -../-g')" || return $?
 706     fi
 707 }
 708 
 709 # UNIQUE deduplicate lines, emitting each unique line only the first time
 710 command -v unique > /dev/null || unique() {
 711     awk '
 712         BEGIN { for (i = 1; i < ARGC; i++) if (s[ARGV[i]]++) delete ARGV[i] }
 713         !c[$0]++
 714     ' "$@"
 715 }
 716 
 717 # View text, using `less`
 718 command -v v > /dev/null || v() {
 719     # busybox's `less` doesn't support options `-C` and `-K`
 720     if [ -L /usr/bin/less ]; then
 721         less -RIMS "$@"
 722     else
 723         less -MKiCRS "$@" 2> /dev/null
 724     fi
 725 }
 726 
 727 # View in Header-mode using `less`, always showing the first line
 728 command -v vh > /dev/null || vh() {
 729     # busybox's `less` doesn't support options `-C` and `-K`
 730     if [ -L /usr/bin/less ]; then
 731         less -RIMS "$@"
 732     else
 733         less -MKiCRS --header=1 "$@" 2> /dev/null
 734     fi
 735 }
 736 
 737 # check shell scripts for common gotchas, avoiding complaints about using
 738 # the `local` keyword, which is widely supported in practice
 739 command -v vetshell > /dev/null || alias vetshell='shellcheck -e 3043'
 740 
 741 # What Are These (?) shows what the names given to it are/do
 742 wat() {
 743     local arg
 744     local gap=0
 745 
 746     if [ $# -eq 0 ]; then
 747         echo "$0"
 748         return 0
 749     fi
 750 
 751     for arg in "$@"; do
 752         [ "${gap}" -gt 0 ] && printf "\n"
 753         gap=1
 754         printf "\033[7m%-80s\033[0m\n" "${arg}"
 755 
 756         while alias "${arg}" > /dev/null 2> /dev/null; do
 757             arg="$(alias "${arg}" | sed -E "s-^[^=]+=['\"](.+)['\"]\$-\\1-")"
 758         done
 759 
 760         if echo "${arg}" | grep -q ' '; then
 761             printf "%s\n" "${arg}"
 762             continue
 763         fi
 764 
 765         # busybox `sh` doesn't support `declare`
 766 
 767         if type -f "${arg}" 2> /dev/null; then
 768             continue
 769         fi
 770 
 771         if type "${arg}" > /dev/null 2> /dev/null; then
 772             type "${arg}" | awk '
 773                 { l[NR] = $0 }
 774                 END { for (i = (NR == 1) ? 1 : 2; i <= NR; i++) print l[i] }
 775             '
 776             continue
 777         fi
 778 
 779         if which "${arg}" > /dev/null 2> /dev/null; then
 780             which "${arg}"
 781             continue
 782         fi
 783     done
 784 }
 785 
 786 # find all WEB/hyperLINKS (both https:// and http://) across text lines
 787 weblinks() {
 788     local arg
 789     local re='https?://[A-Za-z0-9+_.:%-]+(/[A-Za-z0-9+_.%/,#?&=-]*)*'
 790     for arg in "${@:--}"; do
 791         grep -i -E -o "${re}" "${arg}"
 792     done
 793 }
 794 
 795 # WGET to standard Output, instead of saving to a file
 796 alias wgeto='wget -O -'
 797 
 798 # Xargs 0 runs `xargs` in null-delimited-arguments mode
 799 command -v x0 > /dev/null || alias x0='xargs -0'
 800 
 801 # Xargs Lines uses whole lines from the standard input as the extra arguments
 802 command -v xl > /dev/null || xl() {
 803     awk -v ORS='\000' '{ gsub(/\r$/, ""); print }' \
 804     | xargs -0 "$@"
 805 }
 806 
 807 
 808 ##############################################################################
 809 #                            Sound-related Extras                            #
 810 ##############################################################################
 811 
 812 
 813 # if my `playwave` tool isn't available, some sound-playing commands defined
 814 # later need a substitute
 815 command -v playwave > /dev/null || playwave() {
 816     mpv --really-quiet "${@:--}"
 817 }
 818 
 819 if command -v sboard > /dev/null; then
 820 
 821 # play a repeating and annoying high-pitched beep sound a few times a second,
 822 # lasting the number of seconds given, or for 1 second by default; uses my
 823 # tools `sboard` and `playwave`
 824     beeps() { sboard -o beeps "${1:-1}" "${2:-1}" | playwave; }
 825 
 826 # play a repeating synthetic-bell-like sound lasting the number of seconds
 827 # given, or for 1 second by default; uses my tools `sboard` and `playwave`
 828     bell() { sboard -o bell "${1:-1}" "${2:-1}" | playwave; }
 829 
 830 # play a busy-phone-line sound lasting the number of seconds given, or for 1
 831 # second by default; uses my tools `sboard` and `playwave`
 832     busy() { sboard -o busy "${1:-1}" "${2:-1}" | playwave; }
 833 
 834 # play a heartbeat-like sound lasting the number of seconds given, or for 1
 835 # second by default; uses my tools `sboard` and `playwave`
 836     heartbeat() { sboard -o heartbeat "${1:-1}" "${2:-1}" | playwave; }
 837 
 838 # play a stereotypical once-a-second laser sound for the number of seconds
 839 # given, or for 1 second (once) by default; uses my tools `sboard` and
 840 # `playwave`
 841     laser() { sboard -o laser "${1:-1}" "${2:-1}" | playwave; }
 842 
 843 # play a white-noise sound lasting the number of seconds given, or for 1
 844 # second by default; uses my tools `sboard` and `playwave`
 845     noise() { sboard -o noise "${1:-1}" "${2:-1}" | playwave; }
 846 
 847 # play a ready-phone-line sound lasting the number of seconds given, or for 1
 848 # second by default; uses my tools `sboard` and `playwave`
 849     ready() { sboard -o ready "${1:-1}" "${2:-1}" | playwave; }
 850 
 851 # play a tone/sine-wave sound lasting the number of seconds given, or for 1
 852 # second by default: after the optional duration, the next optional arguments
 853 # are the volume and the tone-frequency; uses my tools `sboard`, `playwave`,
 854 # and `waveout`
 855     tone() {
 856         if [ "${3:-440}" -eq 440 ]; then
 857             sboard -o tone "${1:-1}" "${2:-1}"
 858         else
 859             waveout "${1:-1}" "${2:-1} \* sin(${3:-440} \* tau \* t)"
 860         fi \
 861         | playwave
 862     }
 863 
 864 fi
 865 
 866 # play a ringtone-style sound lasting the number of seconds given, or for 1
 867 # second by default; uses my tools `waveout`, and `playwave`
 868 command -v ringtone > /dev/null || ringtone() {
 869     local f='sin(2048 * tau * t) * exp(-50 * (t % 0.1))'
 870     waveout "${1:-1}" "${2:-1} * $f" | playwave
 871 }