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