File: clam.sh 1 #!/bin/sh 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2020-2025 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 # clam 27 # 28 # Command-Line Augmentation Module (clam): get the best out of your shell 29 # 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 # Among these commands, you'll notice a preference for lines whose items 40 # are tab-separated instead of space-separated, and unix-style lines, which 41 # always end with a line-feed, instead of a CRLF byte-pair. This convention 42 # makes plain-text data-streams less ambiguous and generally easier to work 43 # with, especially when passing them along pipes. 44 # 45 # To use this script, you're supposed to `source` it, so its definitions 46 # stay for your whole shell session: for that, you can run `source clam` or 47 # `. clam` (no quotes either way), either directly or at shell startup. 48 # 49 # This script is compatible with `bash`, `zsh`, and even `dash`, which is 50 # debian linux's default non-interactive shell. Some of its commands even 51 # seem to work on busybox's shell. 52 53 54 case "$1" in 55 -h|--h|-help|--help) 56 # show help message, using the info-comment from this very script 57 awk ' 58 /^case / { exit } 59 /^# +clam$/, /^$/ { gsub(/^# ?/, ""); print } 60 ' "$0" 61 exit 0 62 ;; 63 esac 64 65 66 # dash doesn't support regex-matching syntax, forcing to use case statements 67 case "$0" in 68 -bash|-dash|-sh|bash|dash|sh) 69 # script is being sourced with bash or dash, which is good 70 : 71 ;; 72 *) 73 case "$ZSH_EVAL_CONTEXT" in 74 *:file) 75 # script is being sourced with zsh, which is good 76 : 77 ;; 78 *) 79 # script is being run normally, which is a waste of time 80 printf "\e[48;2;255;255;135m\e[38;2;0;0;0mDon't run this script, source it instead: to do that,\e[0m\n" 81 printf "\e[48;2;255;255;135m\e[38;2;0;0;0mrun 'source clam' or '. clam' (no quotes either way).\e[0m\n" 82 # failing during shell-startup may deny shell access, so exit 83 # with a 0 error-code to declare success 84 exit 0 85 ;; 86 esac 87 ;; 88 esac 89 90 91 # n-column-layout shortcuts, using my tool `bsbs` (Book-like Side By Side) 92 alias 1='bsbs 1' 93 alias 2='bsbs 2' 94 alias 3='bsbs 3' 95 alias 4='bsbs 4' 96 alias 5='bsbs 5' 97 alias 6='bsbs 6' 98 alias 7='bsbs 7' 99 alias 8='bsbs 8' 100 alias 9='bsbs 9' 101 alias 0='bsbs 10' 102 103 alias a=avoid 104 105 # find name from the local `apt` database of installable packages 106 aptfind() { 107 local arg 108 local gap=0 109 local options='-JMKiCRS' 110 111 if [ $# -eq 1 ]; then 112 options='--header=1 -JMKiCRS' 113 fi 114 115 for arg in "$@"; do 116 [ "${gap}" -gt 0 ] && printf "\n" 117 gap=1 118 printf "\e[7m%-80s\e[0m\n\n" "${arg}" 119 120 # despite warnings, the `search` command has been around for years 121 apt search "${arg}" 2>/dev/null | 122 grep -E -A 1 "^[a-z0-9-]*${arg}" | sed -u 's/^--$//' 123 done | less "${options}" 124 } 125 126 # APT UPdate/grade 127 aptup() { sudo apt update && sudo apt upgrade "$@"; sudo -k; } 128 129 # emit each argument given as its own line of output 130 args() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@"; } 131 132 # avoid/ignore lines which match any of the regexes given 133 avoid() { 134 awk ' 135 BEGIN { 136 for (i = 1; i < ARGC; i++) { 137 e[i] = ARGV[i] 138 delete ARGV[i] 139 } 140 } 141 142 { 143 for (i = 1; i < ARGC; i++) if ($0 ~ e[i]) next 144 print; fflush() 145 got++ 146 } 147 148 END { exit(got == 0) } 149 ' "${@:-^\r?$}" 150 } 151 152 # show an ansi-styled BANNER-like line 153 banner() { printf "\e[7m%-$(tput cols)s\e[0m\n" "$*"; } 154 155 # emit a colored bar which can help visually separate different outputs 156 bar() { 157 [ "${1:-80}" -gt 0 ] && 158 printf "\e[48;2;218;218;218m%${1:-80}s\e[0m\n" "" 159 } 160 161 # process Blocks/paragraphs of non-empty lines with AWK 162 bawk() { stdbuf -oL awk -F='' -v RS='' "$@"; } 163 164 # play a repeating and annoying high-pitched beep sound a few times a second, 165 # lasting the number of seconds given, or for 1 second by default; uses my 166 # script `waveout` 167 beeps() { 168 local f='sin(2_000 * tau * t) * (t % 0.5 < 0.0625)' 169 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 170 } 171 172 # start by joining all arguments given as a tab-separated-items line of output, 173 # followed by all lines from stdin verbatim 174 begintsv() { 175 awk ' 176 BEGIN { 177 for (i = 1; i < ARGC; i++) { 178 if (i > 1) printf "\t" 179 printf "%s", ARGV[i] 180 delete ARGV[i] 181 } 182 if (ARGC > 1) printf "\n" 183 fflush() 184 } 185 { print; fflush() } 186 ' "$@" 187 } 188 189 # play a repeating synthetic-bell-like sound lasting the number of seconds 190 # given, or for 1 second by default; uses my tool `waveout` 191 bell() { 192 local f='sin(880*tau*u) * exp(-10*u)' 193 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 194 } 195 196 # Breathe Header 5: add an empty line after the first one (the header), 197 # then separate groups of 5 lines with empty lines between them 198 bh5() { 199 awk '(NR - 1) % 5 == 1 && NR > 1 { print "" } { print; fflush() }' "$@" 200 } 201 202 # recursively find all files with at least the number of bytes given; when 203 # not given a minimum byte-count, the default is 100 binary megabytes 204 bigfiles() { 205 local n 206 n="$(echo "${1:-104857600}" | sed -E 's-_--g; s-\.[0-9]+$--')" 207 [ $# -gt 0 ] && shift 208 209 local arg 210 for arg in "${@:-.}"; do 211 if [ ! -d "${arg}" ]; then 212 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 213 return 1 214 fi 215 stdbuf -oL find "${arg}" -type f -size "$n"c -o -size +"$n"c 216 done 217 } 218 219 # Breathe Lines 5: separate groups of 5 lines with empty lines 220 bl5() { 221 awk 'NR % 5 == 1 && NR != 1 { print "" } { print; fflush() }' "$@" 222 } 223 224 # emit a line with a repeating block-like symbol in it 225 blocks() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -█-g'; } 226 227 # Book-like MANual, lays out `man` docs as pairs of side-by-side pages; uses 228 # my tool `bsbs` 229 bman() { 230 local w 231 w="$(tput cols)" 232 w="$((w / 2 - 4))" 233 if [ "$w" -lt 65 ]; then 234 w=65 235 fi 236 MANWIDTH="$w" man "$@" | bsbs 2 237 } 238 239 # BOOK-like MANual, lays out `man` docs as pairs of side-by-side pages; uses 240 # my tool `bsbs` 241 bookman() { 242 local w 243 w="$(tput cols)" 244 w="$((w / 2 - 4))" 245 if [ "$w" -lt 65 ]; then 246 w=65 247 fi 248 MANWIDTH="$w" man "$@" | bsbs 2 249 } 250 251 # split lines using the regex given, turning them into single-item lines 252 breakdown() { 253 local sep="${1:- }" 254 [ $# -gt 0 ] && shift 255 awk -v FPAT="${sep}" '{ for (i = 1; i <= NF; i++) print $i; fflush() }' "$@" 256 } 257 258 # play a busy-phone-line sound lasting the number of seconds given, or for 1 259 # second by default; uses my tool `waveout` 260 busy() { 261 local f='min(1, exp(-90*(u-0.5))) * (sin(480*tau*t) + sin(620*tau*t)) / 2' 262 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 263 } 264 265 c() { cat "$@"; } 266 267 # CAlculator with Nice numbers runs my tool `ca` and colors results with 268 # my tool `nn`, alternating styles to make long numbers easier to read 269 can() { 270 local arg 271 for arg in "$@"; do 272 printf "\e[7m%s\e[0m\n" "${arg}" >&2 273 ca "${arg}" | nn --gray 274 done 275 } 276 277 # uppercase the first letter on each line, and lowercase all later letters 278 capitalize() { 279 awk '{ print; fflush() }' "$@" | sed -E -u 's-^(.*)-\L\1-; s-^(.)-\u\1-' 280 } 281 282 # conCATenate Lines guarantees no lines are ever accidentally joined 283 # across inputs, always emitting a line-feed at the end of every line 284 catl() { awk '{ print; fflush() }' "$@"; } 285 286 # Count with AWK: count the times the AWK expression/condition given is true 287 cawk() { 288 local cond="${1:-1}" 289 [ $# -gt 0 ] && shift 290 awk " 291 { low = lower = tolower(\$0) } 292 ${cond} { count++ } 293 END { print count } 294 " "$@" 295 } 296 297 # Compile C Optimized 298 cco () { cc -Wall -O3 -s -fanalyzer -march=native -mtune=native -flto "$@"; } 299 300 # Compile C Plus Plus Optimized 301 cppo () { c++ -Wall -O3 -s -fanalyzer -march=native -mtune=native -flto "$@"; } 302 303 # center-align lines of text, using the current screen width 304 center() { 305 awk -v width="$(tput cols)" ' 306 { 307 gsub(/\r$/, "") 308 lines[NR] = $0 309 s = $0 310 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "", s) # ANSI style-changers 311 l = length(s) 312 if (maxlen < l) maxlen = l 313 } 314 315 END { 316 n = (width - maxlen) / 2 317 if (n % 1) n = n - (n % 1) 318 fmt = sprintf("%%%ds%%s\n", (n > 0) ? n : 0) 319 for (i = 1; i <= NR; i++) printf fmt, "", lines[i] 320 } 321 ' "$@" 322 } 323 324 # Colored Go Test on the folder given; uses my command `jawk` 325 cgt() { go test "${1:-.}" 2>&1 | jawk '/^ok/' '/^[-]* ?FAIL/' '/^\?/'; } 326 327 # ignore final life-feed from text, if it's the very last byte; also ignore 328 # all trailing carriage-returns 329 choplf() { 330 awk ' 331 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 332 NR > 1 { print ""; fflush() } 333 { gsub(/\r$/, ""); printf "%s", $0; fflush() } 334 ' "$@" 335 } 336 337 # Color Json using the `jq` app, allowing an optional filepath as the data 338 # source, and even an optional transformation formula 339 cj() { jq -C "${2:-.}" "${1:--}"; } 340 341 # clean the screen, after running the command given 342 clean() { 343 local res 344 if [ -p /dev/stdout ]; then 345 "$@" 346 return $? 347 fi 348 349 tput smcup 350 "$@" 351 res=$? 352 tput rmcup 353 return "${res}" 354 } 355 356 # Colored Live/Line-buffered RipGrep ensures results show up immediately, 357 # also emitting colors when piped 358 clrg() { rg --color=always --line-buffered "$@"; } 359 360 # CLear Screen, like the old dos command of the same name 361 cls() { clear; } 362 363 # Colored RipGrep ensures app `rg` emits colors when piped 364 crg() { rg --color=always --line-buffered "$@"; } 365 366 # emit a line with a repeating cross-like symbol in it 367 crosses() { 368 [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -×-g' 369 } 370 371 # split lines using the string given, turning them into single-item lines 372 crumble() { 373 local sep="${1:- }" 374 [ $# -gt 0 ] && shift 375 awk -F "${sep}" '{ for (i = 1; i <= NF; i++) print $i; fflush() }' "$@" 376 } 377 378 # Color Syntax: run syntax-coloring app `bat` without line-wrapping 379 cs() { 380 local cmd="bat" 381 # debian linux uses a different name for the `bat` app 382 if [ -e "/usr/bin/batcat" ]; then 383 cmd="batcat" 384 fi 385 386 "${cmd}" --style=plain,header,numbers \ 387 --theme='Monokai Extended Light' \ 388 --wrap=never --color=always --paging=never "$@" | 389 # make colors readable even on light backgrounds 390 sed -e 's-\x1b\[38;5;70m-\x1b[38;5;28m-g' \ 391 -e 's-\x1b\[38;5;214m-\x1b[38;5;208m-g' \ 392 -e 's-\x1b\[38;5;228m-\x1b[48;5;228m-g' | 393 less -JMKiCRS 394 } 395 396 # Color Syntax of all files in a Folder, showing line numbers 397 csf() { 398 local cmd="bat" 399 # debian linux uses a different name for the `bat` app 400 if [ -e "/usr/bin/batcat" ]; then 401 cmd="batcat" 402 fi 403 404 find "${1:-.}" -type f -print0 | 405 xargs --null "${cmd}" \ 406 --style=plain,header,numbers --theme='Monokai Extended Light' \ 407 --wrap=never --color=always | 408 # make colors readable even on light backgrounds 409 sed -e 's-\x1b\[38;5;70m-\x1b[38;5;28m-g' \ 410 -e 's-\x1b\[38;5;214m-\x1b[38;5;208m-g' \ 411 -e 's-\x1b\[38;5;228m-\x1b[48;5;228m-g' | 412 less -JMKiCRS 413 } 414 415 # Change Units turns common US units into international ones; uses my 416 # scripts `bu` (Better Units) and `nn` (Nice Numbers) 417 cu() { 418 bu "$@" | awk ' 419 NF == 5 || (NF == 4 && $NF == "s") { print $(NF-1), $NF } 420 NF == 4 && $NF != "s" { print $NF } 421 ' | nn --gray 422 } 423 424 # CURL Silent spares you the progress bar, but still tells you about errors 425 curls() { curl --show-error -s "$@"; } 426 427 # listen to streaming DANCE music 428 dance() { 429 printf "streaming \e[7mDance Wave Retro\e[0m\n" 430 # mpv --quiet https://retro.dancewave.online/retrodance.mp3 431 mpv --really-quiet https://retro.dancewave.online/retrodance.mp3 432 } 433 434 # emit a line with a repeating dash-like symbol in it 435 dashes() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -—-g'; } 436 437 # DEcode BASE64-encoded data, or even base64-encoded data-URIs, by ignoring 438 # the leading data-URI declaration, if present 439 debase64() { sed -E 's-^data:.{0,50};base64,--' "${1:--}" | base64 -d; } 440 441 # ignore whole-comment lines, or just trailing unix-style comments in them 442 decomment() { 443 awk '/^ *#/ { next } { gsub(/ *#.*$/, ""); print; fflush(); }' "$@" 444 } 445 446 # turn Comma-Separated-Values tables into tab-separated-values tables 447 # decsv() { xsv fmt -t '\t' "$@"; } 448 449 # DEDUPlicate prevents lines from appearing more than once 450 dedup() { awk '!c[$0]++ { print; fflush() }' "$@"; } 451 452 # dictionary-DEFine the word given, using an online service 453 alias def=define 454 455 # dictionary-define the word given, using an online service 456 define() { 457 local arg 458 local gap=0 459 local options='-JMKiCRS' 460 461 if [ $# -eq 0 ]; then 462 printf "\e[38;2;204;0;0mdefine: no names given\e[0m\n" >&2 463 return 1 464 fi 465 466 if [ $# -eq 1 ]; then 467 options='--header=1 -JMKiCRS' 468 fi 469 470 for arg in "$@"; do 471 [ "${gap}" -gt 0 ] && printf "\n" 472 gap=1 473 printf "\e[7m%-80s\x1b[0m\n" "${arg}" 474 curl -s "dict://dict.org/d:${arg}" | awk ' 475 { gsub(/\r$/, "") } 476 /^151 / { 477 printf "\x1b[38;2;52;101;164m%s\x1b[0m\n", $0; fflush() 478 next 479 } 480 /^[1-9][0-9]{2} / { 481 printf "\x1b[38;2;128;128;128m%s\x1b[0m\n", $0; fflush() 482 next 483 } 484 { print; fflush() } 485 ' 486 done | less "${options}" 487 } 488 489 # turn JSON Lines into a proper json array 490 dejsonl() { jq -s -M "${@:-.}"; } 491 492 # convert lines of Space(s)-Separated Values into lines of tab-separated values 493 dessv() { 494 awk ' 495 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 496 497 { 498 gsub(/\r$/, "") 499 for (i = 1; i <= NF; i++) { 500 if (i > 1) printf "\t" 501 printf "%s", $i 502 } 503 printf "\n"; fflush() 504 } 505 ' "$@" 506 } 507 508 # expand tabs each into up to the number of space given, or 4 by default 509 detab() { expand -t "${1:-4}"; } 510 511 # ignore trailing spaces, as well as trailing carriage returns 512 detrail() { awk '{ gsub(/ *\r?$/, ""); print; fflush() }' "$@"; } 513 514 # turn UTF-16 data into UTF-8 515 deutf16() { iconv -f utf16 -t utf8 "$@"; } 516 517 # DICtionary-define the word given locally 518 dic() { 519 local arg 520 local gap=0 521 local options='-JMKiCRS' 522 523 if [ $# -eq 0 ]; then 524 printf "\e[38;2;204;0;0mdic: no names given\e[0m\n" >&2 525 return 1 526 fi 527 528 if [ $# -eq 1 ]; then 529 options='--header=1 -JMKiCRS' 530 fi 531 532 for arg in "$@"; do 533 [ "${gap}" -gt 0 ] && printf "\n" 534 gap=1 535 printf "\e[7m%-80s\x1b[0m\n" "${arg}" 536 dict "${arg}" 2>&1 | awk ' 537 NR == 1 && /^No definitions found for / { err = 1 } 538 err { printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0; next } 539 1 540 ' 541 done | less "${options}" 542 } 543 544 # DIVide 2 numbers 3 ways, including the complement 545 div() { 546 awk -v a="${1:-1}" -v b="${2:-1}" ' 547 BEGIN { 548 gsub(/_/, "", a) 549 gsub(/_/, "", b) 550 if (a > b) { c = a; a = b; b = c } 551 c = 1 - a / b 552 if (0 <= c && c <= 1) printf "%f\n%f\n%f\n", a / b, b / a, c 553 else printf "%f\n%f\n", a / b, b / a 554 exit 555 }' 556 } 557 558 # emit a line with a repeating dot-like symbol in it 559 dots() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -·-g'; } 560 561 # ignore/remove all matched regexes given on all stdin lines 562 drop() { 563 awk ' 564 BEGIN { 565 for (i = 1; i < ARGC; i++) { 566 e[i] = ARGV[i] 567 delete ARGV[i] 568 } 569 } 570 571 { 572 for (i = 1; i < ARGC; i++) gsub(e[i], "") 573 print; fflush() 574 } 575 ' "${@:-\r$}" 576 } 577 578 # show the current Date and Time 579 dt() { 580 printf "\e[38;2;78;154;6m%s\e[0m \e[38;2;52;101;164m%s\e[0m\n" \ 581 "$(date +'%a %b %d')" "$(date +%T)" 582 } 583 584 # show the current Date, Time, and a Calendar with the 3 `current` months 585 dtc() { 586 { 587 # show the current date/time center-aligned 588 printf "%20s\e[38;2;78;154;6m%s\e[0m \e[38;2;52;101;164m%s\e[0m\n\n" \ 589 "" "$(date +'%a %b %d')" "$(date +%T)" 590 # debian linux has a different `cal` app which highlights the day 591 if [ -e "/usr/bin/ncal" ]; then 592 # fix debian/ncal's weird way to highlight the current day 593 ncal -C -3 | sed -E 's/_\x08(.)/\x1b[7m\1\x1b[0m/g' 594 else 595 cal -3 596 fi 597 } | less -JMKiCRS 598 } 599 600 e() { echo "$@"; } 601 602 # edit plain-text files 603 edit() { micro "$@"; } 604 605 # EDit RUN shell commands, using an interactive editor; uses my tool `leak` 606 edrun() { 607 # dash doesn't support the process-sub syntax 608 # . <( micro -readonly true -filetype shell | leak --inv ) 609 micro -readonly true -filetype shell | leak --inv | . /dev/fd/0 610 } 611 612 # show all empty files in a folder, digging recursively 613 emptyfiles() { 614 local arg 615 for arg in "${@:-.}"; do 616 if [ ! -d "${arg}" ]; then 617 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 618 return 1 619 fi 620 stdbuf -oL find "${arg}" -type f -empty 621 done 622 } 623 624 # show all empty folders in a folder, digging recursively 625 emptyfolders() { 626 local arg 627 for arg in "${@:-.}"; do 628 if [ ! -d "${arg}" ]; then 629 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 630 return 1 631 fi 632 stdbuf -oL find "${arg}" -type d -empty 633 done 634 } 635 636 # follow all lines from stdin with a final line, made by joining all arguments 637 # given as tab-separated-items 638 endtsv() { 639 awk ' 640 BEGIN { 641 for (i = 1; i < ARGC; i++) { 642 item[i] = ARGV[i] 643 delete ARGV[i] 644 } 645 } 646 647 { print; fflush() } 648 649 END { 650 for (i = 1; i < ARGC; i++) { 651 if (i > 1) printf "\t" 652 printf "%s", item[i] 653 } 654 if (ARGC > 1) printf "\n" 655 fflush() 656 } 657 ' "$@" 658 } 659 660 # Extended Plain Interactive Grep 661 epig() { ugrep --color=never -Q -E "$@"; } 662 663 # Extended Plain Recursive Interactive Grep 664 eprig() { ugrep --color=never -Q -E "$@"; } 665 666 # ignore/remove all matched regexes given on all stdin lines 667 alias erase=drop 668 669 # Editor Read-Only 670 ero() { micro -readonly true "$@"; } 671 672 # Expand 4 turns each tab into up to 4 spaces 673 expand4() { expand -t 4 "$@"; } 674 675 # convert EURos into CAnadian Dollars, using the latest official exchange 676 # rates from the bank of canada; during weekends, the latest rate may be 677 # from a few days ago; the default amount of euros to convert is 1, when 678 # not given 679 eur2cad() { 680 local site='https://www.bankofcanada.ca/valet/observations/group' 681 local csv_rates="${site}/FX_RATES_DAILY/csv" 682 local url 683 url="${csv_rates}?start_date=$(date -d '3 days ago' +'%Y-%m-%d')" 684 curl -s "${url}" | awk -F, -v amount="$(echo "${1:-1}" | sed 's-_--g')" ' 685 /EUR/ { for (i = 1; i <= NF; i++) if($i ~ /EUR/) j = i } 686 END { gsub(/"/, "", $j); if (j != 0) printf "%.2f\n", amount * $j }' 687 } 688 689 # Faint (gray) lines with AWK 690 fawk() { 691 local cond="${1:-1}" 692 [ $# -gt 0 ] && shift 693 694 awk ' 695 { low = lower = tolower($0) } 696 697 '"${cond}"' { 698 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;168;168;168m") 699 printf "\x1b[38;2;168;168;168m%s\x1b[0m\n", $0; fflush() 700 next 701 } 702 703 { print; fflush() } 704 ' "$@" 705 } 706 707 # fetch/web-request all URIs given, using protcol HTTPS when none is given 708 fetch() { 709 local a 710 for a in "$@"; do 711 case "$a" in 712 file://*|https://*|http://*) curl --show-error -s "$a";; 713 ftp://*|ftps://*|sftp://*) curl --show-error -s "$a";; 714 dict://*|telnet://*) curl --show-error -s "$a";; 715 data:*) echo "$a" | sed -E 's-^data:.{0,50};base64,--' | base64 -d;; 716 *) curl --show-error -s "https://$a";; 717 esac 718 done 719 } 720 721 # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings 722 ff() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } 723 724 # FInd FIles 725 fifi() { 726 local arg 727 local what="${1:-.}" 728 [ $# -gt 0 ] && shift 729 730 for arg in "${@:-.}"; do 731 if [ ! -d "${arg}" ]; then 732 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 733 return 1 734 fi 735 stdbuf -oL find "${arg}" -type f 736 done | awk -v what="${what}" ' 737 BEGIN { 738 m = "this variant of AWK lacks case-insensitive regex-matching" 739 if (IGNORECASE == "") { 740 printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr" 741 exit 125 742 } 743 IGNORECASE = 1 744 } 745 746 # BEGIN { what = tolower(what) } 747 # tolower($0) ~ what { got++; print; fflush() } 748 $0 ~ what { got++; print; fflush() } 749 END { exit(got == 0) } 750 ' 751 } 752 753 filemime() { file --mime-type "$@"; } 754 755 # show all files in folders, digging recursively 756 files() { 757 local arg 758 for arg in "${@:-.}"; do 759 if [ -f "${arg}" ]; then 760 printf "%s\n" "$(realpath "${arg}")" 761 continue 762 fi 763 if [ -d "${arg}" ]; then 764 stdbuf -oL find "${arg}" -type f 765 fi 766 done | awk '!c[$0]++ { print; fflush() }' 767 } 768 769 # recursively find all files with fewer bytes than the number given 770 filesunder() { 771 local n 772 n="$(echo "${1:-4097}" | sed -E 's-_--g; s-\.[0-9]+$--')" 773 [ $# -gt 0 ] && shift 774 775 local arg 776 for arg in "${@:-.}"; do 777 if [ ! -d "${arg}" ]; then 778 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 779 return 1 780 fi 781 stdbuf -oL find "${arg}" -type f -size -"$n"c 782 done 783 } 784 785 # get the first n lines, or 1 by default 786 first() { head -n "${1:-1}" "${2:--}"; } 787 788 # fix lines, ignoring leading UTF-8_BOMs (byte-order-marks) on each input's 789 # first line, turning all end-of-line CRLF byte-pairs into single line-feeds, 790 # and ensuring each input's last line ends with a line-feed; trailing spaces 791 # are also ignored 792 fixlines() { 793 awk ' 794 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 795 { gsub(/ *\r?$/, ""); print; fflush() } 796 ' "$@" 797 } 798 799 # try to run the command given using line-buffering for its (standard) output 800 flushlines() { stdbuf -oL "$@"; } 801 802 # show all folders in other folders, digging recursively 803 folders() { 804 local arg 805 for arg in "${@:-.}"; do 806 if [ -f "${arg}" ]; then 807 continue 808 fi 809 if [ ! -d "${arg}" ]; then 810 printf "\e[31mno folder named %s\e[0m\n" "${arg}" > /dev/stderr 811 fi 812 stdbuf -oL find "${arg}" -type d | awk '!/^\.$/ { print; fflush() }' 813 done | awk '!c[$0]++ { print; fflush() }' 814 } 815 816 # show total sizes for the all folders given, digging recursively 817 foldersizes() { 818 local arg 819 for arg in "${@:-.}"; do 820 if [ ! -d "${arg}" ]; then 821 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 822 return 1 823 fi 824 du "${arg}" | sort -rnk1 825 done | awk '{ $1 *= 1024; print; fflush() }' | sed -u 's-^ *--; s- *-\t-1' 826 } 827 828 # start from the line number given, skipping all previous ones 829 fromline() { tail -n +"${1:-1}" "${2:--}"; } 830 831 # convert FeeT into meters 832 ft() { 833 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 834 awk '/./ { printf "%.2f\n", 0.3048 * $0; fflush() }' 835 } 836 837 # convert FeeT² (squared) into meters² 838 ft2() { 839 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 840 awk '/./ { printf "%.2f\n", 0.09290304 * $0 }' 841 } 842 843 # convert a mix of FeeT and INches into meters 844 ftin() { 845 local ft="${1:-0}" 846 ft="$(echo "${ft}" | sed 's-_--g')" 847 local in="${2:-0}" 848 in="$(echo "${in}" | sed 's-_--g')" 849 awk "BEGIN { print 0.3048 * ${ft} + 0.0254 * ${in}; exit }" 850 } 851 852 # convert GALlons into liters 853 gal() { 854 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 855 awk '/./ { printf "%.2f\n", 3.785411784 * $0; fflush() }' 856 } 857 858 # convert binary GigaBytes into bytes 859 gb() { 860 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 861 awk '/./ { printf "%.4f\n", 1073741824 * $0; fflush() }' | 862 sed 's-\.00*$--' 863 } 864 865 # Good, Bad, Meh colors lines using up to 3 regular expressions, keeping all 866 # other input lines verbatim 867 gbm() { 868 local good="$1" 869 local bad="$2" 870 local meh="$3" 871 [ $# -gt 0 ] && shift 872 [ $# -gt 0 ] && shift 873 [ $# -gt 0 ] && shift 874 875 awk ' 876 BEGIN { 877 gotgood = ARGC > 1 && ARGV[1] != "" 878 gotbad = ARGC > 2 && ARGV[2] != "" 879 gotmeh = ARGC > 3 && ARGV[3] != "" 880 good = ARGV[1] 881 bad = ARGV[2] 882 meh = ARGV[3] 883 delete ARGV[1] 884 delete ARGV[2] 885 delete ARGV[3] 886 } 887 888 gotgood && $0 ~ good { 889 # code to use a color-blind-friendlier blue, instead of green 890 # gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;0;95;215m") 891 # printf "\x1b[38;2;0;95;215m%s\x1b[0m\n", $0 892 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;0;135;95m") 893 printf "\x1b[38;2;0;135;95m%s\x1b[0m\n", $0; fflush() 894 next 895 } 896 897 gotbad && $0 ~ bad { 898 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;204;0;0m") 899 printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0; fflush() 900 next 901 } 902 903 gotmeh && $0 ~ meh { 904 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;168;168;168m") 905 printf "\x1b[38;2;168;168;168m%s\x1b[0m\n", $0; fflush() 906 next 907 } 908 909 { print; fflush() } 910 ' "${good}" "${bad}" "${meh}" "$@" 911 } 912 913 # glue/stick together various lines, only emitting a line-feed at the end; an 914 # optional argument is the output-item-separator, which is empty by default 915 glue() { 916 local sep="${1:-}" 917 [ $# -gt 0 ] && shift 918 awk -v sep="${sep}" ' 919 NR > 1 { printf "%s", sep } 920 { gsub(/\r/, ""); printf "%s", $0; fflush() } 921 END { if (NR > 0) print ""; fflush() } 922 ' "$@" 923 } 924 925 # GO Build Stripped: a common use-case for the go compiler 926 gobs() { go build -ldflags "-s -w" -trimpath "$@"; } 927 928 # GO DEPendencieS: show all dependencies in a go project 929 godeps() { go list -f '{{ join .Deps "\n" }}' "$@"; } 930 931 # GO IMPortS: show all imports in a go project 932 goimps() { go list -f '{{ join .Imports "\n" }}' "$@"; } 933 934 # go to the folder picked using an interactive TUI; uses my tool `bf` 935 goto() { 936 local where 937 where="$(bf "${1:-.}")" 938 if [ $? -ne 0 ]; then 939 return 0 940 fi 941 942 where="$(realpath "${where}")" 943 if [ ! -d "${where}" ]; then 944 where="$(dirname "${where}")" 945 fi 946 cd "${where}" || return 947 } 948 949 # GRoup via AWK groups lines using common results of the AWK expression given 950 grawk() { 951 local code="${1:-\$0}" 952 [ $# -gt 0 ] && shift 953 954 awk ' 955 { low = lower = tolower($0) } 956 957 { 958 k = '"${code}"' 959 if (!(k in groups)) ordkeys[++okl] = k 960 groups[k][length(groups[k]) + 1] = $0 961 } 962 963 END { 964 for (i = 1; i <= okl; i++) { 965 k = ordkeys[i] 966 n = length(groups[k]) 967 for (j = 1; j <= n; j++) print groups[k][j] 968 } 969 } 970 ' "$@" 971 } 972 973 # Style lines using a GRAY-colored BACKground 974 grayback() { 975 awk ' 976 { 977 gsub(/\x1b\[0m/, "\x1b[0m\x1b[48;2;218;218;218m") 978 printf "\x1b[48;2;218;218;218m%s\x1b[0m\n", $0; fflush() 979 } 980 ' "$@" 981 } 982 983 # Global extended regex SUBstitute, using the AWK function of the same name: 984 # arguments are used as regex/replacement pairs, in that order 985 gsub() { 986 awk ' 987 BEGIN { 988 for (i = 1; i < ARGC; i++) { 989 args[++n] = ARGV[i] 990 delete ARGV[i] 991 } 992 } 993 { 994 for (i = 1; i <= n; i += 2) gsub(args[i], args[i + 1]) 995 print; fflush() 996 } 997 ' "$@" 998 } 999 1000 # show Help laid out on 2 side-by-side columns; uses my tool `bsbs` 1001 h2() { naman "$@" | bsbs 2; } 1002 1003 # Highlight (lines) with AWK 1004 hawk() { 1005 local cond="${1:-1}" 1006 [ $# -gt 0 ] && shift 1007 awk ' 1008 { low = lower = tolower($0) } 1009 '"${cond}"' { 1010 gsub(/\x1b\[0m/, "\x1b[0m\x1b[7m") 1011 printf "\x1b[7m%s\x1b[0m\n", $0; fflush() 1012 next 1013 } 1014 { print; fflush() } 1015 ' "$@" 1016 } 1017 1018 # play a heartbeat-like sound lasting the number of seconds given, or for 1 1019 # second by default; uses my tool `waveout` 1020 heartbeat() { 1021 local a='sin(v[0]*tau*exp(-20*v[1]))*exp(-2*v[1])' 1022 local b='((12, u), (8, (u-0.25)%1))' 1023 local f="sum($a for v in $b) / 2" 1024 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1025 } 1026 1027 # Highlighted-style ECHO 1028 hecho() { printf "\e[7m%s\e[0m\n" "$*"; } 1029 1030 # show each byte as a pair of HEXadecimal (base-16) symbols 1031 hexify() { 1032 cat "$@" | od -x -A n | 1033 awk '{ gsub(/ +/, ""); printf "%s", $0; fflush() } END { printf "\n" }' 1034 } 1035 1036 # HIghlighted-style ECHO 1037 hiecho() { printf "\e[7m%s\e[0m\n" "$*"; } 1038 1039 # highlight lines 1040 highlight() { 1041 awk ' 1042 { 1043 gsub(/\x1b\[0m/, "\x1b[0m\x1b[7m") 1044 printf "\x1b[7m%s\x1b[0m\n", $0; fflush() 1045 } 1046 ' "$@" 1047 } 1048 1049 # HIghlight LEAK emits/tees input both to stdout and stderr, highlighting what 1050 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1051 # involving several steps 1052 hileak() { 1053 awk ' 1054 { 1055 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") 1056 printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" 1057 print; fflush() 1058 } 1059 ' "$@" 1060 } 1061 1062 # highlight lines 1063 alias hilite=highlight 1064 1065 # Help Me Remember my custom shell commands 1066 hmr() { 1067 local cmd="bat" 1068 # debian linux uses a different name for the `bat` app 1069 if [ -e "/usr/bin/batcat" ]; then 1070 cmd="batcat" 1071 fi 1072 1073 "$cmd" \ 1074 --style=plain,header,numbers --theme='Monokai Extended Light' \ 1075 --wrap=never --color=always "$(which clam)" | 1076 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 1077 } 1078 1079 # convert seconds into a colon-separated Hours-Minutes-Seconds triple 1080 hms() { 1081 echo "${@:-0}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { 1082 x = $0 1083 h = (x - x % 3600) / 3600 1084 m = (x % 3600) / 60 1085 s = x % 60 1086 printf "%02d:%02d:%05.2f\n", h, m, s; fflush() 1087 }' 1088 } 1089 1090 # find all hyperlinks inside HREF attributes in the input text 1091 href() { 1092 awk ' 1093 BEGIN { e = "href=\"[^\"]+\"" } 1094 { 1095 for (s = $0; match(s, e); s = substr(s, RSTART + RLENGTH)) { 1096 print substr(s, RSTART + 6, RLENGTH - 7); fflush() 1097 } 1098 } 1099 ' "$@" 1100 } 1101 1102 # Index all lines starting from 0, using a tab right after each line number 1103 i() { stdbuf -oL nl -b a -w 1 -v 0 "$@"; } 1104 1105 alias ia=iavoid 1106 1107 # avoid/ignore lines which case-insensitively match any of the regexes given 1108 iavoid() { 1109 awk ' 1110 BEGIN { 1111 if (IGNORECASE == "") { 1112 m = "this variant of AWK lacks case-insensitive regex-matching" 1113 printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr" 1114 exit 125 1115 } 1116 IGNORECASE = 1 1117 1118 for (i = 1; i < ARGC; i++) { 1119 e[i] = ARGV[i] 1120 delete ARGV[i] 1121 } 1122 } 1123 1124 { 1125 for (i = 1; i < ARGC; i++) if ($0 ~ e[i]) next 1126 print; fflush(); got++ 1127 } 1128 1129 END { exit(got == 0) } 1130 ' "${@:-^\r?$}" 1131 } 1132 1133 # case-Insensitively DEDUPlicate prevents lines from appearing more than once 1134 idedup() { awk '!c[tolower($0)]++ { print; fflush() }' "$@"; } 1135 1136 # ignore/remove all case-insensitively matched regexes given on all stdin lines 1137 idrop() { 1138 awk ' 1139 BEGIN { 1140 if (IGNORECASE == "") { 1141 m = "this variant of AWK lacks case-insensitive regex-matching" 1142 printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr" 1143 exit 125 1144 } 1145 IGNORECASE = 1 1146 1147 for (i = 1; i < ARGC; i++) { e[i] = ARGV[i]; delete ARGV[i] } 1148 } 1149 1150 { 1151 for (i = 1; i < ARGC; i++) gsub(e[i], "") 1152 print; fflush() 1153 } 1154 ' "${@:-\r$}" 1155 } 1156 1157 # ignore/remove all case-insensitively matched regexes given on all stdin lines 1158 alias ierase=idrop 1159 1160 # ignore command in a pipe: this allows quick re-editing of pipes, while 1161 # still leaving signs of previously-used steps, as a memo 1162 ignore() { cat; } 1163 1164 alias im=imatch 1165 1166 # only keep lines which case-insensitively match any of the regexes given 1167 imatch() { 1168 awk ' 1169 BEGIN { 1170 if (IGNORECASE == "") { 1171 m = "this variant of AWK lacks case-insensitive regex-matching" 1172 printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr" 1173 exit 125 1174 } 1175 IGNORECASE = 1 1176 1177 for (i = 1; i < ARGC; i++) { 1178 e[i] = ARGV[i] 1179 delete ARGV[i] 1180 } 1181 } 1182 1183 { 1184 for (i = 1; i < ARGC; i++) { 1185 if ($0 ~ e[i]) { 1186 print; fflush() 1187 got++ 1188 next 1189 } 1190 } 1191 } 1192 1193 END { exit(got == 0) } 1194 ' "${@:-[^\r]}" 1195 } 1196 1197 # start each non-empty line with extra n spaces 1198 indent() { 1199 awk ' 1200 BEGIN { 1201 n = ARGV[1] + 0 1202 delete ARGV[1] 1203 fmt = sprintf("%%%ds%%s\n", (n > 0) ? n : 0) 1204 } 1205 1206 /^\r?$/ { print ""; fflush(); next } 1207 { gsub(/\r$/, ""); printf(fmt, "", $0); fflush() } 1208 ' "$@" 1209 } 1210 1211 # Interactive Read-Only Top 1212 irot() { htop --readonly "$@"; } 1213 1214 # emit each word-like item from each input line on its own line; when a file 1215 # has tabs on its first line, items are split using tabs alone, which allows 1216 # items to have spaces in them 1217 items() { 1218 awk ' 1219 FNR == 1 { FS = ($0 ~ /\t/) ? "\t" : " "; $0 = $0 } 1220 { gsub(/\r$/, ""); for (i = 1; i <= NF; i++) print $i; fflush() } 1221 ' "$@" 1222 } 1223 1224 # case-insensitively deduplicate lines, keeping them in their original order: 1225 # the checking/matching is case-insensitive, but each first match is output 1226 # exactly as is 1227 iunique() { awk '!c[tolower($0)]++ { print; fflush() }' "$@"; } 1228 1229 alias j0=json0 1230 1231 alias j2=json2 1232 1233 # Judge with AWK colors lines green/red/gray, using up to 3 AWK conditions 1234 jawk() { 1235 local good="${1:-0}" 1236 local bad="${2:-0}" 1237 local meh="${3:-0}" 1238 1239 [ $# -gt 0 ] && shift 1240 [ $# -gt 0 ] && shift 1241 [ $# -gt 0 ] && shift 1242 1243 awk ' 1244 { low = lower = tolower($0) } 1245 1246 '"${good}"' { 1247 # code to use a color-blind-friendlier blue, instead of green 1248 # gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;0;95;215m") 1249 # printf "\x1b[38;2;0;95;215m%s\x1b[0m\n", $0; fflush() 1250 1251 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;0;135;95m") 1252 printf "\x1b[38;2;0;135;95m%s\x1b[0m\n", $0; fflush() 1253 next 1254 } 1255 1256 '"${bad}"' { 1257 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;204;0;0m") 1258 printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0; fflush() 1259 next 1260 } 1261 1262 '"${meh}"' { 1263 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;168;168;168m") 1264 printf "\x1b[38;2;168;168;168m%s\x1b[0m\n", $0; fflush() 1265 next 1266 } 1267 1268 { print; fflush() } 1269 ' "$@" 1270 } 1271 1272 # listen to streaming JAZZ music 1273 jazz() { 1274 printf "streaming \e[7mSmooth Jazz Instrumental\e[0m\n" 1275 # mpv https://stream.zeno.fm/00rt0rdm7k8uv 1276 mpv --quiet https://stream.zeno.fm/00rt0rdm7k8uv 1277 } 1278 1279 # Json Lines turns JSON top-level arrays into multiple individually-JSON lines 1280 # using the `jq` app, keeping all other top-level values as single line JSON 1281 # outputs, allowing an optional filepath as the data source 1282 jl() { jq -c -C ".[]" "${1:--}"; } 1283 1284 # show a `dad` JOKE from the web, sometimes even a very funny one 1285 joke() { 1286 curl --show-error -s https://icanhazdadjoke.com | fold -s | 1287 awk '{ gsub(/ *\r?$/, ""); print }' 1288 } 1289 1290 # shrink/compact JSON using the `jq` app, allowing an optional filepath, and 1291 # even an optional transformation formula after that 1292 json0() { jq -c -M "${2:-.}" "${1:--}"; } 1293 1294 # show JSON data on multiple lines, using 2 spaces for each indentation level, 1295 # allowing an optional filepath, and even an optional transformation formula 1296 # after that 1297 json2() { jq --indent 2 -M "${2:-.}" "${1:--}"; } 1298 1299 # JSON Lines turns JSON top-level arrays into multiple individually-JSON lines 1300 # using the `jq` app, keeping all other top-level values as single line JSON 1301 # outputs, allowing an optional filepath as the data source 1302 jsonl() { jq -c -C ".[]" "${1:--}"; } 1303 1304 # emit the given number of random/junk bytes, or 1024 junk bytes by default 1305 junk() { head -c "$(echo "${1:-1024}" | sed 's-_--g')" /dev/urandom; } 1306 1307 # convert binary KiloBytes into bytes 1308 kb() { 1309 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1310 awk '/./ { printf "%.2f\n", 1024 * $0; fflush() }' | 1311 sed 's-\.00*$--' 1312 } 1313 1314 # run `less`, showing line numbers, among other settings 1315 l() { less -JMKNiCRS "$@"; } 1316 1317 # LABEL/precede data with an ANSI-styled line 1318 label() { printf "\e[7m%-*s\e[0m\n" "$(($(tput cols) - 2))" "$*"; cat -; } 1319 1320 # find the LAN (local-area network) IP address for this device 1321 lanip() { hostname -I; } 1322 1323 # play a stereotypical once-a-second laser sound for the number of seconds 1324 # given, or for 1 second (once) by default; uses my tool `waveout` 1325 laser() { 1326 local f='sin(100 * tau * exp(-40 * u))' 1327 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1328 } 1329 1330 # get the last n lines, or 1 by default 1331 last() { tail -n "${1:-1}" "${2:--}"; } 1332 1333 # convert pounds (LB) into kilograms 1334 lb() { 1335 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1336 awk '/./ { printf "%.2f\n", 0.45359237 * $0; fflush() }' 1337 } 1338 1339 # convert a mix of pounds (LB) and weight-ounces (OZ) into kilograms 1340 lboz() { 1341 local lb="${1:-0}" 1342 lb="$(echo "${lb}" | sed 's-_--g')" 1343 local oz="${2:-0}" 1344 oz="$(echo "${oz}" | sed 's-_--g')" 1345 awk "BEGIN { print 0.45359237 * ${lb} + 0.028349523 * ${oz}; exit }" 1346 } 1347 1348 # turn the first n space-separated fields on each line into tab-separated 1349 # ones; this behavior is useful to make the output of many cmd-line tools 1350 # into TSV, since filenames are usually the last fields, and these may 1351 # contain spaces which aren't meant to be split into different fields 1352 leadtabs() { 1353 local n="$1" 1354 local cmd 1355 cmd="$([ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "")" 1356 cmd="s-^ *--; s- *\\r?\$--; $(echo "${cmd}" | sed 's/ /s- +-\\t-1;/g')" 1357 sed -u -E "${cmd}" 1358 } 1359 1360 # limit stops at the first n bytes, or 1024 bytes by default 1361 limit() { head -c "$(echo "${1:-1024}" | sed 's-_--g')" "${2:--}"; } 1362 1363 # Less with Header runs `less` with line numbers, ANSI styles, no line-wraps, 1364 # and using the first n lines as a sticky-header (1 by default), so they 1365 # always show on top 1366 lh() { 1367 local n="${1:-1}" 1368 [ $# -gt 0 ] && shift 1369 less --header="$n" -JMKNiCRS "$@" 1370 } 1371 1372 lh1() { less --header=1 -JMKNiCRS "$@"; } 1373 1374 lh2() { less --header=2 -JMKNiCRS "$@"; } 1375 1376 # LINe-Buffered Output 1377 linbo() { stdbuf -oL "$@"; } 1378 1379 # ensure lines are never accidentally joined across files, by always emitting 1380 # a line-feed at the end of each line 1381 lines() { awk '{ print; fflush() }' "$@"; } 1382 1383 # regroup adjacent lines into n-item tab-separated lines 1384 lineup() { 1385 local n="${1:-0}" 1386 [ $# -gt 0 ] && shift 1387 1388 if [ "$n" -le 0 ]; then 1389 awk ' 1390 NR > 1 { printf "\t" } 1391 { printf "%s", $0; fflush() } 1392 END { if (NR > 0) print "" } 1393 ' "$@" 1394 return $? 1395 fi 1396 1397 awk -v n="$n" ' 1398 NR % n != 1 && n > 1 { printf "\t" } 1399 { printf "%s", $0; fflush() } 1400 NR % n == 0 { print ""; fflush() } 1401 END { if (NR % n != 0) print "" } 1402 ' "$@" 1403 } 1404 1405 # find all hyperLINKS (https:// and http://) in the input text 1406 alias links=weblinks 1407 1408 # LOAD data from the filename or URI given; uses my tool `get` 1409 load() { get "$@"; } 1410 1411 # LOcal SERver webserves files in a folder as localhost, using the port 1412 # number given, or port 8080 by default 1413 loser() { 1414 printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 1415 python3 -m http.server "${1:-8080}" -d "${2:-.}" 1416 } 1417 1418 # LOWercase all ASCII symbols 1419 low() { awk '{ print tolower($0); fflush() }' "$@"; } 1420 1421 # LOWERcase all ASCII symbols 1422 lower() { awk '{ print tolower($0); fflush() }' "$@"; } 1423 1424 # Listen To Youtube 1425 alias lty=yap 1426 1427 alias m=match 1428 1429 # only keep lines which match any of the regexes given 1430 match() { 1431 awk ' 1432 BEGIN { 1433 for (i = 1; i < ARGC; i++) { 1434 e[i] = ARGV[i] 1435 delete ARGV[i] 1436 } 1437 } 1438 1439 { 1440 for (i = 1; i < ARGC; i++) { 1441 if ($0 ~ e[i]) { 1442 print; fflush() 1443 got++ 1444 next 1445 } 1446 } 1447 } 1448 1449 END { exit(got == 0) } 1450 ' "${@:-[^\r]}" 1451 } 1452 1453 # convert binary MegaBytes into bytes 1454 mb() { 1455 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1456 awk '/./ { printf "%.2f\n", 1048576 * $0; fflush() }' | 1457 sed 's-\.00*$--' 1458 } 1459 1460 # Multi-Core MAKE runs `make` using all cores 1461 mcmake() { make -j "$(nproc)" "$@"; } 1462 1463 # convert MIles into kilometers 1464 mi() { 1465 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1466 awk '/./ { printf "%.2f\n", 1.609344 * $0; fflush() }' 1467 } 1468 1469 # convert MIles² (squared) into kilometers² 1470 mi2() { 1471 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1472 awk '/./ { printf "%.2f\n", 2.5899881103360 * $0 }' 1473 } 1474 1475 # Make In Folder 1476 mif() { 1477 local code 1478 pushd "${1:-.}" > /dev/null || return 1479 [ $# -gt 0 ] && shift 1480 make "$@" 1481 code=$? 1482 popd > /dev/null || return "${code}" 1483 return "${code}" 1484 } 1485 1486 # MINimize DECimalS ignores all trailing decimal zeros in numbers, and even 1487 # the decimal dots themselves, when decimals in a number are all zeros 1488 mindecs() { 1489 awk '{ gsub(/\r$/, ""); print; fflush() }' "$@" | 1490 sed -u -E 's-([0-9]+)\.0+\W-\1-g; s-([0-9]+\.[0-9]*[1-9])0+\W-\1-g' 1491 } 1492 1493 # run `make` 1494 mk() { make "$@"; } 1495 1496 # convert Miles Per Hour into kilometers per hour 1497 mph() { 1498 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1499 awk '/./ { printf "%.2f\n", 1.609344 * $0 }' 1500 } 1501 1502 # Number all lines, using a tab right after each line number 1503 n() { stdbuf -oL nl -b a -w 1 -v 1 "$@"; } 1504 1505 # NArrow MANual, keeps `man` narrow, even if the window/tab is wide when run 1506 naman() { 1507 local w 1508 w="$(tput cols)" 1509 w="$((w / 2 - 4))" 1510 if [ "$w" -lt 80 ]; then 1511 w=80 1512 fi 1513 MANWIDTH="$w" man "$@" 1514 } 1515 1516 # Not AND sorts its 2 inputs, then finds lines not in common 1517 nand() { 1518 # comm -3 <(sort "$1") <(sort "$2") 1519 # dash doesn't support the process-sub syntax 1520 (sort "$1" | (sort "$2" | (comm -3 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1521 } 1522 1523 # listen to streaming NEW WAVE music 1524 newwave() { 1525 printf "streaming \e[7mNew Wave radio\e[0m\n" 1526 mpv --quiet https://puma.streemlion.com:2910/stream 1527 } 1528 1529 # Nice CAlculator runs my tool `ca` and colors results with my tool `nn`, 1530 # alternating styles to make long numbers easier to read 1531 nica() { 1532 local arg 1533 for arg in "$@"; do 1534 printf "\e[7m%s\e[0m\n" "${arg}" >&2 1535 ca "${arg}" | nn --gray 1536 done 1537 } 1538 1539 # NIce(r) COlumns makes the output of many commands whose output starts with 1540 # a header line easier to read; uses my tool `nn` 1541 nico() { 1542 awk ' 1543 (NR - 1) % 5 == 1 && NR > 1 { print "" } 1544 { printf "%5d %s\n", NR - 1, $0; fflush() } 1545 ' "$@" | nn --gray | less -JMKiCRS 1546 } 1547 1548 # emit nothing to output and/or discard everything from input 1549 nil() { 1550 if [ $# -gt 0 ]; then 1551 "$@" > /dev/null 1552 else 1553 cat < /dev/null 1554 fi 1555 } 1556 1557 # Nice Json colors data using the `jq` app, allowing an optional filepath as 1558 # the data source, and even an optional transformation formula 1559 nj() { jq -C "${2:-.}" "${1:--}"; } 1560 1561 # Nice Json Lines colors data using the `jq` app, allowing an optional filepath 1562 # as the data source 1563 njl() { jq -c -C ".[]" "${1:--}"; } 1564 1565 # pipe-run my tools `nj` (Nice Json) and `nn` (Nice Numbers) 1566 njnn() { nj "$@" | nn --gray; } 1567 1568 # Nice JSON Lines colors data using the `jq` app, allowing an optional filepath 1569 # as the data source 1570 njsonl() { jq -c -C ".[]" "${1:--}"; } 1571 1572 # Narrow MANual, keeps `man` narrow, even if the window/tab is wide when run 1573 nman() { 1574 local w 1575 w="$(tput cols)" 1576 w="$((w / 2 - 4))" 1577 if [ "$w" -lt 80 ]; then 1578 w=80 1579 fi 1580 MANWIDTH="$w" man "$@" 1581 } 1582 1583 # convert Nautical MIles into kilometers 1584 nmi() { 1585 echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | 1586 awk '/./ { printf "%.2f\n", 1.852 * $0; fflush() }' 1587 } 1588 1589 # play a white-noise sound lasting the number of seconds given, or for 1 1590 # second by default; uses my tool `waveout` 1591 noise() { waveout "${1:-1}" "${2:-0.05} * random()" | mpv --really-quiet -; } 1592 1593 # show the current date and time 1594 now() { date +'%Y-%m-%d %H:%M:%S'; } 1595 1596 # Nice Print Python result; uses my tool `nn` 1597 npp() { python -c "print($1)" | nn "${2:---gray}"; } 1598 1599 # Nice `Process Show` shows/lists all current processes shown by `ps` in a 1600 # way that's easier to read; uses my tool `nn` 1601 nps() { 1602 local res 1603 local code 1604 res="$(ps "${@:-aux}")" 1605 code=$? 1606 if [ "${code}" -ne 0 ]; then 1607 return "${code}" 1608 fi 1609 1610 echo "${res}" | awk ' 1611 BEGIN { 1612 d = strftime("%a %b %d") 1613 t = strftime("%H:%M:%S") 1614 # printf "\x1b[7m%30s%s %s%30s\x1b[0m\n\n", "", d, t, "" 1615 fmt = "\x1b[38;2;128;128;128m\x1b[7m%30s%s %s%30s\x1b[0m\n\n" 1616 printf fmt, "", d, t, "" 1617 } 1618 1619 (NR - 1) % 5 == 1 && NR > 1 { print "" } 1620 1621 $1 == "root" { 1622 gsub(/^/, "\x1b[38;2;52;101;164m") 1623 gsub(/ +/, "&\x1b[0m\x1b[38;2;52;101;164m") 1624 gsub(/$/, "\x1b[0m") 1625 } 1626 1627 { 1628 gsub(/ \? /, "\x1b[38;2;135;135;175m&\x1b[0m") 1629 gsub(/0\.0/, "\x1b[38;2;135;135;175m&\x1b[0m") 1630 gsub(/0:00/, "\x1b[38;2;135;135;175m&\x1b[0m") 1631 printf "%3d %s\n", NR - 1, $0 1632 } 1633 ' | nn --gray | less -JMKiCRS 1634 } 1635 1636 # Nice Size, using my tools `nn` and `cext` 1637 ns() { wc -c "$@" | nn --gray | cext; } 1638 1639 # Nice Systemctl Status 1640 nss() { 1641 systemctl status "$@" 2>&1 | sed 's-\x1b\[[^A-Za-z][A-Za-z]--g' | sed -E \ 1642 -e 's-(^[^ ] )([^ ]+\.service)-\1\x1b[7m\2\x1b[0m-' \ 1643 -e 's- (enabled)- \x1b[38;2;0;135;95m\x1b[7m\1\x1b[0m-g' \ 1644 -e 's- (disabled)- \x1b[38;2;215;95;0m\x1b[7m\1\x1b[0m-g' \ 1645 -e 's- (active \(running\))- \x1b[38;2;0;135;95m\x1b[7m\1\x1b[0m-g' \ 1646 -e 's- (inactive \(dead\))- \x1b[38;2;204;0;0m\x1b[7m\1\x1b[0m-g' \ 1647 -e 's-^(Unit .* could not .*)$-\x1b[38;2;204;0;0m\x1b[7m\1\x1b[0m\n-' \ 1648 -e 's-(\[WARN\].*)$-\x1b[38;2;215;95;0m\x1b[7m\1\x1b[0m\n-' \ 1649 -e 's-(\[ERR\].*)$-\x1b[38;2;204;0;0m\x1b[7m\1\x1b[0m\n-' | 1650 less -JMKiCRS 1651 } 1652 1653 # Nice TimeStamp 1654 nts() { 1655 ts '%Y-%m-%d %H:%M:%S' | 1656 sed -u 's-^-\x1b[48;2;218;218;218m\x1b[38;2;0;95;153m-; s- -\x1b[0m\t-2' 1657 } 1658 1659 # emit nothing to output and/or discard everything from input 1660 null() { 1661 if [ $# -gt 0 ]; then 1662 "$@" > /dev/null 1663 else 1664 cat < /dev/null 1665 fi 1666 } 1667 1668 # NULl-terminate LINES ends each stdin line with a null byte, instead of a 1669 # line-feed byte 1670 # nullines() { 1671 # awk -v ORS='\000' ' 1672 # FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1673 # { gsub(/\r$/, ""); print; fflush() } 1674 # ' "$@" 1675 # } 1676 1677 # (Nice) What Are These (?) shows what the names given to it are/do, coloring 1678 # the syntax of shell functions 1679 nwat() { 1680 local arg 1681 local gap=0 1682 1683 if [ $# -eq 0 ]; then 1684 printf "\e[38;2;204;0;0mnwat: no names given\e[0m\n" >&2 1685 return 1 1686 fi 1687 1688 local cmd="bat" 1689 # debian linux uses a different name for the `bat` app 1690 if [ -e "/usr/bin/batcat" ]; then 1691 cmd="batcat" 1692 fi 1693 1694 for arg in "$@"; do 1695 [ "${gap}" -gt 0 ] && printf "\n" 1696 gap=1 1697 printf "\e[48;2;218;218;218m%-80s\e[0m\n" "${arg}" 1698 # printf "\e[7m%-80s\e[0m\n" "${arg}" 1699 1700 while alias "${arg}" > /dev/null 2> /dev/null; do 1701 arg="$(alias "${arg}" | sed -E "s-^[^=]+=['\"](.+)['\"]\$-\\1-")" 1702 done 1703 1704 if type "${arg}" > /dev/null 2> /dev/null; then 1705 type "${arg}" | awk 'NR == 1 && / is a function$/ { next } 1' | 1706 "$cmd" -l sh --style=plain --theme='Monokai Extended Light' \ 1707 --wrap=never --color=always | 1708 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' 1709 else 1710 printf "\e[38;2;204;0;0m%s not found\e[0m\n" "${arg}" 1711 fi 1712 done | less -JMKiCRS 1713 } 1714 1715 # Nice Weather Forecast gets weather forecasts, using ANSI styles and almost 1716 # filling the terminal's current width 1717 alias nwf=weather 1718 1719 # Nice Zoom Json, using my tools `zj`, and `nj` 1720 nzj() { zj "$@" | nj; } 1721 1722 # Plain text, by ignoring ANSI terminal styling 1723 alias p=plain 1724 1725 # Print Awk expression 1726 pa() { awk "BEGIN { print ${1:-0}; exit }"; } 1727 1728 # Paragraph AWK runs `awk` in block/paragraph/multiline input-mode 1729 pawk() { stdbuf -oL awk -F='' -v RS='' "$@"; } 1730 1731 # Plain `fd` 1732 pfd() { fd --color=never "$@"; } 1733 1734 # pick lines, using all the 1-based line-numbers given 1735 picklines() { 1736 awk ' 1737 BEGIN { m = ARGC - 1; if (ARGC == 1) exit 0 } 1738 BEGIN { for (i = 1; i <= m; i++) { p[i] = ARGV[i]; delete ARGV[i] } } 1739 { l[++n] = $0 } 1740 END { 1741 for (i = 1; i <= m; i++) { 1742 j = p[i] 1743 if (j < 0) j += NR + 1 1744 if (0 < j && j <= NR) print l[j] 1745 } 1746 } 1747 ' "$@" 1748 } 1749 1750 # Plain Interactive Grep 1751 pig() { ugrep --color=never -Q -E "$@"; } 1752 1753 # make text plain, by ignoring ANSI terminal styling 1754 plain() { awk '{ gsub(/\x1b\[[0-9;]*[A-Za-z]/, ""); print; fflush() }' "$@"; } 1755 1756 # play audio/video media 1757 play() { mpv "${@:--}"; } 1758 1759 # Print Python result 1760 pp() { python -c "print($1)"; } 1761 1762 # PRecede (input) ECHO, prepends a first line to stdin lines 1763 precho() { echo "$@" && cat /dev/stdin; } 1764 1765 # PREcede (input) MEMO, prepends a first highlighted line to stdin lines 1766 prememo() { 1767 awk ' 1768 BEGIN { 1769 if (ARGC > 1) printf "\x1b[7m" 1770 for (i = 1; i < ARGC; i++) { 1771 if (i > 1) printf " " 1772 printf "%s", ARGV[i] 1773 delete ARGV[i] 1774 } 1775 if (ARGC > 1) printf "\x1b[0m\n" 1776 fflush() 1777 } 1778 { print; fflush() } 1779 ' "$@" 1780 } 1781 1782 # start by joining all arguments given as a tab-separated-items line of output, 1783 # followed by all lines from stdin verbatim 1784 alias pretsv=begintsv 1785 1786 # Plain Recursive Interactive Grep 1787 prig() { ugrep --color=never -r -Q -E "$@"; } 1788 1789 # show/list all current processes 1790 processes() { 1791 local res 1792 res="$(ps aux)" 1793 echo "${res}" | awk '!/ps aux$/' | sed -E \ 1794 -e 's- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1' \ 1795 -e 's- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1' 1796 } 1797 1798 # Play Youtube Audio 1799 alias pya=yap 1800 1801 # Quiet ignores stderr, without any ugly keyboard-dancing 1802 q() { "$@" 2> /dev/null; } 1803 1804 # Quiet MPV 1805 qmpv() { mpv --quiet "${@:--}"; } 1806 1807 # ignore stderr, without any ugly keyboard-dancing 1808 quiet() { "$@" 2> /dev/null; } 1809 1810 # Reset the screen, which empties it and resets the current style 1811 r() { reset; } 1812 1813 # keep only lines between the 2 line numbers given, inclusively 1814 rangelines() { 1815 { [ "$#" -eq 2 ] || [ "$#" -eq 3 ]; } && [ "${1}" -le "${2}" ] && 1816 { tail -n +"${1:-1}" "${3:--}" | head -n "$(("${2}" - "${1}" + 1))"; } 1817 } 1818 1819 # RANdom MANual page 1820 ranman() { 1821 find "/usr/share/man/man${1:-1}" -type f | shuf -n 1 | xargs basename | 1822 sed 's-\.gz$--' | xargs man 1823 } 1824 1825 # Red AWK styles lines satisfying an AWK condition/expression red, keeping all 1826 # other lines the same 1827 rawk() { 1828 local cond="${1:-1}" 1829 [ $# -gt 0 ] && shift 1830 1831 awk ' 1832 { low = lower = tolower($0) } 1833 1834 '"${cond}"' { 1835 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;204;0;0m") 1836 printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0; fflush() 1837 next 1838 } 1839 1840 { print; fflush() } 1841 ' "$@" 1842 } 1843 1844 # play a ready-phone-line sound lasting the number of seconds given, or for 1 1845 # second by default; uses my tool `waveout` 1846 ready() { 1847 local f='0.5 * sin(350*tau*t) + 0.5 * sin(450*tau*t)' 1848 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1849 } 1850 1851 # reflow/trim lines of prose (text) to improve its legibility: it's especially 1852 # useful when the text is pasted from web-pages being viewed in reader mode 1853 reprose() { 1854 local w="${1:-80}" 1855 [ $# -gt 0 ] && shift 1856 awk ' 1857 FNR == 1 && NR > 1 { print "" } 1858 { gsub(/\r$/, ""); print; fflush() } 1859 ' "$@" | fold -s -w "$w" | sed -u -E 's- *\r?$--' 1860 } 1861 1862 # Run In Folder 1863 rif() { 1864 local code 1865 pushd "${1:-.}" > /dev/null || return 1866 [ $# -gt 0 ] && shift 1867 "$@" 1868 code=$? 1869 popd > /dev/null || return "${code}" 1870 return "${code}" 1871 } 1872 1873 # play a ringtone-style sound lasting the number of seconds given, or for 1 1874 # second by default; uses my tool `waveout` 1875 ringtone() { 1876 local f='sin(2048 * tau * t) * exp(-50 * (t%0.1))' 1877 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1878 } 1879 1880 # Read-Only Editor 1881 roe() { micro -readonly true "$@"; } 1882 1883 # Read-Only Micro (text editor) 1884 rom() { micro -readonly true "$@"; } 1885 1886 # show a RULER-like width-measuring line 1887 ruler() { 1888 [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed -E \ 1889 's- {10}-····╵····│-g; s- -·-g; s-·····-····╵-' 1890 } 1891 1892 # voice-synthesize plain-text; only works on the windows subsystem for linux 1893 # say() { 1894 # awk '{ print; fflush() }' "$@" | powershell.exe -noprofile -command ' 1895 # Add-Type -AssemblyName System.Speech 1896 # $syn = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer 1897 # foreach ($line in $Input) { $syn.Speak($line) } 1898 # ' | cat 1899 # } 1900 1901 # SystemCTL; `sysctl` is already taken for a separate/unrelated app 1902 sctl() { systemctl "$@"; } 1903 1904 # Silent CURL spares you the progress bar, but still tells you about errors 1905 scurl() { curl --show-error -s "$@"; } 1906 1907 # show a unique-looking SEParator line; useful to run between commands 1908 # which output walls of text 1909 sep() { 1910 [ "${1:-80}" -gt 0 ] && 1911 printf "\e[48;2;218;218;218m%${1:-80}s\e[0m\n" "" | sed 's- -·-g' 1912 } 1913 1914 # webSERVE files in a folder as localhost, using the port number given, or 1915 # port 8080 by default 1916 serve() { 1917 printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 1918 python3 -m http.server "${1:-8080}" -d "${2:-.}" 1919 } 1920 1921 # SET DIFFerence sorts its 2 inputs, then finds lines not in the 2nd input 1922 setdiff() { 1923 # comm -23 <(sort "$1") <(sort "$2") 1924 # dash doesn't support the process-sub syntax 1925 (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1926 } 1927 1928 # SET INtersection, sorts its 2 inputs, then finds common lines 1929 setin() { 1930 # comm -12 <(sort "$1") <(sort "$2") 1931 # dash doesn't support the process-sub syntax 1932 (sort "$1" | (sort "$2" | (comm -12 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1933 } 1934 1935 # SET SUBtraction sorts its 2 inputs, then finds lines not in the 2nd input 1936 setsub() { 1937 # comm -23 <(sort "$1") <(sort "$2") 1938 # dash doesn't support the process-sub syntax 1939 (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1940 } 1941 1942 # Show Files (and folders), coloring folders and links; uses my tool `nn` 1943 sf() { 1944 local arg 1945 local gap=0 1946 local options='-JMKiCRS' 1947 1948 if [ $# -le 1 ]; then 1949 options='--header=1 -JMKiCRS' 1950 fi 1951 1952 for arg in "${@:-.}"; do 1953 [ "${gap}" -gt 0 ] && printf "\n" 1954 printf "\e[7m%s\e[0m\n\n" "$(realpath "${arg}")" 1955 gap=1 1956 1957 ls -al --file-type --color=never --time-style iso "${arg}" | awk ' 1958 BEGIN { 1959 drep = "\x1b[38;2;0;135;255m\x1b[48;2;228;228;228m&\x1b[0m" 1960 lrep = "\x1b[38;2;0;135;95m\x1b[48;2;228;228;228m&\x1b[0m" 1961 } 1962 1963 NR < 4 { next } 1964 (NR - 3) % 5 == 1 && (NR - 3) > 1 { print "" } 1965 1966 { 1967 gsub(/^(d[rwx-]+)/, drep) 1968 gsub(/^(l[rwx-]+)/, lrep) 1969 printf "%6d %s\n", NR - 3, $0; fflush() 1970 } 1971 ' 1972 done | nn --gray | less "${options}" 1973 } 1974 1975 # Show File Sizes, using my tools `nn` and `cext` 1976 sfs() { 1977 # turn arg-list into single-item lines 1978 printf "%s\x00" "$@" | 1979 # calculate file-sizes, and reverse-sort results 1980 xargs -0 wc -c | sort -rn | 1981 # add/realign fields to improve legibility 1982 awk ' 1983 # start output with a header-like line, and add a MiB field 1984 BEGIN { printf "%6s %10s %8s name\n", "n", "bytes", "MiB"; fflush() } 1985 # make table breathe with empty lines, so tall outputs are readable 1986 (NR - 1) % 5 == 1 && NR > 1 { print "" } 1987 # emit regular output lines 1988 { 1989 printf "%6d %10d %8.2f ", NR - 1, $1, $1 / 1048576 1990 # first field is likely space-padded 1991 gsub(/^ */, "") 1992 # slice line after the first field, as filepaths can have spaces 1993 $0 = substr($0, length($1) + 1) 1994 # first field is likely space-padded 1995 gsub(/^ /, "") 1996 printf "%s\n", $0; fflush() 1997 } 1998 ' | 1999 # make zeros in the MiB field stand out with a special color 2000 awk ' 2001 { 2002 gsub(/ 00*\.00* /, "\x1b[38;2;135;135;175m&\x1b[0m") 2003 print; fflush() 2004 } 2005 ' | 2006 # make numbers nice, alternating styles along 3-digit groups 2007 nn --gray | 2008 # color-code file extensions 2009 cext | 2010 # make result interactively browsable 2011 less -JMKiCRS 2012 } 2013 2014 # SHOW a command, then RUN it 2015 showrun() { printf "\e[7m%s\e[0m\n" "$*"; "$@"; } 2016 2017 # SHell-QUOTE each line from the input(s): this is useful to make lines of 2018 # single-filepaths compatible with `xargs`, since standard shell settings 2019 # get in the way of filepaths with spaces and other special symbols in them 2020 shquote() { 2021 awk ' 2022 { 2023 s = $0 2024 gsub(/\r$/, "", s) 2025 gsub(/\\/, "\\\\", s) 2026 gsub(/"/, "\\\"", s) 2027 gsub(/`/, "\\`", s) 2028 gsub(/\$/, "\\$", s) 2029 printf "\"%s\"\n", s; fflush() 2030 } 2031 ' "$@" 2032 } 2033 2034 # skip the first n lines, or the 1st line by default 2035 skip() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } 2036 2037 # skip the last n lines, or the last line by default 2038 skiplast() { head -n -"${1:-1}" "${2:--}"; } 2039 2040 # SLOW/delay lines from the standard-input, waiting the number of seconds 2041 # given for each line, or waiting 1 second by default 2042 slow() { 2043 local seconds="${1:-1}" 2044 ( 2045 IFS="$(printf "\n")" 2046 while read -r line; do 2047 sleep "${seconds}" 2048 printf "%s\n" "${line}" 2049 done 2050 ) 2051 } 2052 2053 # Show Latest Podcasts, using my tools `podfeed` and `si` 2054 slp() { 2055 local title 2056 title="Latest Podcast Episodes as of $(date +'%F %T')" 2057 podfeed -title "${title}" "$@" | si 2058 } 2059 2060 # recursively find all files with fewer bytes than the number given 2061 smallfiles() { 2062 local n 2063 n="$(echo "${1:-4097}" | sed -E 's-_--g; s-\.[0-9]+$--')" 2064 [ $# -gt 0 ] && shift 2065 2066 local arg 2067 for arg in "${@:-.}"; do 2068 if [ ! -d "${arg}" ]; then 2069 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 2070 return 1 2071 fi 2072 stdbuf -oL find "${arg}" -type f -size -"$n"c 2073 done 2074 } 2075 2076 # Stdbuf Output Line-buffered 2077 sol() { stdbuf -oL "$@"; } 2078 2079 # emit the first line as is, sorting all lines after that, using the 2080 # `sort` command, passing all/any arguments/options to it 2081 sortrest() { 2082 awk -v sort="sort $*" ' 2083 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2084 { gsub(/\r$/, "") } 2085 NR == 1 { print; fflush() } 2086 NR > 1 { print | sort } 2087 ' 2088 } 2089 2090 # SORt Tab-Separated Values: emit the first line as is, sorting all lines after 2091 # that, using the `sort` command in TSV (tab-separated values) mode, passing 2092 # all/any arguments/options to it 2093 sortsv() { 2094 awk -v sort="sort -t \"$(printf '\t')\" $*" ' 2095 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2096 { gsub(/\r$/, "") } 2097 NR == 1 { print; fflush() } 2098 NR > 1 { print | sort } 2099 ' 2100 } 2101 2102 # emit a line with the number of spaces given in it 2103 spaces() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" ""; } 2104 2105 # ignore leading spaces, trailing spaces, even runs of multiple spaces 2106 # in the middle of lines, as well as trailing carriage returns 2107 squeeze() { 2108 awk ' 2109 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2110 { 2111 gsub(/^ +| *\r?$/, "") 2112 gsub(/ *\t */, "\t") 2113 gsub(/ +/, " ") 2114 print; fflush() 2115 } 2116 ' "$@" 2117 } 2118 2119 # SQUeeze and stOMP, by ignoring leading spaces, trailing spaces, even runs 2120 # of multiple spaces in the middle of lines, as well as trailing carriage 2121 # returns, while also turning runs of empty lines into single empty lines, 2122 # and ignoring leading/trailing empty lines, effectively also `squeezing` 2123 # lines vertically 2124 squomp() { 2125 awk ' 2126 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2127 /^\r?$/ { empty = 1; next } 2128 empty { if (n > 0) print ""; empty = 0 } 2129 { 2130 gsub(/^ +| *\r?$/, "") 2131 gsub(/ *\t */, "\t") 2132 gsub(/ +/, " ") 2133 print; fflush() 2134 n++ 2135 } 2136 ' "$@" 2137 } 2138 2139 # turn runs of empty lines into single empty lines, effectively squeezing 2140 # paragraphs vertically, so to speak; runs of empty lines both at the start 2141 # and at the end are ignored 2142 stomp() { 2143 awk ' 2144 /^\r?$/ { empty = 1; next } 2145 empty { if (n > 0) print ""; empty = 0 } 2146 { print; fflush(); n++ } 2147 ' "$@" 2148 } 2149 2150 # turn SUDo privileges OFF right away: arguments also cause `sudo` to run with 2151 # what's given, before relinquishing existing privileges 2152 sudoff() { 2153 local code=0 2154 if [ $# -gt 0 ]; then 2155 sudo "$@" 2156 code=$? 2157 fi 2158 sudo -k 2159 return "${code}" 2160 } 2161 2162 # append a final Tab-Separated-Values line with the sums of all columns from 2163 # the input table(s) given; items from first lines aren't counted/added 2164 sumtsv() { 2165 awk -F "\t" ' 2166 # FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2167 2168 { 2169 gsub(/\r$/, "") 2170 print; fflush() 2171 if (width < NF) width = NF 2172 } 2173 2174 FNR > 1 { for (i = 1; i <= NF; i++) sums[i] += $i + 0 } 2175 2176 END { 2177 for (i = 1; i <= width; i++) { 2178 if (i > 1) printf "\t" 2179 printf "%s", sums[i] "" 2180 } 2181 if (width > 0) printf "\n" 2182 } 2183 ' "$@" 2184 } 2185 2186 # Time the command given 2187 alias t=time 2188 2189 # show a reverse-sorted tally of all lines read, where ties are sorted 2190 # alphabetically 2191 tally() { 2192 awk -v sortcmd="sort -t \"$(printf '\t')\" -rnk2 -k1d" ' 2193 # reassure users by instantly showing the header 2194 BEGIN { print "value\ttally"; fflush() } 2195 { gsub(/\r$/, ""); t[$0]++ } 2196 END { for (k in t) { printf("%s\t%d\n", k, t[k]) | sortcmd } } 2197 ' "$@" 2198 } 2199 2200 # Tab AWK: TSV-specific I/O settings for `awk` 2201 tawk() { stdbuf -oL awk -F "\t" -v OFS="\t" "$@"; } 2202 2203 # Titled conCATenate Lines highlights each filename, before emitting its lines 2204 tcatl() { 2205 awk ' 2206 FNR == 1 { printf "\x1b[7m%s\x1b[0m\n", FILENAME; fflush() } 2207 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2208 { gsub(/\r$/, ""); print; fflush() } 2209 ' "$@" 2210 } 2211 2212 # turn documents into plain-TEXT 2213 text() { pandoc -s -t plain "$@"; } 2214 2215 # run `top` without showing any of its output after quitting it 2216 tip() { tput smcup; top "$@"; tput rmcup; } 2217 2218 # show current date in a specifc format 2219 today() { date +'%Y-%m-%d %a %b %d'; } 2220 2221 # get the first n lines, or 1 by default 2222 toline() { head -n "${1:-1}" "${2:--}"; } 2223 2224 # lowercase all ASCII symbols 2225 tolower() { awk '{ print tolower($0); fflush() }' "$@"; } 2226 2227 # play a tone/sine-wave sound lasting the number of seconds given, or for 1 2228 # second by default: after the optional duration, the next optional arguments 2229 # are the volume and the tone-frequency; uses my tool `waveout` 2230 tone() { 2231 waveout "${1:-1}" "${2:-1} * sin(${3:-440} * 2 * pi * t)" | 2232 mpv --really-quiet - 2233 } 2234 2235 # get the processes currently using the most cpu 2236 topcpu() { 2237 local n="${1:-10}" 2238 [ "$n" -gt 0 ] && ps aux | awk ' 2239 NR == 1 { print; fflush() } 2240 NR > 1 { print | "sort -rnk3" } 2241 ' | head -n "$(("$n" + 1))" 2242 } 2243 2244 # show all files directly in the folder given, without looking any deeper 2245 topfiles() { 2246 local arg 2247 for arg in "${@:-.}"; do 2248 if [ ! -d "${arg}" ]; then 2249 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 2250 return 1 2251 fi 2252 stdbuf -oL find "${arg}" -maxdepth 1 -type f 2253 done 2254 } 2255 2256 # show all folders directly in the folder given, without looking any deeper 2257 topfolders() { 2258 local arg 2259 for arg in "${@:-.}"; do 2260 if [ ! -d "${arg}" ]; then 2261 printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 2262 return 1 2263 fi 2264 stdbuf -oL find "${arg}" -maxdepth 1 -type d | 2265 awk '!/^\.$/ { print; fflush() }' 2266 done 2267 } 2268 2269 # get the processes currently using the most memory 2270 topmemory() { 2271 local n="${1:-10}" 2272 [ "$n" -gt 0 ] && ps aux | awk ' 2273 NR == 1 { print; fflush() } 2274 NR > 1 { print | "sort -rnk6" } 2275 ' | head -n "$(("$n" + 1))" 2276 } 2277 2278 # transpose (switch) rows and columns from tables 2279 transpose() { 2280 awk ' 2281 { gsub(/\r$/, "") } 2282 2283 FNR == 1 { FS = ($0 ~ /\t/) ? "\t" : " "; $0 = $0 } 2284 2285 { 2286 for (i = 1; i <= NF; i++) lines[i][NR] = $i 2287 if (maxitems < NF) maxitems = NF 2288 } 2289 2290 END { 2291 for (j = 1; j <= maxitems; j++) { 2292 for (i = 1; i <= NR; i++) { 2293 if (i > 1) printf "\t" 2294 printf "%s", lines[j][i] 2295 } 2296 printf "\n" 2297 } 2298 } 2299 ' "$@" 2300 } 2301 2302 # ignore leading/trailing spaces, as well as trailing carriage returns 2303 trim() { awk '{ gsub(/^ +| *\r?$/, ""); print; fflush() }' "$@"; } 2304 2305 # try running a command, emitting an explicit message to standard-error 2306 # if the command given fails 2307 try() { 2308 local code 2309 "$@" 2310 code=$? 2311 2312 if [ "${code}" -ne 0 ]; then 2313 printf "\n\e[38;2;204;0;0m%s \e[48;2;204;0;0m\e[38;2;255;255;255m failed with error code %d \e[0m\n" "$*" "${code}" 2314 fi >&2 2315 return "${code}" 2316 } 2317 2318 # Time Verbosely the command given 2319 tv() { /usr/bin/time -v "$@"; } 2320 2321 # Unique via AWK, avoids lines duplicating the expression given 2322 uawk() { 2323 local code="${1:-\$0}" 2324 [ $# -gt 0 ] && shift 2325 awk '!c['"${code}"']++ { print; fflush() }' "$@" 2326 } 2327 2328 # Underline Every 5 lines: make groups of 5 lines stand out by underlining 2329 # the last line of each such group 2330 ue5() { 2331 awk ' 2332 NR % 5 == 0 && NR != 1 { 2333 gsub(/\x1b\[0m/, "\x1b[0m\x1b[4m") 2334 printf("\x1b[4m%s\x1b[0m\n", $0); fflush() 2335 next 2336 } 2337 { print; fflush() } 2338 ' "$@" 2339 } 2340 2341 # deduplicate lines, keeping them in their original order 2342 unique() { awk '!c[$0]++ { print; fflush() }' "$@"; } 2343 2344 # skip the first/leading n bytes 2345 unleaded() { tail -c +$(("$1" + 1)) "${2:--}"; } 2346 2347 # go UP n folders, or go up 1 folder by default 2348 up() { 2349 if [ "${1:-1}" -le 0 ]; then 2350 cd . 2351 else 2352 cd "$(printf "%${1:-1}s" "" | sed 's- -../-g')" || return $? 2353 fi 2354 } 2355 2356 # convert United States Dollars into CAnadian Dollars, using the latest 2357 # official exchange rates from the bank of canada; during weekends, the 2358 # latest rate may be from a few days ago; the default amount of usd to 2359 # convert is 1, when not given 2360 usd2cad() { 2361 local site='https://www.bankofcanada.ca/valet/observations/group' 2362 local csv_rates="${site}/FX_RATES_DAILY/csv" 2363 local url 2364 url="${csv_rates}?start_date=$(date -d '3 days ago' +'%Y-%m-%d')" 2365 curl -s "${url}" | awk -F, -v amount="$(echo "${1:-1}" | sed 's-_--g')" ' 2366 /USD/ { for (i = 1; i <= NF; i++) if($i ~ /USD/) j = i } 2367 END { gsub(/"/, "", $j); if (j != 0) printf "%.2f\n", amount * $j }' 2368 } 2369 2370 # View with `less` 2371 v() { less -JMKiCRS "$@"; } 2372 2373 # run a command, showing its success/failure right after 2374 verdict() { 2375 local code 2376 "$@" 2377 code=$? 2378 2379 if [ "${code}" -eq 0 ]; then 2380 printf "\n\e[38;2;0;135;95m%s \e[48;2;0;135;95m\e[38;2;255;255;255m succeeded \e[0m\n" "$*" 2381 else 2382 printf "\n\e[38;2;204;0;0m%s \e[48;2;204;0;0m\e[38;2;255;255;255m failed with error code %d \e[0m\n" "$*" "${code}" 2383 fi >&2 2384 return "${code}" 2385 } 2386 2387 # run `cppcheck` with even stricter options 2388 vetc() { 2389 cppcheck --enable=portability --enable=style --check-level=exhaustive "$@" 2390 } 2391 2392 # run `cppcheck` with even stricter options, also checking for c89 compliance 2393 vetc89() { 2394 cppcheck --enable=portability --enable=style \ 2395 --check-level=exhaustive --std=c89 "$@" 2396 } 2397 2398 # run `cppcheck` with even stricter options 2399 vetcpp() { 2400 cppcheck --enable=portability --enable=style --check-level=exhaustive "$@" 2401 } 2402 2403 # check shell scripts for common gotchas, avoiding complaints about using 2404 # the `local` keyword, which is widely supported in practice 2405 vetshell() { shellcheck -e 3043 "$@"; } 2406 2407 # View with Header runs `less` without line numbers, with ANSI styles, no 2408 # line-wraps, and using the first n lines as a sticky-header (1 by default), 2409 # so they always show on top 2410 vh() { 2411 local n="${1:-1}" 2412 [ $# -gt 0 ] && shift 2413 less --header="$n" -JMKiCRS "$@" 2414 } 2415 2416 vh1() { less --header=1 -JMKiCRS "$@"; } 2417 2418 vh2() { less --header=2 -JMKiCRS "$@"; } 2419 2420 # View Nice Hexadecimals; uses my tool `nh` 2421 vnh() { nh "$@" | less -JMKiCRS; } 2422 2423 # View Nice Json / Very Nice Json; uses my tools `nj` and `nn` 2424 vnj() { nj "$@" | less -JMKiCRS; } 2425 2426 # View Very Nice Json with Nice Numbers; uses my tools `nj` and `nn` 2427 vnjnn() { nj "$@" | nn --gray | less -JMKiCRS; } 2428 2429 # View Nice Numbers; uses my tool `nn` 2430 vnn() { nn "${@:---gray}" | less -JMKiCRS; } 2431 2432 # View Nice Table / Very Nice Table; uses my tools `nt` and `nn` 2433 vnt() { 2434 awk '{ gsub(/\r$/, ""); printf "%d\t%s\n", NR - 1, $0; fflush() }' "$@" | 2435 nt | nn --gray | 2436 awk '(NR - 1) % 5 == 1 && NR > 1 { print "" } { print; fflush() }' | 2437 less -JMKiCRS 2438 } 2439 2440 # View Text, turning documents into plain-text if needed 2441 vt() { 2442 local arg 2443 local gap=0 2444 local options='-JMKiCRS' 2445 2446 if [ $# -eq 1 ]; then 2447 options='--header=1 -JMKiCRS' 2448 fi 2449 2450 if [ $# -eq 0 ]; then 2451 pandoc -s -t plain - 2>&1 | less -JMKiCRS 2452 else 2453 for arg in "$@"; do 2454 [ "${gap}" -eq 1 ] && printf "\n" 2455 gap=1 2456 printf "\e[7m%-80s\e[0m\n" "${arg}" 2457 pandoc -s -t plain "${arg}" 2>&1 | awk 1 2458 done | less "${options}" 2459 fi 2460 } 2461 2462 # What Are These (?) shows what the names given to it are/do 2463 wat() { 2464 local arg 2465 local gap=0 2466 2467 if [ $# -eq 0 ]; then 2468 printf "\e[38;2;204;0;0mwat: no names given\e[0m\n" >&2 2469 return 1 2470 fi 2471 2472 for arg in "$@"; do 2473 [ "${gap}" -gt 0 ] && printf "\n" 2474 gap=1 2475 # printf "\e[48;2;218;218;218m%-80s\e[0m\n" "${arg}" 2476 printf "\e[7m%-80s\e[0m\n" "${arg}" 2477 2478 while alias "${arg}" > /dev/null 2> /dev/null; do 2479 arg="$(alias "${arg}" | sed -E "s-^[^=]+=['\"](.+)['\"]\$-\\1-")" 2480 done 2481 2482 if type "${arg}" > /dev/null 2> /dev/null; then 2483 type "${arg}" | awk 'NR == 1 && / is a function$/ { next } 1' 2484 else 2485 printf "\e[38;2;204;0;0m%s not found\e[0m\n" "${arg}" 2486 fi 2487 done | less -JMKiCRS 2488 } 2489 2490 # Word-Count TSV, runs the `wc` app using all stats, emitting tab-separated 2491 # lines instead 2492 wctsv() { 2493 printf "file\tbytes\tlines\tcharacters\twords\tlongest\n" 2494 stdbuf -oL wc -cmlLw "${@:--}" | sed -E -u \ 2495 's-^ *([^ ]*) *([^ ]*) *([^ ]*) *([^ ]*) *([^ ]*) *([^\r]*)$-\6\t\4\t\1\t\3\t\2\t\5-' | 2496 awk ' 2497 NR > 1 { print prev; fflush() } 2498 { prev = $0 } 2499 END { if (NR == 1 || !/^total\t/) print } 2500 ' 2501 } 2502 2503 # get weather forecasts, almost filling the terminal's current width 2504 weather() { 2505 local gap=0 2506 local width="$(($(tput cols) - 2))" 2507 local place 2508 2509 for place in "$@"; do 2510 [ "${gap}" -gt 0 ] && printf "\n" 2511 gap=1 2512 2513 # printf "\e[48;2;218;218;218m%-${width}s\e[0m\n" "${place}" 2514 printf "\e[7m%-${width}s\e[0m\n" "${place}" 2515 2516 printf "%s~%s\r\n\r\n" "${place}" "${width}" | 2517 curl --show-error -s telnet://graph.no:79 | 2518 sed -u -E \ 2519 -e 's/ *\r?$//' \ 2520 -e '/^\[/d' \ 2521 -e 's/^ *-= *([^=]+) +=- *$/\1\n/' \ 2522 -e 's/-/\x1b[38;2;196;160;0m●\x1b[0m/g' \ 2523 -e 's/^( +)\x1b\[38;2;196;160;0m●\x1b\[0m/\1-/g' \ 2524 -e 's/\|/\x1b[38;2;52;101;164m█\x1b[0m/g' \ 2525 -e 's/#/\x1b[38;2;218;218;218m█\x1b[0m/g' \ 2526 -e 's/([=\^][=\^]*)/\x1b[38;2;164;164;164m\1\x1b[0m/g' \ 2527 -e 's/\*/○/g' \ 2528 -e 's/_/\x1b[48;2;216;200;0m_\x1b[0m/g' \ 2529 -e 's/([0-9][0-9]\/[0-9][0-9])/\x1b[7m\1\x1b[0m/g' | awk 1 2530 done | less -JMKiCRS 2531 } 2532 2533 # find all WEB/hyperLINKS (https:// and http://) in the input text 2534 weblinks() { 2535 awk ' 2536 BEGIN { e = "https?://[A-Za-z0-9+_.:%-]+(/[A-Za-z0-9+_.%/,#?&=-]*)*" } 2537 { 2538 # match all links in the current line 2539 for (s = $0; match(s, e); s = substr(s, RSTART + RLENGTH)) { 2540 print substr(s, RSTART, RLENGTH); fflush() 2541 } 2542 } 2543 ' "$@" 2544 } 2545 2546 # recursively find all files with trailing spaces/CRs 2547 wheretrails() { rg -c --line-buffered '[ \r]+$' "${@:-.}"; } 2548 2549 # recursively find all files with trailing spaces/CRs 2550 whichtrails() { rg -c --line-buffered '[ \r]+$' "${@:-.}"; } 2551 2552 # run `xargs`, using zero/null bytes as the extra-arguments terminator 2553 x0() { xargs -0 "$@"; } 2554 2555 # run `xargs`, using whole lines as extra arguments 2556 xl() { 2557 awk -v ORS='\000' ' 2558 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2559 { gsub(/\r$/, ""); print; fflush() } 2560 ' | xargs -0 "$@" 2561 } 2562 2563 # Youtube AAC audio 2564 ya() { yt-dlp -x --audio-format best "$@"; } 2565 2566 # Youtube AAC audio 2567 yaac() { yt-dlp -f 140 "$@"; } 2568 2569 # Youtube MP4 video 2570 ymp4() { yt-dlp -f 22 "$@"; } 2571 2572 # Youtube Audio Player 2573 # yap() { 2574 # local url 2575 # # some youtube URIs end with extra playlist/tracker parameters 2576 # url="$(echo "$1" | sed 's-&.*--')" 2577 # mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" 2578 # } 2579 2580 # Youtube Audio Player 2581 yap() { 2582 local url 2583 # some youtube URIs end with extra playlist/tracker parameters 2584 url="$(echo "$1" | sed 's-&.*--')" 2585 mpv "$(yt-dlp -x --audio-format best --get-url "${url}" 2> /dev/null)" 2586 } 2587 2588 # Youtube Download AAC audio 2589 ydaac() { yt-dlp -f 140 "$@"; } 2590 2591 # Youtube Download MP4 video 2592 ydmp4() { yt-dlp -f 22 "$@"; } 2593 2594 # show a calendar for the current YEAR, or for the year given 2595 year() { 2596 { 2597 # show the current date/time center-aligned 2598 printf "%20s\e[38;2;78;154;6m%s\e[0m \e[38;2;52;101;164m%s\e[0m\n\n" \ 2599 "" "$(date +'%a %b %d %Y')" "$(date +%T)" 2600 # debian linux has a different `cal` app which highlights the day 2601 if [ -e "/usr/bin/ncal" ]; then 2602 # fix debian/ncal's weird way to highlight the current day 2603 ncal -C -y "$@" | sed -E 's/_\x08(.)/\x1b[7m\1\x1b[0m/g' 2604 else 2605 cal -y "$@" 2606 fi 2607 } | less -JMKiCRS 2608 } 2609 2610 # show the current date in the YYYY-MM-DD format 2611 ymd() { date +'%Y-%m-%d'; } 2612 2613 # YouTube DownLoad Plus 2614 ytdlp() { yt-dlp "$@"; } 2615 2616 # YouTube Url 2617 ytu() { 2618 local url 2619 # some youtube URIs end with extra playlist/tracker parameters 2620 url="$(echo "$1" | sed 's-&.*--')" 2621 [ $# -gt 0 ] && shift 2622 yt-dlp "$@" --get-url "${url}" 2623 } 2624 2625 # underline every few lines: make groups of 5 lines (by default) stand out by 2626 # underlining the last line of each 2627 alias zebra=ue5 2628 2629 # Zero/null-terminate LINES ends each stdin line with a null byte, instead of 2630 # a line-feed byte 2631 zlines() { 2632 awk -v ORS='\000' ' 2633 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2634 { gsub(/\r$/, ""); print; fflush() } 2635 ' "$@" 2636 }