File: clam.sh 1 #!/bin/sh 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2024 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. It even seems to work with 51 # 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 '/^# +clam/, /^$/ { gsub(/^# ?/, ""); print }' "$0" 59 exit 0 60 ;; 61 esac 62 63 # dash doesn't support regex-matching syntax, forcing to use case statements 64 case "$0" in 65 -bash|-dash|-sh|bash|dash|sh) 66 # script is being sourced with bash or dash, which is good 67 :;; 68 *) 69 case "$ZSH_EVAL_CONTEXT" in 70 *:file) 71 # script is being sourced with zsh, which is good 72 :;; 73 *) 74 # script is being run normally, which is a waste of time 75 printf "\e[48;5;228m\e[30mDon't run this script, source it instead: to do that,\e[0m\n" 76 printf "\e[48;5;228m\e[30mrun 'source clam' or '. clam' (no quotes either way).\e[0m\n" 77 # failing during shell-startup may deny shell access, so don't 78 # exit with a non-0 error code 79 ;; 80 esac 81 ;; 82 esac 83 84 85 # use a simple shell prompt; this setting works on both bash and zsh 86 # export PS1="\$ " 87 # export PS2="> " 88 89 # use a simple shell prompt, showing the current folder as the tab label; 90 # this setting is bash-specific 91 # export PS1="\[\e]0;\w\a\]$ " 92 # export PS2="> " 93 94 # prevent `less` from saving/remembering commands 95 # export LESSHISTFILE="-" 96 # export LESSSECURE=1 97 98 # prevent the shell from saving/remembering commands across live sessions 99 # unset HISTFILE 100 101 # avoid saving nodejs command-history to file 102 # export NODE_REPL_HISTORY="" 103 # export NODE_REPL_HISTORY_SIZE=0 104 105 # in the future, this may be how you tell `go` to mind its own biz 106 # export GOTELEMETRY=off 107 108 109 # alias all-files='allfiles' 110 # alias all-folders='allfolders' 111 # alias begin-tsv='begintsv' 112 # alias big-files='bigfiles' 113 # alias blow-tabs='blowtabs' 114 # alias but-first='butfirst' 115 # alias but-last='butlast' 116 # alias chop-decs='chopdecs' 117 # alias chop-lf='choplf' 118 # alias first-bytes='firstbytes' 119 # alias first-lines='firstlines' 120 # alias from-line='fromline' 121 # alias last-bytes='lastbytes' 122 # alias last-lines='lastlines' 123 # alias line-up='lineup' 124 # alias pre-tsv='pretsv' 125 # alias range-lines='rangelines' 126 # alias ring-tone='ringtone' 127 # alias show-run='showrun' 128 # alias skip-bytes='skipbytes' 129 # alias skip-lines='skiplines' 130 # alias small-files='smallfiles' 131 # alias sort-rest='sortrest' 132 # alias to-line='toline' 133 # alias to-lower='tolower' 134 # alias to-upper='toupper' 135 # alias top-files='topfiles' 136 # alias top-folders='topfolders' 137 # alias trim-end='trimend' 138 # alias trim-ends='trimends' 139 # alias trim-trail='trimtrail' 140 # alias trim-trails='trimtrails' 141 # alias ts-awk='tsawk' 142 # alias tsv-dedup='tsvdedup' 143 # alias where-trails='wheretrails' 144 # alias which-trails='whichtrails' 145 # alias yd-aac='ydaac' 146 # alias yd-mp4='ydmp4' 147 148 149 # n-Column-layout shortcuts, using my script `sbs` (Side By Side) 150 # 1c() { sbs 1 "$@"; } 151 # 2c() { sbs 2 "$@"; } 152 # 3c() { sbs 3 "$@"; } 153 # 4c() { sbs 4 "$@"; } 154 # 5c() { sbs 5 "$@"; } 155 # 6c() { sbs 6 "$@"; } 156 # 7c() { sbs 7 "$@"; } 157 # 8c() { sbs 8 "$@"; } 158 # 9c() { sbs 9 "$@"; } 159 160 # n-Column-layout shortcuts, using my script `sbs` (Side By Side) 161 c1() { sbs 1 "$@"; } 162 c2() { sbs 2 "$@"; } 163 c3() { sbs 3 "$@"; } 164 c4() { sbs 4 "$@"; } 165 c5() { sbs 5 "$@"; } 166 c6() { sbs 6 "$@"; } 167 c7() { sbs 7 "$@"; } 168 c8() { sbs 8 "$@"; } 169 c9() { sbs 9 "$@"; } 170 171 # shortcuts for my script `ca`, an arbitrary-precision calculator, using 172 # various decimal precisions 173 ca0() { ca "scale=0; $*"; } 174 ca1() { ca "scale=1; $*"; } 175 ca2() { ca "scale=2; $*"; } 176 ca3() { ca "scale=3; $*"; } 177 ca4() { ca "scale=4; $*"; } 178 ca5() { ca "scale=5; $*"; } 179 ca6() { ca "scale=6; $*"; } 180 ca7() { ca "scale=7; $*"; } 181 ca8() { ca "scale=8; $*"; } 182 ca9() { ca "scale=9; $*"; } 183 ca10() { ca "scale=10; $*"; } 184 ca20() { ca "scale=20; $*"; } 185 ca30() { ca "scale=30; $*"; } 186 ca40() { ca "scale=40; $*"; } 187 ca50() { ca "scale=50; $*"; } 188 ca60() { ca "scale=60; $*"; } 189 ca70() { ca "scale=70; $*"; } 190 ca80() { ca "scale=80; $*"; } 191 ca90() { ca "scale=90; $*"; } 192 193 # expand Tabs up to n spaces each 194 t1() { expand -t 1 "$@"; } 195 t2() { expand -t 2 "$@"; } 196 t3() { expand -t 3 "$@"; } 197 t4() { expand -t 4 "$@"; } 198 t5() { expand -t 5 "$@"; } 199 t6() { expand -t 6 "$@"; } 200 t7() { expand -t 7 "$@"; } 201 t8() { expand -t 8 "$@"; } 202 t9() { expand -t 9 "$@"; } 203 204 # expand TABs up to n spaces each 205 tab1() { expand -t 1 "$@"; } 206 tab2() { expand -t 2 "$@"; } 207 tab3() { expand -t 3 "$@"; } 208 tab4() { expand -t 4 "$@"; } 209 tab5() { expand -t 5 "$@"; } 210 tab6() { expand -t 6 "$@"; } 211 tab7() { expand -t 7 "$@"; } 212 tab8() { expand -t 8 "$@"; } 213 tab9() { expand -t 9 "$@"; } 214 215 a() { awk "$@"; } 216 217 # show all files in a folder, digging recursively 218 allfiles() { 219 local arg 220 for arg in "${@:-.}"; do 221 find "${arg}" -type f 222 done 223 } 224 225 # show all folders in a folder, digging recursively 226 allfolders() { 227 local arg 228 for arg in "${@:-.}"; do 229 find "${arg}" -type d | awk 'NR > 1' 230 done 231 } 232 233 # show only the lines which appear on all the inputs given 234 and() { 235 awk ' 236 FNR == 1 { filenum++ } 237 238 { 239 lines[n++] = $0 240 if (last[$0] != filenum) { 241 last[$0] = filenum 242 tally[$0]++ 243 } 244 } 245 246 END { 247 n = ARGC - 1 248 for (i = 1; i <= NR; i++) { 249 l = lines[i] 250 if (tally[l] == n) { 251 tally[l] = -1 252 print l 253 } 254 } 255 }' "$@" 256 } 257 258 # emit each argument given as its own line of output 259 args() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@"; } 260 261 # keep visible ASCII bytes as they are, turning (almost) all other bytes into 262 # ASCII spaces, by default; tabs, line-feeds, carriage-returns, and escapes 263 # are also kept as given, to avoid messing up lines and ANSI-styles 264 # asciify() { tr -c '\11\12\15\33\41-\176' "${1:- }"; } 265 266 # turn UTF-8 into visible pseudo-ASCII, where variants of latin letters become 267 # their basic ASCII counterparts, and where non-ASCII symbols become question 268 # marks, one question mark for each code-point byte 269 asciify() { iconv -f utf-8 -t ascii//translit "$@"; } 270 271 # avoid/ignore lines which match any of the regexes given 272 avoid() { 273 awk ' 274 BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } 275 { for (i in re) if ($0 ~ re[i]) { next } } 276 { print; fflush() }' "${@:-^$}" 277 } 278 279 b() { bc "$@"; } 280 281 # emit a line with a repeating ball-like symbol in it 282 balls() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -●-g'; } 283 284 # show an ansi-styled BANNER-like line 285 # banner() { printf "\e[48;5;253m%s\e[0m\n" "$*"; } 286 287 # show an ansi-styled BANNER-like line 288 banner() { printf "\e[7m%s\e[0m\n" "$*"; } 289 290 # emit a colored bar which can help visually separate different outputs 291 bar() { [ "${1:-80}" -gt 0 ] && printf "\e[48;5;253m%${1:-80}s\e[0m\n" " "; } 292 293 # process Blocks/paragraphs of non-empty lines with AWK 294 bawk() { awk -F='' -v RS='' "$@"; } 295 296 # Background-colored ECHO 297 becho() { printf "\e[48;5;253m%s\e[0m\n" "$*"; } 298 299 # play a repeating and annoying high-pitched beep sound a few times a second, 300 # lasting the number of seconds given, or for 1 second by default; uses my 301 # script `waveout` 302 beeps() { 303 local f='sin(2_000 * tau * t) * (t % 0.5 < 0.0625)' 304 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 305 } 306 307 # start by joining all arguments given as a tab-separated-items line of output, 308 # followed by all lines from stdin verbatim 309 begintsv() { 310 awk 'BEGIN { 311 for (i = 1; i < ARGC; i++) { 312 if (i > 1) { printf "\t" } 313 printf "%s", ARGV[i] 314 delete ARGV[i] 315 } 316 printf "\n" 317 } 318 { print; fflush() }' "$@" 319 } 320 321 # play a repeating synthetic-bell-like sound lasting the number of seconds 322 # given, or for 1 second by default; uses my script `waveout` 323 bell() { 324 local f='sin(880*tau*u) * exp(-10*u)' 325 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 326 } 327 328 # play a repeating sound with synthetic-bells, lasting the number of seconds 329 # given, or for 1 second by default; uses my script `waveout` 330 bells() { 331 local f="sum(sin(880*tau*v)*exp(-10*v) for v in (u, (u-0.25)%1)) / 2" 332 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 333 } 334 335 # Breathe Header: add an empty line after the first one (the header), then 336 # separate groups of 5 lines (by default) with empty lines between them 337 bh() { 338 local n="${1:-5}" 339 [ $# -gt 0 ] && shift 340 awk -v n="$n" ' 341 (NR - 1) % n == 1 && NR > 1 { print "" } 342 { print; fflush() }' "$@" 343 } 344 345 # recursively find all files with at least the number of bytes given 346 bigfiles() { 347 local n 348 n="$(echo "${1}" | sed 's-_--g')" 349 find "${2:-.}" -size "$n"c -o -size +"$n"c 350 } 351 352 # Breathe Lines: separate groups of 5 lines (by default) with empty lines 353 bl() { 354 local n="${1:-5}" 355 [ $# -gt 0 ] && shift 356 awk -v n="$n" ' 357 NR % n == 1 && NR != 1 { print "" } 358 { print; fflush() }' "$@" 359 } 360 361 # process BLocks/paragraphs of non-empty lines with AWK 362 blawk() { awk -F='' -v RS='' "$@"; } 363 364 # Blue LEAK emits/tees input both to stdout and stderr, coloring blue what 365 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` 366 # pipes involving several steps 367 bleak() { 368 awk '{ 369 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 370 printf "\x1b[38;5;26m%s\x1b[0m\n", $0 > "/dev/stderr" 371 print 372 fflush() 373 }' "$@" 374 } 375 376 # emit a line with a repeating block-like symbol in it 377 blocks() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -█-g'; } 378 379 # expand all tabs into runs of spaces 380 blow() { 381 local tabstop="${1:-4}" 382 [ $# -gt 0 ] && shift 383 expand -t "${tabstop}" "$@" 384 } 385 386 # expand all tabs into runs of spaces 387 blowtabs() { 388 local tabstop="${1:-4}" 389 [ $# -gt 0 ] && shift 390 expand -t "${tabstop}" "$@" 391 } 392 393 # BREATHE lines: separate groups of 5 lines (by default) with empty lines 394 breathe() { 395 local n="${1:-5}" 396 [ $# -gt 0 ] && shift 397 awk -v n="$n" ' 398 NR % n == 1 && NR != 1 { print "" } 399 { print; fflush() }' "$@" 400 } 401 402 # show a reverse-sorted tally of all lines read, where ties are sorted 403 # alphabetically, and where trailing bullets are added to quickly make 404 # the tally counts comparable at a glance 405 bully() { 406 printf "value\ttally\tbullets\n" 407 awk ' 408 { tally[$0]++ } 409 410 END { 411 # find the max tally, which is needed to build the bullets-string 412 max = 0 413 for (k in tally) { 414 if (max < tally[k]) max = tally[k] 415 } 416 417 # make enough bullets for all tallies: this loop makes growing the 418 # string a task with complexity O(n * log n), instead of a naive 419 # O(n**2), which can slow-down things when tallies are high enough 420 bullets = "•" 421 for (n = max; n > 1; n /= 2) { 422 bullets = bullets bullets 423 } 424 425 # emit unsorted output lines to the sort cmd, which will emit the 426 # final reverse-sorted tally lines 427 for (k in tally) { 428 s = substr(bullets, 1, tally[k]) 429 printf "%s\t%d\t%s\n", k, tally[k], s 430 } 431 } 432 ' "$@" | sort -t "$(printf "\t")" -rnk2 -k1d 433 } 434 435 # play a busy-phone-line sound lasting the number of seconds given, or for 1 436 # second by default; uses my script `waveout` 437 busy() { 438 # local f='(u < 0.5) * (sin(480*tau * t) + sin(620*tau * t)) / 2' 439 local f='min(1, exp(-90*(u-0.5))) * (sin(480*tau*t) + sin(620*tau*t)) / 2' 440 # local f='(sin(350*tau*t) + sin(450*tau*t)) / 2 * min(1, exp(-90*(u-0.5)))' 441 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 442 } 443 444 # keep all BUT the FIRST (skip) n lines, or skip just the 1st line by default 445 butfirst() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } 446 447 # keep all BUT the LAST n lines, or skip just the last line by default 448 butlast() { head -n -"${1:-1}" "${2:--}"; } 449 450 c() { cat "$@"; } 451 452 # CAlculator with Nice numbers runs my script `ca` and colors results with 453 # my script `nn`, alternating styles to make long numbers easier to read 454 can() { ca "$@" | nn; } 455 456 # conCATenate Lines 457 catl() { 458 awk ' 459 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 460 { print; fflush() }' "$@" | sed -E 's-\r$--' 461 } 462 463 # Csv AWK: CSV-specific input settings for `awk` 464 # cawk() { awk --csv -v OFS="\t" "$@"; } 465 466 # Csv AWK: CSV-specific input settings for `awk` 467 cawk() { awk --csv "$@"; } 468 469 # Colored Go Test on the folder given; uses my script `gbm` 470 cgt() { go test "${1:-.}" 2>&1 | gbm '/^ok/' '/^[-]* ?FAIL/' '/^\?/'; } 471 472 # lookup docs/examples using the curl-friendly website cheat.sh 473 cheat() { 474 local cmd="bat" 475 # debian linux uses a different name for the `bat` app 476 if [ -e "/usr/bin/batcat" ]; then 477 cmd="batcat" 478 fi 479 480 if [ -z "$1" ]; then 481 curl -s cheat.sh/ | 482 awk '1 { gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, ""); print }' 483 return 484 fi 485 486 # all but the top-level page can be shell-syntax colored 487 curl -s "cheat.sh/$1" | 488 awk '1 { gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, ""); print }' | 489 "$cmd" -l sh \ 490 --style=plain --theme='Monokai Extended Light' \ 491 --wrap=never --color=always - | 492 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' 493 } 494 495 # CHOP DECimalS ignores all trailing decimal zeros in numbers, even the 496 # decimal dots themselves, when decimals in a number are all zeros 497 chopdecs() { 498 awk 1 "$@" | sed -E 's-(\.[0-9]+[1-9]+)0+$-\1-g; s-([0-9]+)\.0*$-\1-g' 499 } 500 501 # ignore final life-feed from text, if it's the very last byte; also ignore 502 # all trailing carriage-returns 503 choplf() { 504 awk ' 505 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 506 NR > 1 { print ""; fflush() } 507 { gsub(/\r$/, ""); printf "%s", $0 } 508 ' "$@" 509 } 510 511 # Color Json using the `jq` app, allowing an optional filepath as the data 512 # source, and even an optional transformation formula 513 cj() { jq -C "${2:-.}" "${1:--}"; } 514 515 # show a live digital clock 516 clock() { watch -n 1 echo 'Press Ctrl + C to quit this clock'; } 517 518 # CLear Screen, like the old dos command of the same name 519 cls() { clear; } 520 521 # COunt COndition: count how many times the AWK expression given is true 522 coco() { 523 local cond="${1:-1}" 524 [ $# -gt 0 ] && shift 525 awk " 526 { low = lower = tolower(\$0) } 527 ${cond} { count++ } 528 END { print count } 529 " "$@" 530 } 531 532 # COlor SYntax: run syntax-coloring app `bat` without line-wrapping 533 cosy() { 534 local cmd="bat" 535 # debian linux uses a different name for the `bat` app 536 if [ -e "/usr/bin/batcat" ]; then 537 cmd="batcat" 538 fi 539 540 "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ 541 --wrap=never --color=always "$@" | 542 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 543 } 544 545 # COlor SYntax of all files in a FOlder, showing line numbers 546 cosyfo() { 547 local cmd="bat" 548 # debian linux uses a different name for the `bat` app 549 if [ -e "/usr/bin/batcat" ]; then 550 cmd="batcat" 551 fi 552 553 find "${1:-.}" -type f -print0 | xargs --null "$cmd" \ 554 --style=plain,header,numbers --theme='Monokai Extended Light' \ 555 --wrap=never --color=always | 556 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 557 } 558 559 # Colored RipGrep ensures app `rg` emits colors when piped 560 crg() { rg --color=always "$@"; } 561 562 # emit a line with a repeating cross-like symbol in it 563 crosses() { 564 [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -×-g' 565 } 566 567 # Color Syntax: run syntax-coloring app `bat` without line-wrapping 568 cs() { 569 local cmd="bat" 570 # debian linux uses a different name for the `bat` app 571 if [ -e "/usr/bin/batcat" ]; then 572 cmd="batcat" 573 fi 574 575 "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ 576 --wrap=never --color=always "$@" | 577 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 578 } 579 580 # Color Syntax of all files in a Folder, showing line numbers 581 csf() { 582 local cmd="bat" 583 # debian linux uses a different name for the `bat` app 584 if [ -e "/usr/bin/batcat" ]; then 585 cmd="batcat" 586 fi 587 588 find "${1:-.}" -type f -print0 | xargs --null "$cmd" \ 589 --style=plain,header,numbers --theme='Monokai Extended Light' \ 590 --wrap=never --color=always | 591 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 592 } 593 594 # Change Units turns common US units into international ones; uses my 595 # scripts `bu` (Better Units) and `nn` (Nice Numbers) 596 cu() { 597 bu "$@" | awk ' 598 NF == 5 { print $(NF-1), $NF } 599 NF == 4 && $NF == "s" { print $(NF-1), $NF } 600 NF == 4 && $NF != "s" { print $NF } 601 ' | nn 602 } 603 604 # run CURL in Silent/quiet mode 605 curls() { curl -s "$@"; } 606 607 # Date and time 608 d() { 609 printf "\e[32m%s\e[0m \e[34m%s\e[0m\n" "$(date +'%a %b %d')" "$(date +%T)" 610 } 611 612 # Date and time 613 # d() { date "$@"; } 614 615 # emit a line with a repeating ball-like symbol in it 616 dashes() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -—-g'; } 617 618 # DEcode BASE-64-encoded data 619 debase64() { base64 -d "$@"; } 620 621 # DEDUPlicate prevents lines from appearing more than once 622 dedup() { awk '!c[$0]++' "$@"; } 623 624 # DEDUPlicatE prevents lines from appearing more than once 625 dedupe() { awk '!c[$0]++' "$@"; } 626 627 # dictionary-DEFine the word given, using an online service 628 def() { 629 curl -s "dict://dict.org/d:$*" | sed 's-\r--g' | awk ' 630 { gsub(/\r/, "") } 631 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 632 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 633 { print; fflush() }' | less -JMKiCRS 634 } 635 636 # dictionary-define the word given, using an online service 637 define() { 638 curl -s "dict://dict.org/d:$*" | sed 's-\r--g' | awk ' 639 { gsub(/\r/, "") } 640 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 641 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 642 { print; fflush() }' | less -JMKiCRS 643 } 644 645 # DEcompress GZip-encoded data 646 degz() { zcat "$@"; } 647 648 # DEcompress GZIP-encoded data 649 degzip() { zcat "$@"; } 650 651 # turn json lines into a proper json array 652 dejsonl() { jq -s -M "${@:-.}"; } 653 654 # delay lines being piped from standard input; the delay is given as a number 655 # of seconds, and is by default 1, when not given 656 delaylines() { 657 ( 658 IFS="$(printf "\n")" 659 while read -r l; do 660 sleep "${1:-1}" 661 printf "%s\n" "${l}" 662 done 663 ) 664 } 665 666 # turn lines of Space-Separated Values into lines of Tab-Separated Values 667 dessv() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?--; s- +-\t-g'; } 668 669 # turn UTF-16 data into UTF-8 670 deutf16() { iconv -f utf16 -t utf8 "$@"; } 671 672 # lookup words using an online DICtionary 673 dic() { 674 curl -s "dict://dict.org/d:$*" | awk ' 675 { gsub(/\r/, "") } 676 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 677 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 678 { print; fflush() }' | less -JMKiCRS 679 } 680 681 # lookup words using an online DICTionary 682 dict() { 683 curl -s "dict://dict.org/d:$*" | awk ' 684 { gsub(/\r/, "") } 685 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 686 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 687 { print; fflush() }' | less -JMKiCRS 688 } 689 690 # DIVide 2 numbers 3 ways, including the complement 691 div() { 692 awk -v a="${1:-1}" -v b="${2:-1}" ' 693 BEGIN { 694 gsub(/_/, "", a); gsub(/_/, "", b) 695 if (a > b) { c = a; a = b; b = c; } 696 printf "%f\n%f\n%f\n", a / b, b / a, 1 - a / b 697 exit 698 }' 699 } 700 701 # get/fetch data from the filename or URI given; named `dog` because dogs can 702 # `fetch` things for you 703 dog() { 704 case "$1" in 705 http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) 706 [ -e "$1" ] && cat "$1" || curl -s "$1";; 707 file://*) 708 [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; 709 *) 710 cat "$1";; 711 esac 2> /dev/null || { 712 printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 713 return 1 714 } 715 } 716 717 # emit a line with a repeating dot-like symbol in it 718 dots() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -·-g'; } 719 720 # show the current Date and Time 721 dt() { 722 printf "\e[32m%s\e[0m \e[34m%s\e[0m\n" "$(date +'%a %b %d')" "$(date +%T)" 723 } 724 725 # show the current Date, Time, and a Calendar with the 3 `current` months 726 dtc() { 727 # show the current time center-aligned 728 printf "%22s\e[32m%s\e[0m \e[34m%s\e[0m\n" \ 729 " " "$(date +'%a %b %d')" "$(date +%T)" 730 731 printf "\n" 732 733 # debian linux has a different `cal` app which highlights the day 734 if [ -e "/usr/bin/ncal" ]; then 735 ncal -C -3 736 else 737 cal -3 738 fi 739 } 740 741 # Download from Youtube 742 dy() { yt-dlp "$@"; } 743 744 # Download Youtube AAC audio 745 dyaac() { yt-dlp -f 140 "$@"; } 746 747 # Download Youtube MP4 video 748 dymp4() { yt-dlp -f 22 "$@"; } 749 750 # Evaluate Awk expression 751 ea() { awk "BEGIN { print ${1:-0}; exit }"; } 752 753 # Evaluate Awk expression 754 # ea() { awk --bignum "BEGIN { print ${1:-0}; exit }"; } 755 756 # run a command for each of the files given as lines from standard input 757 each() { 758 ( 759 IFS="$(printf "\n")" 760 while read -r name; do 761 "$@" < "${name}" 762 err="$?" 763 if [ "${err}" -ne 0 ]; then 764 exit "${err}" 765 fi 766 done 767 ) 768 } 769 770 # ECHO Background, using gray-colored background 771 echob() { printf "\e[48;5;253m%s\e[0m\n" "$*"; } 772 773 # Green ECHO, using a green style 774 gecho() { printf "\e[38;5;29m%s\e[0m\n" "$*"; } 775 776 # ECHO Highlighted, using inverted style 777 echoh() { printf "\e[7m%s\e[0m\n" "$*"; } 778 779 # ECHO Inverted, using inverted style 780 echoi() { printf "\e[7m%s\e[0m\n" "$*"; } 781 782 # ECHO Lines emits each argument given as its own line of output 783 echol() { 784 awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" 785 } 786 787 # edit plain-text files 788 # edit() { micro "$@"; } 789 790 # edit plain-text files 791 # edit() { tilde "$@"; } 792 793 # Extended-mode Grep, enabling its full regex syntax 794 # eg() { grep -E "$@"; } 795 796 # Extended-mode Grep, enabling its full regex syntax 797 eg() { grep -E --line-buffered "$@"; } 798 799 # Echo Lines emits each argument given as its own line of output 800 el() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@"; } 801 802 # Evaluate Nodejs expression 803 en() { 804 local expr="${1:-null}" 805 expr="$(echo "${expr}" | sed 's-\\-\\\\-g; s-`-\`-g')" 806 node -e "console.log(${expr})" | sed 's-\x1b\[[^A-Za-z]+[A-Za-z]--g' 807 } 808 809 # Evaluate Python expression 810 ep() { python -c "print(${1:-None})"; } 811 812 # Evaluate Ruby expression 813 er() { ruby -e "puts ${1:-nil}"; } 814 815 # Editor Read-Only 816 ero() { micro -readonly true "$@"; } 817 818 # Extended-mode Sed, enabling its full regex syntax 819 es() { sed -E -u "$@"; } 820 821 # f() { find "$@"; } 822 823 # run the fuzzy Finder (fzf) in multi-choice mode, with custom keybindings 824 f() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } 825 826 # convert Fahrenheit into Celsius 827 # f2c() { 828 # echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | 829 # awk '{ print ($0 - 32) * 9.0/5.0 }' 830 # } 831 832 # convert FAHRenheit into celsius 833 fahr() { 834 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | 835 awk '{ print ($0 - 32) * 9.0/5.0 }' 836 } 837 838 # convert fahrenheit into celsius 839 fahrenheit() { 840 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | 841 awk '{ print ($0 - 32) * 9.0/5.0 }' 842 } 843 844 # Faint (lines) with AWK, using a gray color 845 fawk() { 846 local cond="${1:-1}" 847 [ $# -gt 0 ] && shift 848 849 awk "${cond} { 850 gsub(/\\x1b\\[0m/, \"\x1b[0m\\x1b[38;5;248m\") 851 printf \"\\x1b[38;5;248m%s\\x1b[0m\\n\", \$0 852 fflush() 853 next 854 } 855 856 { 857 print 858 fflush() 859 }" "$@" 860 } 861 862 # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings 863 ff() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } 864 865 # deduplicate lines by the field given, keeping them in their original order 866 fieldedup() { 867 local n="${1:-0}" 868 [ $# -gt 0 ] && shift 869 awk "!c[(\$${n} >= 0) ? \$${n} : \$(NF + ${n} - 1)]++" "$@" 870 } 871 872 # show all files in a folder, digging recursively 873 files() { 874 local arg 875 for arg in "${@:-.}"; do 876 find "${arg}" -type f 877 done 878 } 879 880 # get the first n lines, or 1 by default 881 first() { head -n "${1:-1}" "${2:--}"; } 882 883 # limit data up to the first n bytes 884 firstbytes() { head -c "$1" "${2:--}"; } 885 886 # get the first n lines, or 1 by default 887 firstlines() { head -n "${1:-1}" "${2:--}"; } 888 889 # fix lines, ignoring leading UTF-8_BOMs (byte-order-marks) on each input's 890 # first line, turning all end-of-line CRLF byte-pairs into single line-feeds, 891 # and ensuring each input's last line ends with a line-feed 892 fixlines() { 893 awk ' 894 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 895 { print; fflush() }' "$@" | sed -E 's-\r$--' 896 } 897 898 # Faint LEAK emits/tees input both to stdout and stderr, coloring gray what 899 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 900 # involving several steps 901 fleak() { 902 awk '{ 903 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 904 printf "\x1b[38;5;248m%s\x1b[0m\n", $0 > "/dev/stderr" 905 print 906 fflush() 907 }' "$@" 908 } 909 910 # Faster Nice Hex tries to speed-up my script `nh` via `pypy` 911 fnh() { pypy3 "$(which nh)" "$@"; } 912 913 # show all folders in a folder, digging recursively 914 folders() { 915 local arg 916 for arg in "${@:-.}"; do 917 find "${arg}" -type d | awk 'NR > 1' 918 done 919 } 920 921 # make text lines look "FRAMEd" by padding them to the current terminal 922 # width and using ANSI-style background colors; uses my script `tl` 923 frame() { 924 local color="${1:-green}" 925 [ $# -gt 0 ] && shift 926 tl "${color}back(f'{plain(line):$(tput cols)}')" "$@" 927 } 928 929 # start from the line number given, skipping all previous ones 930 fromline() { tail -n +"${1:-1}" "${2:--}"; } 931 932 # convert FeeT into meters 933 ft() { 934 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.3048 * $0 }' 935 } 936 937 # convert FeeT² (squared) into meters² 938 ft2() { 939 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.09290304 * $0 }' 940 } 941 942 # convert a mix of FeeT and INches into meters 943 ftin() { 944 local ft="${1:-0}" 945 ft="$(echo "${ft}" | sed 's-_--g')" 946 local in="${2:-0}" 947 in="$(echo "${in}" | sed 's-_--g')" 948 awk "BEGIN { print 0.3048 * ${ft} + 0.0254 * ${in}; exit }" 949 } 950 951 # Faster Transform Json, by using pypy instead of standard python 952 ftj() { pypy3 "$(which tj)" "$@"; } 953 954 # Faster Transform Lines, by using pypy instead of standard python 955 ftl() { pypy3 "$(which tl)" "$@"; } 956 957 # run the FuZzy finder (fzf) in multi-choice mode, with custom keybindings 958 fz() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } 959 960 # Get/fetch data from the filename or URI given 961 # g() { 962 # case "$1" in 963 # http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) 964 # [ -e "$1" ] && cat "$1" || curl -s "$1";; 965 # file://*) 966 # [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; 967 # *) 968 # cat "$1";; 969 # esac 2> /dev/null || { 970 # printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 971 # return 1 972 # } 973 # } 974 975 # run `grep` in extended mode, enabling its full regex syntax 976 # g() { grep -E "$@"; } 977 978 # run `grep` in extended mode, enabling its full regex syntax 979 g() { grep -E --line-buffered "$@"; } 980 981 # convert GALlons into liters 982 gal() { 983 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 3.785411784 * $0 }' 984 } 985 986 # GCCGO Build Stripped: a niche use-case for the go/gccgo compilers 987 gccgobs() { go build -compiler gccgo -gccgoflags "-s -O2" -trimpath "$@"; } 988 989 # Green-colored ECHO 990 gecho() { printf "\e[38;5;29m%s\e[0m\n" "$*"; } 991 992 # GET/fetch data from the filename or URI given 993 # get() { 994 # case "$1" in 995 # http://*|https://*|ftp://*) [ -e "$1" ] && cat "$1" || wget -O - "$1";; 996 # *) cat "$1";; 997 # esac 2> /dev/null || { 998 # printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 999 # return 1 1000 # } 1001 # } 1002 1003 # GET/fetch data from the filename or URI given 1004 get() { 1005 case "$1" in 1006 http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) 1007 [ -e "$1" ] && cat "$1" || curl -s "$1";; 1008 file://*) 1009 [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; 1010 *) 1011 cat "$1";; 1012 esac 2> /dev/null || { 1013 printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 1014 return 1 1015 } 1016 } 1017 1018 # Green LEAK emits/tees input both to stdout and stderr, coloring green what 1019 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1020 # involving several steps 1021 gleak() { 1022 awk '{ 1023 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1024 printf "\x1b[38;5;29m%s\x1b[0m\n", $0 > "/dev/stderr" 1025 print 1026 fflush() 1027 }' "$@" 1028 } 1029 1030 # GO Build Stripped: a common use-case for the go compiler 1031 gobs() { go build -ldflags "-s -w" -trimpath "$@"; } 1032 1033 # GO DEPendencies: shows all dependencies in a go project 1034 # godep() { go list -f '{{ join .Deps "\n" }}' "$@"; } 1035 1036 # GO DEPendencieS: shows all dependencies in a go project 1037 godeps() { go list -f '{{ join .Deps "\n" }}' "$@"; } 1038 1039 # GO IMPorts: show all imports in a go project 1040 goimp() { go list -f '{{ join .Imports "\n" }}' "$@"; } 1041 1042 # GO IMPortS: show all imports in a go project 1043 goimps() { go list -f '{{ join .Imports "\n" }}' "$@"; } 1044 1045 # Grep, Recursive Interactive and Plain 1046 grip() { ugrep -r -Q --color=never -E "$@"; } 1047 1048 # transform lines using AWK's `gsub` (Global-regex SUBstitution) function 1049 gsub() { 1050 local what="$1" 1051 local with="$2" 1052 [ $# -gt 0 ] && shift 1053 [ $# -gt 0 ] && shift 1054 awk "{ gsub(/${what}/, \"${with}\"); print; fflush() }" "$@" 1055 } 1056 1057 # show Help for the command given 1058 h() { "$@" --help || "$@" -h || "$@" --h || "$@" -help || "$@" help; } 1059 1060 # Highlight (lines) with AWK 1061 hawk() { 1062 local cond="${1:-1}" 1063 [ $# -gt 0 ] && shift 1064 1065 awk "${cond} { 1066 gsub(/\\x1b\\[0m/, \"\x1b[0m\\x1b[7m\") 1067 printf \"\\x1b[7m%s\\x1b[0m\\n\", \$0 1068 fflush() 1069 next 1070 } 1071 1072 { 1073 print 1074 fflush() 1075 }" "$@" 1076 } 1077 1078 # play a heartbeat-like sound lasting the number of seconds given, or for 1 1079 # second by default; uses my script `waveout` 1080 # heartbeat() { 1081 # local f='sum(sin(10*tau*exp(-20*v))*exp(-2*v) for v in (u, (u-0.25)%1))/2' 1082 # waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1083 # } 1084 1085 # play a heartbeat-like sound lasting the number of seconds given, or for 1 1086 # second by default; uses my script `waveout` 1087 heartbeat() { 1088 local a='sin(v[0]*tau*exp(-20*v[1]))*exp(-2*v[1])' 1089 local b='((12, u), (8, (u-0.25)%1))' 1090 local f="sum($a for v in $b) / 2" 1091 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1092 } 1093 1094 # Highlighted-style ECHO 1095 hecho() { printf "\e[7m%s\e[0m\n" "$*"; } 1096 1097 # show each byte as a pair of HEXadecimal (base-16) symbols 1098 hexify() { 1099 cat "$@" | od -x -A n | sed 's- --g' | 1100 awk '{ printf "%s", $0 } END { printf "\n" }' 1101 } 1102 1103 # HIghlighted-style ECHO 1104 hiecho() { printf "\e[7m%s\e[0m\n" "$*"; } 1105 1106 highlight() { 1107 awk '{ 1108 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") 1109 printf "\x1b[7m%s\x1b[0m\n", $0 1110 fflush() 1111 }' "$@" 1112 } 1113 1114 # HIghlight LEAK emits/tees input both to stdout and stderr, highlighting what 1115 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1116 # involving several steps 1117 hileak() { 1118 awk '{ 1119 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1120 printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" 1121 print 1122 fflush() 1123 }' "$@" 1124 } 1125 1126 hilite() { 1127 awk '{ 1128 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") 1129 printf "\x1b[7m%s\x1b[0m\n", $0 1130 fflush() 1131 }' "$@" 1132 } 1133 1134 # Header Less runs `less` with line numbers, ANSI styles, no line-wrapping, 1135 # and using the first line as a sticky-header, so it always shows on top 1136 hl() { less --header=1 -JMKNiCRS "$@"; } 1137 1138 # Highlight LEAK emits/tees input both to stdout and stderr, highlighting what 1139 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1140 # involving several steps 1141 hleak() { 1142 awk '{ 1143 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1144 printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" 1145 print 1146 fflush() 1147 }' "$@" 1148 } 1149 1150 # Help Me Remember my custom shell commands 1151 hmr() { 1152 local cmd="bat" 1153 # debian linux uses a different name for the `bat` app 1154 if [ -e "/usr/bin/batcat" ]; then 1155 cmd="batcat" 1156 fi 1157 1158 "$cmd" \ 1159 --style=plain,header,numbers --theme='Monokai Extended Light' \ 1160 --wrap=never --color=always "$(which clam)" | 1161 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 1162 } 1163 1164 # convert seconds into a colon-separated Hours-Minutes-Seconds triple 1165 hms() { 1166 echo "${@:-0}" | sed 's-_--g; s- *-\n-g' | awk '{ 1167 x = $0 1168 h = (x - x % 3600) / 3600 1169 m = (x % 3600) / 60 1170 s = x % 60 1171 printf "%02d:%02d:%05.2f\n", h, m, s 1172 }' 1173 } 1174 1175 # Header View runs `less` without line numbers, with ANSI styles, with no 1176 # line-wrapping, and using the first line as a sticky-header, so it always 1177 # shows on top 1178 hv() { less --header=1 -JMKiCRS "$@"; } 1179 1180 # Index all lines starting from 0, using a tab right after each line number 1181 i() { nl -b a -w 1 -v 0 "$@"; } 1182 1183 # avoid/ignore lines which case-insensitively match any of the regexes given 1184 iavoid() { 1185 awk ' 1186 BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } 1187 { l = tolower($0); for (i in re) if (l ~ re[i]) { next } } 1188 { print; fflush() }' "${@:-^$}" 1189 } 1190 1191 # pipe-identity operation which ignores all arguments given, which is useful 1192 # to quickly disable a step along a long pipe of commands (`idem` is latin 1193 # for `same`) 1194 idem() { cat; } 1195 1196 # pipe-IDENtity operation which ignores all arguments given, which is useful 1197 # to quickly disable a step along a long pipe of commands 1198 iden() { cat; } 1199 1200 # pipe-identity operation which ignores all arguments given, which is useful 1201 # to quickly disable a step along a long pipe of commands 1202 identity() { cat; } 1203 1204 # Inverted-style ECHO 1205 iecho() { printf "\e[7m%s\e[0m\n" "$*"; } 1206 1207 # Invert LEAK emits/tees input both to stdout and stderr, inverse-styling what 1208 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1209 # involving several steps 1210 ileak() { 1211 awk '{ 1212 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1213 printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" 1214 print 1215 fflush() 1216 }' "$@" 1217 } 1218 1219 # only keep lines which case-insensitively match any of the regexes given 1220 imatch() { 1221 awk ' 1222 BEGIN { 1223 for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } 1224 } 1225 1226 { 1227 l = tolower($0) 1228 for (i in re) if (l ~ re[i]) { print; fflush(); next } 1229 }' "${@:-.}" 1230 } 1231 1232 # emit each word-like item from each input line on its own line 1233 items() { awk '{ for (i = 1; i <= NF; i++) print $i; fflush() }' "$@"; } 1234 1235 # shrink/compact Json data, allowing an optional filepath 1236 # j0() { python -m json.tool --compact "${1:--}"; } 1237 1238 # shrink/compact Json using the `jq` app, allowing an optional filepath, and 1239 # even an optional transformation formula after that 1240 # j0() { jq -c -M "${2:-.}" "${1:--}"; } 1241 1242 # show Json data on multiple lines, using 2 spaces for each indentation level, 1243 # allowing an optional filepath 1244 # j2() { python -m json.tool --indent 2 "${1:--}"; } 1245 1246 # show Json data on multiple lines, using 2 spaces for each indentation level, 1247 # allowing an optional filepath, and even an optional transformation formula 1248 # after that 1249 # j2() { jq --indent 2 -M "${2:-.}" "${1:--}"; } 1250 1251 # listen to streaming JAZZ music 1252 # jazz() { 1253 # printf "streaming \e[7mSmooth Jazz Instrumental\e[0m\n" 1254 # mpv https://stream.zeno.fm/00rt0rdm7k8uv 1255 # } 1256 1257 # listen to streaming JAZZ music 1258 jazz() { 1259 printf "streaming \e[7mSmooth Jazz Instrumental\e[0m\n" 1260 mpv --quiet https://stream.zeno.fm/00rt0rdm7k8uv 1261 } 1262 1263 # show a `dad` JOKE from the web, sometimes even a very funny one 1264 joke() { 1265 curl -s https://icanhazdadjoke.com | fold -s | sed -E 's- *\r?$--' 1266 # plain-text output from previous cmd doesn't end with a line-feed 1267 printf "\n" 1268 } 1269 1270 # shrink/compact JSON data, allowing an optional filepath 1271 # json0() { python -m json.tool --compact "${1:--}"; } 1272 1273 # shrink/compact JSON using the `jq` app, allowing an optional filepath, and 1274 # even an optional transformation formula after that 1275 json0() { jq -c -M "${2:-.}" "${1:--}"; } 1276 1277 # show JSON data on multiple lines, using 2 spaces for each indentation level, 1278 # allowing an optional filepath 1279 # json2() { python -m json.tool --indent 2 "${1:--}"; } 1280 1281 # show JSON data on multiple lines, using 2 spaces for each indentation level, 1282 # allowing an optional filepath, and even an optional transformation formula 1283 # after that 1284 json2() { jq --indent 2 -M "${2:-.}" "${1:--}"; } 1285 1286 # emit the given number of random/junk bytes, or 1024 junk bytes by default 1287 junk() { head -c "$(echo "${1:-1024}" | sed 's-_--g')" /dev/urandom; } 1288 1289 # run `less`, showing line numbers, among other settings 1290 l() { less -JMKNiCRS "$@"; } 1291 1292 # Like A Book groups lines as 2 side-by-side pages, the same way books 1293 # do it; uses my script `book` 1294 lab() { book "$(($(tput lines) - 1))" "$@" | less -JMKiCRS; } 1295 1296 # Line xARGS: `xargs` using line separators, which handles filepaths 1297 # with spaces, as long as the standard input has 1 path per line 1298 largs() { xargs -d '\n' "$@"; } 1299 1300 # get the last n lines, or 1 by default 1301 # last() { tail -n "${1:-1}" "${2:--}"; } 1302 1303 # get up to the last given number of bytes 1304 lastbytes() { tail -c "${1:-1}" "${2:--}"; } 1305 1306 # get the last n lines, or 1 by default 1307 lastlines() { tail -n "${1:-1}" "${2:--}"; } 1308 1309 # turn UTF-8 into its latin-like subset, where variants of latin letters stay 1310 # as given, and where all other symbols become question marks, one question 1311 # mark for each code-point byte 1312 latinize() { 1313 iconv -f utf-8 -t latin-1//translit "$@" | iconv -f latin-1 -t utf-8 1314 } 1315 1316 # convert pounds (LB) into kilograms 1317 lb() { 1318 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.45359237 * $0 }' 1319 } 1320 1321 # convert pounds (LBS) into kilograms 1322 lbs() { 1323 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.45359237 * $0 }' 1324 } 1325 1326 # convert a mix of pounds (LB) and weight-ounces (OZ) into kilograms 1327 lboz() { 1328 local lb="${1:-0}" 1329 lb="$(echo "${lb}" | sed 's-_--g')" 1330 local oz="${2:-0}" 1331 oz="$(echo "${oz}" | sed 's-_--g')" 1332 awk "BEGIN { print 0.45359237 * ${lb} + 0.028349523 * ${oz}; exit }" 1333 } 1334 1335 # leak stdin to stderr, before also copying it to stdout: its main use is to 1336 # inspect/debug the intermediate stages of a `pipelined` shell command 1337 # leak() { 1338 # awk '{ 1339 # gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1340 # printf "\x1b[38;5;248m%s\x1b[0m\n", $0 > "/dev/stderr" 1341 # print 1342 # fflush() 1343 # }' "$@" 1344 # } 1345 1346 # LEAK Orange emits/tees input both to stdout and stderr, coloring orange what 1347 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1348 # involving several steps 1349 leako() { 1350 awk '{ 1351 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1352 printf "\x1b[38;5;166m%s\x1b[0m\n", $0 > "/dev/stderr" 1353 print 1354 fflush() 1355 }' "$@" 1356 } 1357 1358 # run `less`, showing line numbers, among other settings 1359 least() { less -JMKNiCRS "$@"; } 1360 1361 # Lines ECHO emits each argument given as its own line of output 1362 lecho() { 1363 awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" 1364 } 1365 1366 # limit stops at the first n bytes, or 1024 bytes by default 1367 limit() { head -c "$(echo "${1:-1024}" | sed 's-_--g')" "${2:--}"; } 1368 1369 # Less with Header runs `less` with line numbers, ANSI styles, no line-wraps, 1370 # and using the first line as a sticky-header, so it always shows on top 1371 lh() { less --header=1 -JMKNiCRS "$@"; } 1372 1373 # regroup adjacent lines into n-item tab-separated lines 1374 lineup() { 1375 local n="${1:-0}" 1376 [ $# -gt 0 ] && shift 1377 1378 if [ "$n" -le 0 ]; then 1379 awk ' 1380 NR > 1 { printf "\t" } 1381 { printf "%s", $0 } 1382 END { if (NR > 0) print "" } 1383 ' "$@" 1384 return $? 1385 fi 1386 1387 awk -v n="$n" ' 1388 NR % n != 1 { printf "\t" } 1389 { printf "%s", $0 } 1390 NR % n == 0 { print ""; fflush() } 1391 END { if (NR % n != 0) print "" } 1392 ' "$@" 1393 } 1394 1395 # LOAD data from the filename or URI given 1396 load() { 1397 case "$1" in 1398 http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) 1399 [ -e "$1" ] && cat "$1" || curl -s "$1";; 1400 file://*) 1401 [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; 1402 *) 1403 cat "$1";; 1404 esac 2> /dev/null || { 1405 printf "\e[31mcan't load %s\e[0m\n" "$1" >&2 1406 return 1 1407 } 1408 } 1409 1410 # LOwercase line, check (awk) COndition: on each success, the original line 1411 # is output with its original letter-casing, as its lower-cased version is 1412 # only a convenience meant for the condition 1413 loco() { 1414 local cond="${1:-1}" 1415 [ $# -gt 0 ] && shift 1416 awk " 1417 { 1418 line = orig = original = \$0 1419 low = lower = tolower(\$0) 1420 \$0 = lower 1421 } 1422 ${cond} { print line; fflush() } 1423 " "$@" 1424 } 1425 1426 # LOcal SERver webserves files in a folder as localhost, using the port 1427 # number given, or port 8080 by default 1428 loser() { 1429 # printf "\e[38;5;26mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 1430 printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 1431 python3 -m http.server "${1:-8080}" -d "${2:-.}" 1432 } 1433 1434 # LOWERcase all ASCII symbols 1435 lower() { awk '{ print tolower($0); fflush() }' "$@"; } 1436 1437 # LOWERCASE all ASCII symbols 1438 lowercase() { awk '{ print tolower($0); fflush() }' "$@"; } 1439 1440 # LiSt files, showing 4096-byte filesystem Page counts 1441 lsp() { ls -s --block-size=4096 "$@"; } 1442 1443 # Listen To Youtube 1444 lty() { 1445 local url 1446 # some youtube URIs end with extra playlist/tracker parameters 1447 url="$(echo "$1" | sed 's-&.*--')" 1448 # mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" 1449 mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" 1450 } 1451 1452 # Match lines with any of the regexes given 1453 m() { 1454 awk ' 1455 BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } 1456 { for (i in re) if ($0 ~ re[i]) { print; fflush(); next } }' "${@:-.}" 1457 } 1458 1459 # only keep lines which match any of the regexes given 1460 match() { 1461 awk ' 1462 BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } 1463 { for (i in re) if ($0 ~ re[i]) { print; fflush(); next } }' "${@:-.}" 1464 } 1465 1466 # merge stderr into stdout, without any ugly keyboard-dancing 1467 merrge() { "$@" 2>&1; } 1468 1469 # convert MIles into kilometers 1470 mi() { 1471 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 1.609344 * $0 }' 1472 } 1473 1474 # Make In Folder 1475 # mif() { 1476 # pushd "${1:-.}" > /dev/null || return 1477 # [ $# -gt 0 ] && shift 1478 # make "$@" 1479 # popd > /dev/null || return 1480 # } 1481 1482 # convert MIles² (squared) into kilometers² 1483 mi2() { 1484 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | 1485 awk '{ print 2.5899881103360 * $0 }' 1486 } 1487 1488 # convert Miles Per Hour into kilometers per hour 1489 mph() { 1490 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 1.609344 * $0 }' 1491 } 1492 1493 # Number all lines, starting from the number given, or 1 by default 1494 # n() { 1495 # local n="${1:-1}" 1496 # [ $# -gt 0 ] && shift 1497 # awk -v n="$n" '{ printf "%d\t%s\n", NR - 1 + n, $0; fflush() }' "$@" 1498 # } 1499 1500 # Number all lines, starting from the number given, or 1 by default 1501 # n() { 1502 # local n="${1:-1}" 1503 # [ $# -gt 0 ] && shift 1504 # nl -b a -w 1 -v "$n" "$@" 1505 # } 1506 1507 # Number all lines, using a tab right after each line number 1508 n() { nl -b a -w 1 "$@"; } 1509 1510 # Nice Byte Count, using my scripts `nn` and `cext` 1511 nbc() { wc -c "$@" | nn | cext; } 1512 1513 # Nice numbers CAlculator runs my script `ca` and colors results with my 1514 # script `nn`, alternating styles to make long numbers easier to read 1515 nca() { ca "$@" | nn; } 1516 1517 # No (standard) Error ignores stderr, without any ugly keyboard-dancing 1518 ne() { "$@" 2> /dev/null; } 1519 1520 # No (standard) ERRor ignores stderr, without any ugly keyboard-dancing 1521 nerr() { "$@" 2> /dev/null; } 1522 1523 # Nice File Sizes, using my scripts `nn` and `cext` 1524 nfs() { 1525 # turn arg-list into single-item lines 1526 awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" | 1527 # calculate file-sizes, and reverse-sort results 1528 xargs -d '\n' wc -c | sort -rn | 1529 # start output with a header-like line, and add a MiB field 1530 awk 'BEGIN { printf "%6s %10s %8s name\n", "n", "bytes", "MiB" } 1531 { printf "%6d %10d %8.2f %s\n", NR - 1, $1, $1 / 1048576, $2 }' | 1532 # make zeros in the MiB field stand out with a special color 1533 awk '{ gsub(/ 0.00 /, "\x1b[38;5;103m 0.00 \x1b[0m"); print }' | 1534 # make numbers nice, alternating styles along 3-digit groups 1535 nn | 1536 # color-code file extensions 1537 cext | 1538 # make table breathe with empty lines, so tall outputs are readable 1539 awk '(NR - 2) % 5 == 1 && NR > 1 { print "" } 1' 1540 } 1541 1542 # Nice Hex Faster tries to speed-up my script `nh` via `pypy` 1543 nhf() { pypy3 "$(which nh)" "$@"; } 1544 1545 # NIce numbers CAlculator runs my script `ca` and colors results with my 1546 # script `nn`, alternating styles to make long numbers easier to read 1547 nica() { ca "$@" | nn; } 1548 1549 # emit nothing to output and/or discard everything from input 1550 nil() { 1551 if [ -p /dev/stdin ]; then 1552 cat > /dev/null 1553 else 1554 head -c 0 1555 fi 1556 } 1557 1558 # convert Nautical MIles into kilometers 1559 nmi() { 1560 echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 1.852 * $0 }' 1561 } 1562 1563 # NO (standard) ERRor ignores stderr, without any ugly keyboard-dancing 1564 noerr() { "$@" 2> /dev/null; } 1565 1566 # play a white-noise sound lasting the number of seconds given, or for 1 1567 # second by default; uses my script `waveout` 1568 noice() { waveout "${1:-1}" "${2:-0.05} * random()" | mpv --really-quiet -; } 1569 1570 # play a white-noise sound lasting the number of seconds given, or for 1 1571 # second by default; uses my script `waveout` 1572 noise() { waveout "${1:-1}" "${2:-0.05} * random()" | mpv --really-quiet -; } 1573 1574 # show the current date and time 1575 now() { date +'%Y-%m-%d %H:%M:%S'; } 1576 1577 # Nice Size, using my scripts `nn` and `cext` 1578 ns() { wc -c "$@" | nn | cext; } 1579 1580 # No Standard Error ignores stderr, without any ugly keyboard-dancing 1581 nse() { "$@" 2> /dev/null; } 1582 1583 # Nice Transform Json, using my scripts `tj`, and `nj` 1584 # ntj() { tj "$@" | nj; } 1585 1586 # Nice Transform Json, using my scripts `tj`, `nj`, and `nn` 1587 ntj() { tj "$@" | nj | nn; } 1588 1589 # Nice numbers Word-Count runs `wc` and colors results with my script `nn`, 1590 # alternating styles to make long numbers easier to read 1591 nwc() { wc "$@" | nn; } 1592 1593 # Nice Zoom Json, using my scripts `zj`, and `nj` 1594 # nzj() { zj "$@" | nj; } 1595 1596 # Nice Zoom Json, using my scripts `zj`, `nj`, and `nn` 1597 nzj() { zj "$@" | nj | nn; } 1598 1599 # unify Output by merging stderr into stdout, without any keyboard-dancing 1600 o() { "$@" 2>&1; } 1601 1602 # Orange LEAK emits/tees input both to stdout and stderr, coloring orange what 1603 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1604 # involving several steps 1605 oleak() { 1606 awk '{ 1607 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1608 printf "\x1b[38;5;166m%s\x1b[0m\n", $0 > "/dev/stderr" 1609 print 1610 fflush() 1611 }' "$@" 1612 } 1613 1614 # emit each unique line only the first time, ignoring all later occurrences 1615 once() { awk '!c[$0]++' "$@"; } 1616 1617 # open/pop-up files, GUI file-explorers for folders, and even web-browsers 1618 # for any URIs given; only works on the windows subsystem for linux 1619 # open() { 1620 # local arg 1621 # for arg in "${@:-.}"; do 1622 # cmd.exe /c start "$(echo "$arg" | sed 's-/$--; s-/-\\-g')" 1623 # done | cat 1624 # } 1625 1626 # emit each unique line only the first time, ignoring all later occurrences; 1627 # this is equivalent to an `or` operation on sets of lines 1628 or() { awk '!c[$0]++' "$@"; } 1629 1630 # make text Plain, by ignoring ANSI terminal styling 1631 p() { 1632 awk '{ 1633 # ignore notifications (code 9) and hyperlinks (code 8) 1634 gsub(/\x1b\](8|9);[^\x07]*\x07/, "") 1635 # ignore cursor-movers and style-changers 1636 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1637 1638 print 1639 fflush() 1640 }' "$@" 1641 } 1642 1643 # ignore all arguments given, which is useful to quickly disable a step along 1644 # a long pipe of commands 1645 pass() { cat; } 1646 1647 # ignore all arguments given, which is useful to quickly disable a step along 1648 # a long pipe of commands 1649 passthru() { cat; } 1650 1651 # Paragraph AWK runs `awk` in block/paragraph/multiline input-mode 1652 pawk() { awk -F='' -v RS='' "$@"; } 1653 1654 # Plain Interactive Grep 1655 pig() { ugrep --color=never -Q -E "$@"; } 1656 1657 # make text plain, by ignoring ANSI terminal styling 1658 plain() { 1659 awk '{ 1660 # ignore notifications (code 9) and hyperlinks (code 8) 1661 gsub(/\x1b\](8|9);[^\x07]*\x07/, "") 1662 # ignore cursor-movers and style-changers 1663 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1664 1665 print 1666 fflush() 1667 }' "$@" 1668 } 1669 1670 # play audio/video media, possibly using a GUI 1671 play() { mpv "${@:--}"; } 1672 1673 # Purple LEAK emits/tees input both to stdout and stderr, coloring purple what 1674 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 1675 # involving several steps 1676 pleak() { 1677 awk '{ 1678 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 1679 printf "\x1b[38;5;99m%s\x1b[0m\n", $0 > "/dev/stderr" 1680 print 1681 fflush() 1682 }' "$@" 1683 } 1684 1685 # PLay data from standard INput 1686 plin() { mpv "$@" -; } 1687 1688 # Paused MPV 1689 pmpv() { mpv --pause "${@:--}"; } 1690 1691 # Print Python result 1692 pp() { python -c "print($1)"; } 1693 1694 # PRint AWK result 1695 # prawk() { awk "BEGIN { print ${1:-0}; exit }"; } 1696 1697 # PRint AWK result 1698 # prawk() { awk --bignum "BEGIN { print ${1:-0}; exit }"; } 1699 1700 # start by joining all arguments given as a tab-separated-items line of output, 1701 # followed by all lines from stdin verbatim 1702 pretsv() { 1703 awk 'BEGIN { 1704 for (i = 1; i < ARGC; i++) { 1705 if (i > 1) { printf "\t" } 1706 printf "%s", ARGV[i] 1707 delete ARGV[i] 1708 } 1709 printf "\n" 1710 } 1711 { print; fflush() }' "$@" 1712 } 1713 1714 # Plain Recursive Interactive Grep 1715 prig() { ugrep --color=never -r -Q -E "$@"; } 1716 1717 # Print RUBY expression 1718 # pruby() { ruby -e "puts ${1:-nil}"; } 1719 1720 # PRint pYTHON result 1721 # prython() { python -c "print(${1:-None})"; } 1722 1723 # Play Youtube Audio 1724 pya() { 1725 local url 1726 # some youtube URIs end with extra playlist/tracker parameters 1727 url="$(echo "$1" | sed 's-&.*--')" 1728 # mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" 1729 mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" 1730 } 1731 1732 # Quiet ignores stderr, without any ugly keyboard-dancing 1733 q() { "$@" 2> /dev/null; } 1734 1735 # Quiet MPV 1736 qmpv() { mpv --quiet "${@:--}"; } 1737 1738 # ignore stderr, without any ugly keyboard-dancing 1739 quiet() { "$@" 2> /dev/null; } 1740 1741 # Quiet cURL 1742 qurl() { curl -s "$@"; } 1743 1744 # Reset the screen, which empties it and resets the current style 1745 r() { reset; } 1746 1747 # keep only lines between the 2 line numbers given, inclusively 1748 rangelines() { 1749 { [ "$#" -eq 2 ] || [ "$#" -eq 3 ]; } && [ "${1}" -le "${2}" ] && 1750 { tail -n +"${1:-1}" "${3:--}" | head -n "$(("${2}" - "${1}" + 1))"; } 1751 } 1752 1753 # RANdom MANual page 1754 ranman() { 1755 find "/usr/share/man/man${1:-1}" -type f | shuf -n 1 | xargs basename | 1756 sed 's-\.gz$--g' | xargs man 1757 } 1758 1759 # play a ready-phone-line sound lasting the number of seconds given, or for 1 1760 # second by default; uses my script `waveout` 1761 ready() { 1762 local f='0.5 * sin(350*tau*t) + 0.5 * sin(450*tau*t)' 1763 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1764 } 1765 1766 # emit a line using the symbol given, REPeated a number of times (1 by default) 1767 rep() { 1768 local what="${1}" 1769 local times="${2:-1}" 1770 [ "${times}" -gt 0 ] && printf "%${times}s\n" " " | 1771 if [ "${what}" = ' ' ]; then 1772 cat 1773 elif [ "${what}" = '-' ]; then 1774 sed "s/ /${what}/g" 1775 else 1776 sed "s- -${what}-g" 1777 fi 1778 } 1779 1780 # reflow/trim lines of prose (text) to improve its legibility: it's especially 1781 # useful when the text is pasted from web-pages being viewed in reader mode 1782 reprose() { 1783 local w="${1:-80}" 1784 [ $# -gt 0 ] && shift 1785 awk 'FNR == 1 && NR > 1 { print "" } { print; fflush() }' "$@" | 1786 fold -s -w "$w" | sed -E 's- *\r?$--' 1787 } 1788 1789 # change color/style of lines, using my script `tl` 1790 restyle() { 1791 local style="$1" 1792 [ $# -gt 0 ] && shift 1793 tl "${style}(plain(l))" "$@" 1794 } 1795 1796 # play a ringtone-style sound lasting the number of seconds given, or for 1 1797 # second by default; uses my script `waveout` 1798 ring() { 1799 local f='sin(2048 * tau * t) * exp(-50 * (t%0.1))' 1800 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1801 } 1802 1803 # play a ringtone-style sound lasting the number of seconds given, or for 1 1804 # second by default; uses my script `waveout` 1805 ringtone() { 1806 local f='sin(2048 * tau * t) * exp(-50 * (t%0.1))' 1807 waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - 1808 } 1809 1810 # Read-Only Micro (text editor) 1811 rom() { micro -readonly true "$@"; } 1812 1813 # Right STRIP ignores trailing spaces, as well as trailing carriage returns 1814 rstrip() { awk 1 "$@" | sed -E 's- *\r?$--'; } 1815 1816 # Right TRIM ignores trailing spaces, as well as trailing carriage returns 1817 rtrim() { awk 1 "$@" | sed -E 's- *\r?$--'; } 1818 1819 # show a RULER-like width-measuring line 1820 ruler() { 1821 [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed -E \ 1822 's- {5}-···· -g; s/(···· ){2}/····╵····│/g; s- -·-g; s-·{5}-····╵-' 1823 } 1824 1825 # RUN a command, after showing/anNOUNCE-ing it 1826 runnounce() { printf "\e[7m%s\e[0m\n" "$*" && "$@"; } 1827 1828 # run `sed` in extended mode, enabling its full regex syntax 1829 s() { sed -E -u "$@"; } 1830 1831 # voice-synthesize plain-text; only works on the windows subsystem for linux 1832 # say() { 1833 # awk 1 "$@" | powershell.exe -noprofile -command ' 1834 # Add-Type -AssemblyName System.Speech 1835 # $syn = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer 1836 # 1837 # foreach ($line in $Input) { 1838 # $syn.Speak($line) 1839 # }' | cat 1840 # } 1841 1842 # Silent CURL 1843 scurl() { curl -s "$@"; } 1844 1845 # show a unique-looking SEParator line; useful to run between commands 1846 # which output walls of text 1847 sep() { 1848 [ "${1:-80}" -gt 0 ] && 1849 printf "\e[48;5;253m%${1:-80}s\e[0m\n" " " | sed 's- -·-g' 1850 } 1851 1852 # webSERVE files in a folder as localhost, using the port number given, or 1853 # port 8080 by default 1854 serve() { 1855 # printf "\e[38;5;26mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 1856 printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 1857 python3 -m http.server "${1:-8080}" -d "${2:-.}" 1858 } 1859 1860 # SET DIFference 1861 setdif() { 1862 awk ' 1863 FNR == 1 { filenum++ } 1864 1865 { 1866 if (filenum == 1) { 1867 lines[++n] = $0 1868 } else { 1869 avoid[$0]++ 1870 } 1871 } 1872 1873 END { 1874 for (i = 1; i <= n; i++) { 1875 l = lines[i] 1876 if (avoid[l] == 0) { 1877 avoid[l] = 1 1878 print l 1879 } 1880 } 1881 }' "$@" 1882 } 1883 1884 # SET DIFFerence sorts its 2 inputs, then finds lines not in the 2nd input 1885 setdiff() { 1886 # comm -23 <(sort "$1") <(sort "$2") 1887 # dash doesn't support the process-sub syntax 1888 (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1889 } 1890 1891 # SET INtersection, sorts its 2 inputs, then finds common lines 1892 setin() { 1893 # comm -12 <(sort "$1") <(sort "$2") 1894 # dash doesn't support the process-sub syntax 1895 (sort "$1" | (sort "$2" | (comm -12 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1896 } 1897 1898 # SET SUBtraction sorts its 2 inputs, then finds lines not in the 2nd input 1899 setsub() { 1900 # comm -23 <(sort "$1") <(sort "$2") 1901 # dash doesn't support the process-sub syntax 1902 (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1903 } 1904 1905 # Show Files (and folders), coloring folders and links 1906 sf() { 1907 ls -al --file-type --color=never --time-style iso "$@" | awk ' 1908 /^d/ { printf "\x1b[38;5;33m%s\x1b[0m\n", $0; next } 1909 /^l/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; next } 1910 1' 1911 } 1912 1913 # SHOW a command, then RUN it 1914 showrun() { printf "\e[7m%s\e[0m\n" "$*" && "$@"; } 1915 1916 # start from the line number given, skipping all previous ones 1917 sinceline() { tail -n +"${1:-1}" "${2:--}"; } 1918 1919 # skip the first n lines, or the 1st line by default 1920 skip() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } 1921 1922 # skip the first n bytes 1923 skipbytes() { tail -c +$(("$1" + 1)) "${2:--}"; } 1924 1925 # skip the last n lines, or the last line by default 1926 skiplast() { head -n -"${1:-1}" "${2:--}"; } 1927 1928 # skip the last n bytes 1929 skiplastbytes() { head -c -"$1" "${2:--}"; } 1930 1931 # skip the first n lines, or the 1st line by default 1932 skiplines() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } 1933 1934 # Styled LEAK runs my script `leak` 1935 sleak() { leak "$@"; } 1936 1937 # Show Latest Podcasts, using my scripts `podfeed` and `si` 1938 slp() { 1939 local title 1940 title="Latest Podcast Episodes as of $(date +'%F %T')" 1941 podfeed -title "${title}" "$@" | si 1942 } 1943 1944 # recursively find all files with fewer bytes than the number given 1945 smallfiles() { find "${2:-.}" -size -"$(echo "${1}" | sed 's-_--g')"c; } 1946 1947 # emit the first line as is, sorting all lines after that, using the 1948 # `sort` command, passing all/any arguments/options to it 1949 sortrest() { 1950 awk -v sort="sort $*" 'NR == 1 { print; fflush() } NR > 1 { print | sort }' 1951 } 1952 1953 # ignore leading spaces, trailing spaces, even runs of multiple spaces 1954 # in the middle of lines, as well as trailing carriage returns 1955 squeeze() { 1956 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { print; fflush() }' "$@" | 1957 sed -E 's-^ +--; s- +- -g; s- *\t *-\t-g; s- *\r?$--' 1958 } 1959 1960 # Show a command, then Run it 1961 sr() { printf "\e[7m%s\e[0m\n" "$*" && "$@"; } 1962 1963 # turn lines of Space-Separated Values into lines of Tab-Separated Values 1964 ssv2tsv() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?--; s- +-\t-g'; } 1965 1966 # change color/style of lines, using my script `tl` 1967 style() { 1968 local style="$1" 1969 [ $# -gt 0 ] && shift 1970 tl "${style}(plain(l))" "$@" 1971 } 1972 1973 # show a random command defined in `clam`, using `wat` from `clam` itself 1974 surprise() { 1975 wat "$(grep -E '^[a-z]+\(' "$(which clam)" | shuf -n 1 | sed -E 's-\(.*--')" 1976 } 1977 1978 # show a reverse-sorted tally of all lines read, where ties are sorted 1979 # alphabetically 1980 tally() { 1981 printf "value\ttally\n" 1982 awk ' 1983 { t[$0]++ } 1984 END { for (k in t) printf "%s\t%d\n", k, t[k] } 1985 ' "$@" | sort -t "$(printf "\t")" -rnk2 -k1d 1986 } 1987 1988 # Tab AWK: TSV-specific I/O settings for `awk` 1989 tawk() { awk -F "\t" -v OFS="\t" "$@"; } 1990 1991 # timestamp all lines, as soon as they come from the standard input 1992 timestamp() { 1993 ts '%Y-%m-%d %H:%M:%S' | 1994 sed -u 's-^-\x1b[48;5;255m\x1b[38;5;24m-; s- -\x1b[0m\t-2' 1995 } 1996 1997 title() { 1998 local title="${1:-no title given}" 1999 [ $# -gt 0 ] && shift 2000 printf "\x1b[7m%s\x1b[0m\n", "${title}" 2001 awk 1 "$@" 2002 } 2003 2004 # Transform Json, using an interactive Editor for the formula/expression 2005 tje() { 2006 case "$1" in 2007 =|-nil|--nil|-none|--none|-null|--null) 2008 [ $# -gt 0 ] && shift 2009 tj -nil "$(micro -readonly true -filetype python | leak --inv)" "$@" 2010 ;; 2011 *) 2012 tj "$(micro -readonly true -filetype python | leak --inv)" "$@";; 2013 esac 2014 } 2015 2016 # Transform Json Faster, by using pypy instead of standard python 2017 tjf() { pypy3 "$(which tj)" "$@"; } 2018 2019 # Transform Json (Node), using an interactive Editor for the expression 2020 tjne() { 2021 case "$1" in 2022 =|-nil|--nil|-none|--none|-null|--null) 2023 [ $# -gt 0 ] && shift 2024 tjn -nil "$(micro -readonly true -filetype js | leak --inv)" "$@" 2025 ;; 2026 *) 2027 tjn "$(micro -readonly true -filetype js | leak --inv)" "$@";; 2028 esac 2029 } 2030 2031 # Transform Lines, using an interactive Editor for the formula/expression 2032 tle() { 2033 case "$1" in 2034 =|-nil|--nil|-none|--none|-null|--null) 2035 [ $# -gt 0 ] && shift 2036 tl -nil "$(micro -readonly true -filetype python | leak --inv)" "$@" 2037 ;; 2038 *) 2039 tl "$(micro -readonly true -filetype python | leak --inv)" "$@";; 2040 esac 2041 } 2042 2043 # Transform Lines Faster, by using pypy instead of standard python 2044 tlf() { pypy3 "$(which tl)" "$@"; } 2045 2046 # Transform Lines (Node), using the Micro editor for the formula/expression 2047 tlne() { 2048 case "$1" in 2049 =|-nil|--nil|-none|--none|-null|--null) 2050 [ $# -gt 0 ] && shift 2051 tln -nil "$(micro -readonly true -filetype js | leak --inv)" "$@" 2052 ;; 2053 *) 2054 tln "$(micro -readonly true -filetype js | leak --inv)" "$@";; 2055 esac 2056 } 2057 2058 # show current date in a specifc format, which is both people-friendly 2059 # and machine/tool/search/automation-friendly 2060 today() { date +'%Y-%m-%d %a %b %d'; } 2061 2062 # get the first n lines, or 1 by default 2063 toline() { head -n "${1:-1}" "${2:--}"; } 2064 2065 # lowercase all ASCII symbols 2066 tolower() { awk '{ print tolower($0); fflush() }' "$@"; } 2067 2068 # show all files directly in the folder given, without looking any deeper 2069 topfiles() { 2070 local arg 2071 for arg in "${@:-.}"; do 2072 find "${arg}" -maxdepth 1 -type f 2073 done 2074 } 2075 2076 # show all folders directly in the folder given, without looking any deeper 2077 topfolders() { 2078 local arg 2079 for arg in "${@:-.}"; do 2080 find "${arg}" -maxdepth 1 -type d | awk 'NR > 1' 2081 done 2082 } 2083 2084 # uppercase all ASCII symbols 2085 toupper() { awk '{ print toupper($0); fflush() }' "$@"; } 2086 2087 # ignore leading/trailing spaces, as well as trailing carriage returns 2088 trim() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?$--'; } 2089 2090 # ignore trailing spaces, as well as trailing carriage returns 2091 trimend() { awk 1 "$@" | sed -E 's- *\r?$--'; } 2092 2093 # ignore trailing spaces, as well as trailing carriage returns 2094 trimends() { awk 1 "$@" | sed -E 's- *\r?$--'; } 2095 2096 # ignore trailing spaces, as well as trailing carriage returns 2097 trimtrail() { awk 1 "$@" | sed -E 's- *\r?$--'; } 2098 2099 # ignore trailing spaces, as well as trailing carriage returns 2100 trimtrails() { awk 1 "$@" | sed -E 's- *\r?$--'; } 2101 2102 # try running a command, emitting an explicit message to standard-error 2103 # if the command given fails 2104 try() { 2105 "$@" || { 2106 printf "\n\e[31mrunning \e[41m\e[97m %s \e[0m\e[31m failed\e[0m\n" \ 2107 "$*" >&2 2108 return 255 2109 } 2110 } 2111 2112 # TimeStamp lines satisfying an AWK condition, ignoring all other lines 2113 tsawk() { 2114 awk -v line="\x1b[48;5;255m\x1b[38;5;24m%s\x1b[0m\t%s\n" \ 2115 -v time="%Y-%m-%d %H:%M:%S" \ 2116 "${1:-1} { printf line, strftime(time), \$0; fflush() }" 2117 } 2118 2119 # run my script `realign` using tab as the only field-separator, 2120 # which means the resulting TSV items can have spaces in them 2121 tsv2ssv() { realign --tsv "$@"; } 2122 2123 # TSV lines DEDUPlicated by the field given, keeping their original order 2124 tsvdedup() { 2125 local n="${1:-0}" 2126 [ $# -gt 0 ] && shift 2127 awk -F "\t" "!c[(\$${n} >= 0) ? \$${n} : \$(NF + ${n} - 1)]++" "$@" 2128 } 2129 2130 # Unique deduplicates lines, keeping them in their original order 2131 u() { awk '!c[$0]++' "$@"; } 2132 2133 # UNdo (decode) BASE-64-encoding of data 2134 unbase64() { base64 -d "$@"; } 2135 2136 # convert away from CSV tables, using my script `decsv` 2137 uncsv() { decsv "$@"; } 2138 2139 # UNcompress GZip-encoded data 2140 ungz() { zcat "$@"; } 2141 2142 # UNcompress GZIP-encoded data 2143 ungzip() { zcat "$@"; } 2144 2145 # deduplicate lines, keeping them in their original order 2146 unique() { awk '!c[$0]++' "$@"; } 2147 2148 # concatenate all named input sources unix-style: all trailing CRLFs become 2149 # single LFs, each non-empty input will always end in a LF, so lines from 2150 # different sources are accidentally joined; also leading UTF-8 BOMs on the 2151 # first line of each input are ignored, as those are useless at best 2152 unixify() { 2153 awk ' 2154 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 2155 { print; fflush() }' "$@" | sed -E 's-\r$--' 2156 } 2157 2158 # turn json lines into a proper json array 2159 unjsonl() { jq -s -M "${@:-.}"; } 2160 2161 # turn lines of Space-Separated Values into lines of Tab-Separated Values 2162 unssv() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?--; s- +-\t-g'; } 2163 2164 # convert away from TSV tables, using my script `detsv` 2165 untsv() { detsv "$@"; } 2166 2167 # turn UTF-16 data into UTF-8 2168 unutf16() { iconv -f utf16 -t utf8 "$@"; } 2169 2170 # Unify Output by merging stderr into stdout, without any keyboard-dancing 2171 uo() { "$@" 2>&1; } 2172 2173 # go UP n folders, or go up 1 folder by default 2174 up() { 2175 if [ "${1:-1}" -le 0 ]; then 2176 cd . 2177 return $? 2178 fi 2179 2180 cd "$(printf "%${1:-1}s" " " | sed 's- -../-g')" || return $? 2181 } 2182 2183 # UPPERcase all ASCII symbols 2184 upper() { awk '{ print toupper($0); fflush() }' "$@"; } 2185 2186 # UPPERCASE all ASCII symbols 2187 uppercase() { awk '{ print toupper($0); fflush() }' "$@"; } 2188 2189 # View with `less` 2190 v() { less -JMKiCRS "$@"; } 2191 2192 # run a command, showing its success/failure right after 2193 verdict() { 2194 local code 2195 2196 "$@" 2197 code=$? 2198 2199 if [ "${code}" -eq 0 ]; then 2200 printf "\n\e[38;5;29m%s \e[48;5;29m\e[97m succeeded \e[0m\n" "$*" >&2 2201 else 2202 printf "\n\e[31m%s \e[41m\e[97m failed with error code %d \e[0m\n" \ 2203 "$*" "${code}" >&2 2204 fi 2205 2206 return "${code}" 2207 } 2208 2209 # View with Header runs `less` without line numbers, with ANSI styles, with 2210 # no line-wrapping, and using the first line as a sticky-header, so it always 2211 # shows on top 2212 vh() { less --header=1 -JMKiCRS "$@"; } 2213 2214 # View Nice Hexadecimals; uses my script `nh` 2215 vnh() { nh "$@" | less -JMKiCRS; } 2216 2217 # View Nice Json / Very Nice Json; uses my scripts `nj` and `nn` 2218 vnj() { nj "$@" | nn | less -JMKiCRS; } 2219 2220 # View Nice Table / Very Nice Table; uses my scripts `nt` and `nn` 2221 vnt() { 2222 awk 1 "$@" | nl -b a -w 1 -v 0 | nt | nn | 2223 awk '(NR - 1) % 5 == 1 && NR > 1 { print "" } { print; flush() }' | 2224 less -JMKiCRS 2225 } 2226 2227 # turn UTF-8 into its latin-like subset, where variants of latin letters stay 2228 # as given, and where all other symbols become question marks, one question 2229 # mark for each code-point byte; the name comes from `vulgarization`, which 2230 # refers to the mutation of languages away from latin, during the middle ages 2231 vulgarize() { 2232 iconv -f utf-8 -t latin-1//translit "$@" | iconv -f latin-1 -t utf-8 2233 } 2234 2235 # View Zoom Json, using my scripts `zj`, `nj`, and `nn` 2236 vzj() { zj "$@" | nj | nn | less -JMKiCRS; } 2237 2238 # What Are These (?) shows what the names given to it are/do 2239 wat() { 2240 local a 2241 local code=0 2242 2243 for a in "$@"; do 2244 # printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "$a" 2245 printf "\e[48;5;253m%-80s\e[0m\n" "$a" 2246 # printf "\e[7m%-80s\e[0m\n" "$a" 2247 2248 # resolve 1 alias level 2249 if alias "$a" 2> /dev/null > /dev/null; then 2250 a="$(alias "$a" | sed "s-.*=--; s-['\"]--g")" 2251 fi 2252 2253 if echo "$a" | grep -E '[^ ]+ +[^ ]+' > /dev/null; then 2254 # resolved aliases with args/spaces in them would otherwise fail 2255 echo "$a" 2256 elif type "$a" > /dev/null 2> /dev/null; then 2257 # dash doesn't support `declare`, and `type` in bash/zsh emits 2258 # a redundant first output line, when it's a shell function 2259 type "$a" | awk ' 2260 NR == 1 && /^[a-z0-9_-]+ is a function$/ { skipped = $0; next } 2261 1 2262 END { if (NR < 2 && skipped) print skipped } 2263 ' 2264 else 2265 printf "\e[31m%s not found\e[0m\n" "$a" 2266 code=1 2267 fi 2268 done 2269 2270 return "${code}" 2271 } 2272 2273 # Word-Count with Nice numbers runs `wc` and colors results with my script 2274 # `nn`, alternating styles to make long numbers easier to read 2275 wcn() { wc "$@" | nn; } 2276 2277 # Word-Count Plus runs `wc` and enriches its output; uses my scripts `nn` 2278 # and `cext` 2279 wcp() { 2280 wc "$@" | sort -rn | nn | cext | 2281 awk '{ printf "%6d %s\n", NR - 1, $0; fflush() }' 2282 } 2283 2284 # get weather forecasts, almost filling the terminal's current width 2285 weather() { 2286 finger "${*}~$(($(tput cols) - 2))@graph.no" | 2287 sed -E 's/-/@/g; s/^ +@=/ -=/; s/=@ *$/=-/' | 2288 grep -v '^\[' 2289 } 2290 2291 # WHAT are these (?) shows what the names given to it are/do 2292 what() { 2293 local a 2294 local code=0 2295 2296 for a in "$@"; do 2297 # printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "$a" 2298 printf "\e[48;5;253m%-80s\e[0m\n" "$a" 2299 # printf "\e[7m%-80s\e[0m\n" "$a" 2300 2301 # resolve 1 alias level 2302 if alias "$a" 2> /dev/null > /dev/null; then 2303 a="$(alias "$a" | sed "s-.*=--; s-['\"]--g")" 2304 fi 2305 2306 if echo "$a" | grep -E '[^ ]+ +[^ ]+' > /dev/null; then 2307 # resolved aliases with args/spaces in them would otherwise fail 2308 echo "$a" 2309 elif type "$a" > /dev/null 2> /dev/null; then 2310 # dash doesn't support `declare`, and `type` in bash/zsh emits 2311 # a redundant first output line, when it's a shell function 2312 type "$a" | awk ' 2313 NR == 1 && /^[a-z0-9_-]+ is a function$/ { skipped = $0; next } 2314 1 2315 END { if (NR < 2 && skipped) print skipped } 2316 ' 2317 else 2318 printf "\e[31m%s not found\e[0m\n" "$a" 2319 code=1 2320 fi 2321 done 2322 2323 return "${code}" 2324 } 2325 2326 # recursively find all files with trailing spaces/CRs 2327 wheretrails() { rg -c '[ \r]+$' "${@:-.}"; } 2328 2329 # recursively find all files with trailing spaces/CRs 2330 whichtrails() { rg -c '[ \r]+$' "${@:-.}"; } 2331 2332 # What Is This (?) shows what the names given to it are/do 2333 wit() { 2334 local a 2335 local code=0 2336 2337 for a in "$@"; do 2338 # printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "$a" 2339 printf "\e[48;5;253m%-80s\e[0m\n" "$a" 2340 # printf "\e[7m%-80s\e[0m\n" "$a" 2341 2342 # resolve 1 alias level 2343 if alias "$a" 2> /dev/null > /dev/null; then 2344 a="$(alias "$a" | sed "s-.*=--; s-['\"]--g")" 2345 fi 2346 2347 if echo "$a" | grep -E '[^ ]+ +[^ ]+' > /dev/null; then 2348 # resolved aliases with args/spaces in them would otherwise fail 2349 echo "$a" 2350 elif type "$a" > /dev/null 2> /dev/null; then 2351 # dash doesn't support `declare`, and `type` in bash/zsh emits 2352 # a redundant first output line, when it's a shell function 2353 type "$a" | awk ' 2354 NR == 1 && /^[a-z0-9_-]+ is a function$/ { skipped = $0; next } 2355 1 2356 END { if (NR < 2 && skipped) print skipped } 2357 ' 2358 else 2359 printf "\e[31m%s not found\e[0m\n" "$a" 2360 code=1 2361 fi 2362 done 2363 2364 return "${code}" 2365 } 2366 2367 # emit each word-like item from each input line on its own line 2368 words() { awk '{ for (i = 1; i <= NF; i++) print $i; fflush() }' "$@"; } 2369 2370 # Where Trails recursively find all files with trailing spaces/CRs 2371 wt() { rg -c '[ \r]+$' "${@:-.}"; } 2372 2373 # run `xargs`, using zero/null bytes as the extra-arguments terminator 2374 x0() { xargs -0 "$@"; } 2375 2376 # run `xargs`, using whole lines as extra arguments 2377 xl() { xargs -d '\n' "$@"; } 2378 2379 # Youtube Audio Player 2380 yap() { 2381 local url 2382 # some youtube URIs end with extra playlist/tracker parameters 2383 url="$(echo "$1" | sed 's-&.*--')" 2384 # mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" 2385 mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" 2386 } 2387 2388 # Youtube Download 2389 yd() { yt-dlp "$@"; } 2390 2391 # Youtube Download AAC audio 2392 ydaac() { yt-dlp -f 140 "$@"; } 2393 2394 # Youtube Download MP4 video 2395 ydmp4() { yt-dlp -f 22 "$@"; } 2396 2397 # show a calendar for the current year, or for the year given 2398 year() { 2399 # debian linux has a different `cal` app which highlights the day 2400 if [ -e "/usr/bin/ncal" ]; then 2401 ncal -C -y "$@" 2402 else 2403 cal -y "$@" 2404 fi 2405 } 2406 2407 # show the current date in the YYYY-MM-DD format 2408 ymd() { date +'%Y-%m-%d'; } 2409 2410 # Zoom Json Nice, using my scripts `zj` and `nj` 2411 # zjn() { zj "$@" | nj; } 2412 2413 # Zoom Json Nice, using my scripts `zj`, `nj`, and `nn` 2414 zjn() { zj "$@" | nj | nn; }