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 }