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 # Command-Line Augmentation Module: get the best out of your shell 28 # 29 # This is a collection of arguably useful shell functions and shortcuts: 30 # some of these can be real time/effort savers, letting you concentrate 31 # on getting things done. 32 # 33 # You're supposed to `source` this script, so its definitions stay for 34 # your whole shell session: for that, you can run `source clam` or 35 # `. clam` (no quotes either way), either directly or at shell startup. 36 # 37 # Most of these shell functions rely on tools which are almost always 38 # available. The functions depending on tools which are least-likely 39 # pre-installed are 40 # 41 # cancur curl 42 # cgt go 43 # crg rg 44 # cs bat 45 # csf bat 46 # dic curl 47 # dict curl 48 # fetch curl 49 # fetchjson curl 50 # get curl 51 # getjson curl 52 # gobs go 53 # gobwingui go 54 # godep go 55 # goimp go 56 # h hat my own `HAndy Tools` app; commented out 57 # hmr bat 58 # j2 python commented out 59 # jc python uses non-built-in package; commented out 60 # joke curl 61 # lab book my own script; commented out 62 # plin mpv 63 # qurl curl 64 # serve python 65 # wheretrails rg 66 # whichtrails rg 67 # yap mpv, yt-dlp 68 # yd yt-dlp 69 # ydaac yt-dlp 70 # ydmp4 yt-dlp 71 # 72 # 73 # Full list of funcs/commands added 74 # 75 # a run Awk 76 # allfiles show all files in a folder, digging recursively 77 # allfolders show all folders in a folder, digging recursively 78 # args emit each argument given to it on its own line 79 # avoid ignore lines matching the regex given 80 # banner show words/arguments given as a styled line 81 # bar emit a styled bar to visually separate different outputs 82 # begincsv emit a line with comma-separated values, then emit stdin lines 83 # begintsv emit a line with tab-separated values, then emit stdin lines 84 # bh Breathe Header makes lines breathe starting from the 2nd one 85 # bigfiles dig into a folder recursively for files of at least n bytes 86 # bl Breathe Lines regularly adds extra empty lines every few 87 # blawk process BLocks of non-empty lines with AWK 88 # bleak Blue LEAK helps you debug pipes, via colored stderr lines 89 # blow expand tabs into spaces, using the tabstop-width given 90 # blowtabs expand tabs into spaces, using the tabstop-width given 91 # breathe Breathe regularly adds extra empty lines every few 92 # c run `cat`, which is useful, despite claims to the contrary 93 # cancur get the latest exchange rates from the bank of canada as TSV 94 # cap limit lines up to their first n bytes, line-feed excluded 95 # cawk Comma AWK runs AWK in CSV mode 96 # cgt Colored Go Test runs `go test`, making problems stand out 97 # chopdecs ignore trailing decimal zeros in numbers 98 # choplf ignore last byte if it's a line-feed 99 # cls CLear Screen 100 # coco COunt COndition, uses an AWK condition as its first arg 101 # crep Colored Regular Expression Printer runs `grep` in color-mode 102 # crg Colored RipGrep 103 # cs Color Syntax 104 # csf Color Syntax for a whole Folder, even its subfolders 105 # debase64 DEcode BASE64 bytes 106 # debom ignore leading utf-8 BOM markers for each input, when present 107 # decap DECAPitate emits the first n lines to stderr 108 # decrlf turn all CRLF byte-pairs into single line-feed bytes 109 # dedent ignore the first n leading spaces per line, or 4 by default 110 # dedup deduplicate lines, keeping them in their original order 111 # delay delay each input line, waiting the number of seconds given 112 # detab expand tabs into spaces, using the tabstop-width given 113 # dic lookup words using an online dictionary 114 # dict lookup words using an online dictionary 115 # div divide 2 numbers both ways/orders 116 # dt show the current Date and Time, and the 3 `current` months 117 # each run command using each stdin line as a stdin-redirected file 118 # eg Extended Grep 119 # emptyfiles dig into a folder recursively to show all empty files in it 120 # emptyfolders dig into a folder recursively to show all empty folders in it 121 # enum enumerate all lines, starting from 1 122 # enum1 enumerate all lines, starting from 1 123 # enum0 enumerate all lines, starting from 0 124 # except ignore lines matching the regex given 125 # f show all Files in a folder, digging recursively 126 # fail show an error message and fail 127 # fetch get/fetch bytes from the URI given 128 # fetchjson asks webserver at URI given to respond with a JSON payload 129 # findtargets show/find all targets from makefiles 130 # first keep only the first n lines, or just the first 1 by default 131 # flawk First Line AWK, and lines satisfying the optional condition 132 # forever keep (re)running the command given, until forced to quit 133 # g run Grep (extended) 134 # get get/fetch bytes from the URI given 135 # gethelp show help for the command given, using common help options 136 # getjson asks webserver at URI given to respond with a JSON payload 137 # get try to get/fetch JSON data from the URI given 138 # gleak Green LEAK helps you debug pipes, via colored stderr lines 139 # glue join all input lines using the separator/joiner string given 140 # gobs GO Build Stripped 141 # gobwingui GO Build WINdows GUI 142 # godep GO DEPendencies from the folder given 143 # goimp GO IMPorts from the folder given 144 # gsub run the awk function of the same name 145 # h run my own `hat` (HAndy Tools) cmd-line app; disabled 146 # hawk highlight lines matching the awk condition given 147 # helpless view most apps' help messages using `less` 148 # hl Header Less runs `less` and always shows the 1st line on top 149 # hmr Help Me Remember one of these commands 150 # hv Header View runs `less` and always shows the 1st line on top 151 # indent indent text the number of spaces given, or 4 by default 152 # j2 reformat JSON into multiple lines with 2-space indent levels 153 # jc shortcut to run `jc`, a useful data-to-JSON python module 154 # joke show a `Dad Joke` from the web, sometimes even a funny one 155 # l run `less`, enabling line numbers, scrolling, and ANSI styles 156 # lab layout content like a book; disabled; uses my own `book` app 157 # largs run `xargs` taking the extra arguments from whole stdin lines 158 # last keep only the last n lines, or just the last 1 by default 159 # leak emit input lines both to stdout and stderr, to debug pipes 160 # leako LEAK Orange helps you debug pipes, via colored stderr lines 161 # least run `less`, enabling line numbers, scrolling, and ANSI styles 162 # lf List Files, coloring folders and links 163 # lh Less with Header runs `less` and always shows the 1st line 164 # lineup regroup adjacent lines into n-item tab-separated lines 165 # listfiles list files, coloring folders and links 166 # loser LOcal SERver webserves files in a folder as localhost 167 # lower lowercase all lines 168 # m case-sensitively match the extended regex given 169 # match case-sensitively match the extended regex given 170 # merrge redirect stderr into stdout, without any ugly finger-dancing 171 # n Number all lines, starting from 1 172 # n0 Number all lines, starting from 0, instead of 1 173 # n1 Number all lines, starting from 1 174 # narrow try to limit lines up to n symbols per line, or 80 by default 175 # nfs Nice File Sizes 176 # nil emit nothing to stdout and/or discard everything from stdin 177 # noerr ignore stderr, without any ugly finger-dancing 178 # now show the current date and time 179 # nth keep only the n-th line from the input, if it has enough lines 180 # oleak Orange LEAK helps you debug pipes, via colored stderr lines 181 # p Plain ignores ANSI terminal styling 182 # pawk Print AWK expressions 183 # plain ignore all ANSI styles 184 # plainend reset ANSI styles at the end of each line 185 # plin PLay INput handles playable/multimedia streams from stdin 186 # qurl Quiet cURL 187 # reprose reflow/trim lines of prose (text) for improved legibility 188 # repstr REPeat a STRing n times, or 80 times by default 189 # runin run a command in the folder given 190 # s run Sed (extended) 191 # sep emit a unique-looking separator line 192 # serve start a local webserver from the current folder 193 # setdiff find the SET DIFFerence, after sorting its 2 inputs 194 # setin SET INtersection, sorts its 2 inputs, then finds common lines 195 # setsub find the SET SUBtraction, after sorting its 2 inputs 196 # showrun show a command, then run it 197 # skip skip the first n lines, or the first 1 by default 198 # skipfirst skip the first n lines, or the first 1 by default 199 # skiplast ignore the last n lines, or the very last 1 by default 200 # smallfiles dig into a folder recursively for files under n bytes 201 # sosi reverse-SOrted SIzes of the files given 202 # squeeze aggressively get rid of extra spaces on every line 203 # ssv2tsv turn each run of 1+ spaces into a single tab 204 # stripe underline every 5th line 205 # t Trim trailing spaces and carriage-returns 206 # tawk Tab AWK, runs AWK using tab as its IO item-separator 207 # today show current date in a way friendly both to people and tools 208 # topfiles show all files directly in a folder, without recursion 209 # topfolders show all folders directly in a folder, without recursion 210 # trim get rid of leading and trailing spaces on lines 211 # trimprefix ignore the prefix given from input lines which start with it 212 # trimsuffix ignore the suffix given from input lines which end with it 213 # trimtrail get rid of trailing spaces on lines 214 # trimtrails get rid of trailing spaces on lines 215 # try try running the command given 216 # tsawk TimeStamp lines satisfying AWK condition, ignoring the rest 217 # u Unixify ensures plain-text lines are unix-like 218 # unbase64 decode base64 bytes 219 # unique deduplicate lines, keeping them in their original order 220 # unixify ensure plain-text lines are unix-like 221 # uncrlf turn all CRLF byte-pairs into single line-feed bytes 222 # untab expand tabs into spaces, using the tabstop-width given 223 # up go UP n folders, or go up 1 folder by default 224 # upsidedown emit input lines in reverse order, or last to first 225 # ut Underline Table underlines the 1st line, then 1 line every 5 226 # v View runs `less`, enabling scrolling and ANSI styles 227 # verdict run a command, showing its success/failure right after 228 # vh View with Header runs `less` and always shows the 1st line 229 # wat What Are These (?) shows what the names given to it do 230 # wheretrails find all files which have trailing spaces/CRs on their lines 231 # whichtrails find all files which have trailing spaces/CRs on their lines 232 # wit What Is This (?) shows what the name given to it does 233 # yap Youtube Audio Player 234 # yd Youtube Download 235 # ydaac Youtube Download AAC audio 236 # ydmp4 Youtube Download MP4 video 237 # year show a full calendar for the current year, or the year given 238 # ymd show the current date in the YYYY-MM-DD format 239 240 241 # handle help options 242 case "$1" in 243 -h|--h|-help|--help) 244 # show help message, extracting the info-comment at the start 245 # of this file, and quit 246 awk '/^# +clam/, /^$/ { gsub(/^# ?/, ""); print }' "$0" 247 exit 0 248 ;; 249 esac 250 251 252 # use a simple shell prompt 253 # PS1="\$ " 254 # PS2="> " 255 256 # use a simple shell prompt, showing the current folder in the title 257 # PS1="\[\e]0;\w\a\]\$ " 258 # PS2="> " 259 260 # prevent `less` from saving searches/commands 261 # LESSHISTFILE="-" 262 # LESSSECURE=1 263 264 # prevent the shell from saving commands 265 # unset HISTFILE 266 267 268 # dashed aliases of multi-word commands defined later 269 alias all-files='allfiles' 270 alias all-folders='allfolders' 271 alias begin-csv='begincsv' 272 alias begin-tsv='begintsv' 273 alias blow-tabs='blowtabs' 274 alias chop-decs='chopdecs' 275 alias chop-lf='choplf' 276 alias empty-files='emptyfiles' 277 alias empty-folders='emptyfolders' 278 alias fetch-json='fetchjson' 279 alias get-help='gethelp' 280 alias get-json='getjson' 281 alias help-for='gethelp' 282 alias help-less='helpless' 283 alias line-up='lineup' 284 alias list-files='listfiles' 285 alias plain-end='plainend' 286 alias rep-str='repstr' 287 alias run-in='runin' 288 alias set-diff='setdiff' 289 alias set-in='setin' 290 alias set-sub='setsub' 291 alias show-run='showrun' 292 alias skip-first='skipfirst' 293 alias skip-last='skiplast' 294 alias small-files='smallfiles' 295 alias ssv-to-tsv='ssv2tsv' 296 alias top-files='topfiles' 297 alias top-folders='topfolders' 298 alias trim-prefix='trimprefix' 299 alias trim-suffix='trimsuffix' 300 alias trim-trail='trimtrail' 301 alias trim-trails='trimtrails' 302 alias ts-awk='tsawk' 303 alias upside-down='upsidedown' 304 alias where-trails='wheretrails' 305 alias which-trails='whichtrails' 306 alias ydaac='ydaac' 307 alias ydmp4='ydmp4' 308 309 # undashed aliases for commands defined later 310 alias bocler='cancur' 311 312 313 # run Awk 314 a() { 315 awk "$@" 316 } 317 318 # show all files in a folder, digging recursively 319 allfiles() { 320 local arg 321 for arg in "${@:-.}"; do 322 find "${arg}" -type f 323 done 324 } 325 326 # show all folders in a folder, digging recursively 327 allfolders() { 328 local arg 329 for arg in "${@:-.}"; do 330 find "${arg}" -type d | awk 'NR > 1' 331 done 332 } 333 334 # emit each argument given as its own line of output 335 args() { 336 awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" 337 } 338 339 # avoid lines matching the regex given, or avoid empty(ish) lines by default 340 avoid() { 341 regex="${1:-[^ *]\r?$}" 342 shift 343 grep -E -v "${regex}" "$@" 344 } 345 346 # show a line which clearly labels part of a shell session 347 banner() { 348 # printf "\x1b[7m%-80s\x1b[0m\n" "$*" 349 printf "\x1b[48;5;253m%-80s\x1b[0m\n" "$*" 350 } 351 352 # emit a colored bar which can help visually separate different outputs 353 bar() { 354 printf "\x1b[48;5;253m%${1:-80}s\x1b[0m\n" " " 355 } 356 357 # emit a line with comma-separated values, then emit all stdin lines 358 begincsv() { 359 awk 'BEGIN { 360 for (i = 1; i < ARGC; i++) { 361 if (i > 1) printf "," 362 printf "%s", ARGV[i] 363 delete ARGV[i] 364 } 365 if (ARGC > 0) printf "\n" 366 } 367 1' "$@" 368 } 369 370 # emit a line with tab-separated values, then emit all stdin lines 371 begintsv() { 372 awk 'BEGIN { 373 for (i = 1; i < ARGC; i++) { 374 if (i > 1) printf "\t" 375 printf "%s", ARGV[i] 376 delete ARGV[i] 377 } 378 if (ARGC > 0) printf "\n" 379 } 380 1' "$@" 381 } 382 383 # Breathe Header: add an empty line after the first one (the header), 384 # then separate groups of 5 lines with empty lines between them 385 bh() { 386 awk '(NR - 1) % 5 == 1 && NR > 1 { print "" } 1' "$@" 387 } 388 389 # dig into a folder recursively for files of at least n bytes 390 bigfiles() { 391 find "${2:-.}" -type f -size "${1:-1000000}"c -o -size +"${1:-1000000}"c 392 } 393 394 # Breathe Lines: separate groups of 5 lines with empty lines 395 bl() { 396 awk 'NR % 5 == 1 && NR != 1 { print "" } 1' "$@" 397 } 398 399 # process BLocks of non-empty lines with AWK 400 blawk() { 401 awk -F='' -v RS='' "$@" 402 } 403 404 # Blue LEAK emits/tees input both to stdout and stderr, coloring blue what 405 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` 406 # pipes involving several steps 407 bleak() { 408 awk ' 409 { 410 print 411 fflush() 412 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;5;26m") 413 printf "\x1b[38;5;26m%s\x1b[0m\n", $0 > "/dev/stderr" 414 fflush("/dev/stderr") 415 }' 416 } 417 418 # expand tabs into spaces using the tabstop given 419 blow() { 420 local tabstop 421 tabstop="${1:-4}" 422 shift 423 expand -t "${tabstop}" "$@" 424 } 425 426 # expand tabs into spaces using the tabstop given 427 blowtabs() { 428 local tabstop 429 tabstop="${1:-4}" 430 shift 431 expand -t "${tabstop}" "$@" 432 } 433 434 # separate groups of 5 lines with empty lines, making text-rows much 435 # easier to follow/eye-scan along, especially with tall walls of text 436 breathe() { 437 awk 'NR % 5 == 1 && NR != 1 { print "" } 1' "$@" 438 } 439 440 # `cat` can be useful, despite claims to the contrary 441 c() { 442 cat "$@" 443 } 444 445 # CANadian CURrencies emits the Bank Of Canada's Latest Exchange Rates as a 446 # 2-line table of tab-separated values, where the first line is the header 447 cancur() { 448 local b 449 b='https://www.bankofcanada.ca/valet/observations/group/FX_RATES_DAILY/' 450 # starting `3 days ago` ensures this works even on weekends, while 451 # minimizing the data transmitted 452 curl -s "${b}csv?start_date=$(date -d '3 days ago' +'%Y-%m-%d')" | 453 # pick the header line, along with the last one, turning CSV into TSV 454 awk '/^"date"/; END { print }' | tr -d '"' | tr ',' '\t' | 455 # simplify/change most column names 456 sed 's-FX--g; s-CAD--g' 457 } 458 459 # limit lines up to their first n bytes (80 by default), line-feed excluded 460 cap() { 461 local n 462 n="${1:-80}" 463 shift 464 awk -v n="${n}" '{ print substr($0, 1, n) }' "$@" 465 } 466 467 # Comma AWK: run awk in CSV mode 468 cawk() { 469 awk --csv "$@" 470 } 471 472 # Colored Go Test on the folder given 473 cgt() { 474 go test "${1:-.}" 2>&1 | awk ' 475 /^ok/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; fflush() } 476 /^[-]* ?FAIL/ { printf "\x1b[38;5;1m%s\x1b[0m\n", $0; fflush() } 477 /^\?/ { printf "\x1b[38;5;249m%s\x1b[0m\n", $0; fflush() }' 478 } 479 480 # ignore trailing decimal zeros in numbers 481 chopdecs() { 482 awk '{ 483 for (i = 1; i <= NF; i++) { 484 gsub(/(\.[0-9]+[1-9]+)0+$/, "&1") 485 gsub(/([0-9]+)\.0*$/, "&1") 486 } 487 print 488 }' "$@" 489 } 490 491 # ignore final life-feed from text, if it's the very last byte; all 492 # carriage-returns in CRLF byte-pairs are also ignored 493 choplf() { 494 awk 'NR > 1 { print "" } { printf "%s", $0 }' "$@" 495 } 496 497 # CLear Screen 498 cls() { 499 clear 500 } 501 502 # COunt COndition: count how many times the AWK expression given is true 503 coco() { 504 local cond 505 cond="${1:-1}" 506 shift 507 awk "${cond} { c++ } END { print c }" "$@" 508 } 509 510 # Colored Regular Expression Printer runs `grep` to shows colored matches 511 crep() { 512 grep --color=always "$@" 513 } 514 515 # Colored RipGrep: ensures app `rg` emits colors when piped 516 crg() { 517 rg --color=always "$@" 518 } 519 520 # Color Syntax: run syntax-coloring app `bat` without line-wrapping 521 cs() { 522 local cmd 523 cmd="bat" 524 # debian linux uses a different name for the `bat` app 525 if [ -e "/usr/bin/batcat" ]; then 526 cmd="batcat" 527 fi 528 529 "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ 530 --wrap=never --color=always "$@" | 531 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -KiCRS 532 } 533 534 # Color Syntax of all files in a Folder, showing line numbers 535 csf() { 536 local cmd 537 cmd="bat" 538 # debian linux uses a different name for the `bat` app 539 if [ -e "/usr/bin/batcat" ]; then 540 cmd="batcat" 541 fi 542 543 find "${1:-.}" -type f -print0 | xargs --null "$cmd" \ 544 --style=plain,header,numbers --theme='Monokai Extended Light' \ 545 --wrap=never --color=always | 546 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -KiCRS 547 } 548 549 # DEcode BASE64 bytes 550 debase64() { 551 base64 -d "$@" 552 } 553 554 # ignore leading utf-8 BOM markers for each input, when present; CRLF 555 # byte-pairs are also turned into single LF bytes 556 debom() { 557 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" 558 } 559 560 # DECAPitate emits the first n lines to stderr, and the rest to stdout; 561 # the name is a reference to the standard cmd-line app `head`, which 562 # emits only the first n lines to stdout 563 decap() { 564 local n 565 n="${1:-1}" 566 shift 567 568 awk -v n="${n}" ' 569 BEGIN { 570 if (n !~ /^-?[0-9]+$/) { 571 fmt = "leading arg %s isn'\''t a valid line-count\n" 572 printf fmt, n > "/dev/stderr" 573 exit 1 574 } 575 } 576 577 NR <= n { print > "/dev/stderr"; next } 578 1' "$@" 579 } 580 581 # turn all CRLF byte-pairs into single line-feed bytes 582 decrlf() { 583 cat "$@" | sed 's-\r$--' 584 } 585 586 # ignore up to n leading spaces for each line, or up to 4 by default 587 dedent() { 588 local upto 589 upto="${1:-4}" 590 shift 591 awk "{ gsub(/^ {0,${upto}}/, \"\"); print }" "$@" 592 } 593 594 # DEDUPlicate prevents lines from appearing more than once 595 dedup() { 596 awk '!c[$0]++' "$@" 597 } 598 599 # DEDUPlicatE prevents lines from appearing more than once 600 dedupe() { 601 awk '!c[$0]++' "$@" 602 } 603 604 # delay each input line, waiting the number of seconds given, or wait 605 # 1 second before each line by default 606 delay() { 607 local delay 608 local line 609 IFS='' # keep all spaces from lines 610 delay="${1:-1}" 611 shift 612 613 awk 1 "$@" | while read -r line; do 614 sleep "${delay}" 615 printf "%s\n" "${line}" 616 done 617 } 618 619 # expand tabs into spaces using the tabstop given 620 detab() { 621 local tabstop 622 tabstop="${1:-4}" 623 shift 624 expand -t "${tabstop}" "$@" 625 } 626 627 # DICtionary definitions, using an online service 628 dic() { 629 curl -s "dict://dict.org/d:$*" | awk ' 630 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 631 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 632 1' 633 } 634 635 # DICTionary definitions, using an online service 636 dict() { 637 curl -s "dict://dict.org/d:$*" | awk ' 638 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 639 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 640 1' 641 } 642 643 # divide 2 numbers both ways, also showing their proper complement 644 div() { 645 awk -v x="${1:-1}" -v y="${2:-1}" ' 646 BEGIN { 647 gsub(/_/, "", x) 648 gsub(/_/, "", y) 649 x = x + 0 650 y = y + 0 651 min = x < y ? x : y 652 max = x > y ? x : y 653 print x/y 654 print y/x 655 print 1 - min / max 656 exit 657 }' < /dev/null 658 } 659 660 # show the current Date and Time, and the 3 `current` months 661 dt() { 662 # debian linux has a different `cal` app which highlights the day 663 if [ -e "/usr/bin/ncal" ]; then 664 ncal -C -3 665 else 666 cal -3 667 fi 668 669 # show the current time center-aligned 670 # printf "%28s\x1b[34m%s\x1b[0m\n" " " "$(date +'%T')" 671 printf "%22s\x1b[32m%s\x1b[0m \x1b[34m%s\x1b[0m\n" " " \ 672 "$(date +'%a %b %d')" "$(date +'%T')" 673 } 674 675 # run command using each stdin line as a stdin-redirected file 676 each() { 677 local arg 678 while read -r arg; do 679 "$@" < "${arg}" 680 done 681 } 682 683 # Extended Grep 684 eg() { 685 grep -E "$@" 686 } 687 688 # dig into a folder recursively to show all empty files in it 689 emptyfiles() { 690 find "${1:-.}" -type f -empty 691 } 692 693 # dig into a folder recursively to show all empty folders in it 694 emptyfolders() { 695 find "${1:-.}" -type d -empty 696 } 697 698 # enumerate lines, starting from 1, and using a tab as the separator: 699 # even empty lines are counted, unlike with `nl` 700 enum() { 701 awk '{ printf "%d\t", NR; print }' "$@" 702 } 703 704 # enumerate lines, starting from 1, and using a tab as the separator: 705 # even empty lines are counted, unlike with `nl` 706 enum1() { 707 awk '{ printf "%d\t", NR; print }' "$@" 708 } 709 710 # enumerate lines, starting from 0, and using a tab as the separator: 711 # even empty lines are counted, unlike with `nl` 712 enum0() { 713 awk '{ printf "%d\t", NR - 1; print }' "$@" 714 } 715 716 # avoid lines matching the regex given, or avoid empty(ish) lines by default 717 except() { 718 local regex 719 regex="${1:-[^ *]\r?$}" 720 shift 721 grep -E -v "${regex}" "$@" 722 } 723 724 # show all Files in a folder, digging recursively 725 f() { 726 local arg 727 for arg in "${@:-.}"; do 728 find "${arg}" -type f 729 done 730 } 731 732 # show an error message and fail 733 fail() { 734 printf "\x1b[41m\x1b[38;5;15m %s \x1b[0m\n" "$*" >&2 && return 255 735 } 736 737 # get/fetch data from the URI given 738 fetch() { 739 curl -s "$@" || ( 740 printf "\x1b[31mcan't get %s\x1b[0m\n" "$@" >&2 741 return 1 742 ) 743 } 744 745 # asks webserver at URI given to respond with a JSON payload 746 fetchjson() { 747 curl -s "$@" -H 'Accept: application/json' || ( 748 printf "\x1b[31mcan't get JSON from %s\x1b[0m\n" "$@" >&2 749 return 1 750 ) 751 } 752 753 # show/find all targets from makefiles 754 findtargets() { 755 grep -E -i '^[a-z0-9_\.$-]+.*:' "${@:-Makefile}" 756 } 757 758 # get the first n lines, or 1 by default 759 first() { 760 head -n "${1:-1}" "${2:--}" 761 } 762 763 # First Line AWK, and lines satisfying the optional condition 764 flawk() { 765 local cond 766 cond="${1:-0}" 767 shift 768 awk "NR == 1 || ${cond}" "$@" 769 } 770 771 # keep (re)running the command given, until forced to quit, whether 772 # directly or indirectly 773 forever() { 774 while true; do 775 "$@" || return "$?" 776 done 777 } 778 779 # run Grep (extended) 780 g() { 781 grep -E "$@" 782 } 783 784 # get/fetch data from the URI given 785 get() { 786 curl -s "$@" || ( 787 printf "\x1b[31mcan't get %s\x1b[0m\n" "$@" >&2 788 return 1 789 ) 790 } 791 792 # view most apps' help messages using `less`; this command used to be 793 # called `helpless` 794 gethelp() { 795 "${1}" "${2:---help}" 2>&1 | less -KiCRS 796 } 797 798 # asks webserver at URI given to respond with a JSON payload 799 getjson() { 800 curl -s "$@" -H 'Accept: application/json' || ( 801 printf "\x1b[31mcan't get JSON from %s\x1b[0m\n" "$@" >&2 802 return 1 803 ) 804 } 805 806 # Green LEAK emits/tees input both to stdout and stderr, coloring green what 807 # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes 808 # involving several steps 809 gleak() { 810 awk ' 811 { 812 print 813 fflush() 814 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;5;29m") 815 printf "\x1b[38;5;29m%s\x1b[0m\n", $0 > "/dev/stderr" 816 fflush("/dev/stderr") 817 }' 818 } 819 820 # join all input lines using the separator/joiner string given; the name 821 # `join` is already taken by a standard(ish) cmd-line app 822 glue() { 823 local sep 824 sep="${1:-}" 825 shift 826 827 awk -v sep="${sep}" ' 828 NR > 1 { printf "%s", sep } 829 1 { printf "%s", $0 } 830 END { printf "\n" }' "$@" 831 } 832 833 # GO Build Stripped: a common use-case for the go compiler 834 gobs() { 835 go build -ldflags "-s -w" -trimpath "$@" 836 } 837 838 # GO Build WINdows GUI: a common use-case for the go compiler 839 gobwingui() { 840 go build -ldflags "-s -w -H=windowsgui" -trimpath "$@" 841 } 842 843 # GO DEPendencies: shows all dependencies in a go project 844 godep() { 845 go list -f '{{ join .Deps "\n" }}' "$@" 846 } 847 848 # GO IMPorts: show all imports in a go project 849 goimp() { 850 go list -f '{{ join .Imports "\n" }}' "$@" 851 } 852 853 # transform lines using AWK's gsub func (global substitute) 854 gsub() { 855 local what 856 local with 857 what="${1}" 858 shift 859 with="${1}" 860 shift 861 # awk "{ gsub(/${what}/, \"${with}\"); print }" "$@" 862 awk "{ gsub(${what}, \"${with}\"); print }" "$@" 863 } 864 865 # run my own `hat` (HAndy Tools) cmd-line app 866 # h() { 867 # hat "$@" 868 # } 869 870 # Highlight (lines) with AWK 871 hawk() { 872 local cond 873 cond="${1:-1}" 874 shift 875 876 awk " 877 ${cond} { 878 gsub(/\\x1b\\[0m/, \"\x1b[0m\\x1b[7m\") 879 printf \"\\x1b[7m%s\\x1b[0m\\n\", \$0 880 fflush() 881 next 882 } 883 884 { print; fflush() }" "$@" 885 } 886 887 # view most apps' help messages using `less` 888 helpless() { 889 "${1}" "${2:---help}" 2>&1 | less -KiCRS 890 } 891 892 # Header Less runs `less` with line numbers, ANSI styles, no line-wrapping, 893 # and using the first line as a sticky-header, so it always shows on top 894 hl() { 895 less --header=1 -KNiCRS "$@" 896 } 897 898 # Help Me Remember my custom shell commands 899 hmr() { 900 local cmd 901 cmd="bat" 902 # debian linux uses a different name for the `bat` app 903 if [ -e "/usr/bin/batcat" ]; then 904 cmd="batcat" 905 fi 906 907 "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ 908 --wrap=never --color=always "$(which clam)" | 909 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -KiCRS 910 } 911 912 # Header View runs `less` without line numbers, with ANSI styles, with no 913 # line-wrapping, and using the first line as a sticky-header, so it always 914 # shows on top 915 hv() { 916 less --header=1 -KiCRS "$@" 917 } 918 919 # indent each line the number of spaces given, or 4 spaces by default 920 indent() { 921 local n 922 n="${1:-4}" 923 shift 924 925 awk -v n="${n}" ' 926 BEGIN { 927 # for (i = 1; i <= n; i++) pre = pre " " 928 pre = " " 929 while (length(pre) < n) pre = pre pre 930 pre = substr(pre, 1, n) 931 } 932 933 { printf pre; print }' "$@" 934 } 935 936 # reformat JSON into multiple lines with 2-space indent levels 937 # j2() { 938 # cat "${1:--}" | python -c "#!/usr/bin/python3 939 # from json import load, dump 940 # from sys import exit, stderr, stdin, stdout 941 # try: 942 # seps = (', ', ': ') 943 # stdout.reconfigure(newline='\n', encoding='utf-8') 944 # dump(load(stdin), stdout, indent=2, allow_nan=False, separators=seps) 945 # stdout.write('\n') 946 # except Exception as e: 947 # print('\x1b[31m' + str(e) + '\x1b[0m', file=stderr) 948 # exit(1) 949 # " 950 # } 951 952 # Json Converter; uses python package `jc`, which isn't built-in 953 # jc() { 954 # python -m jc "$@" 955 # } 956 957 # show a `dad` JOKE from the web, some of which are even funny 958 joke() { 959 curl -s https://icanhazdadjoke.com | fold -s | sed -E 's- *\r?$--' 960 # plain-text output from previous cmd doesn't end with a line-feed 961 printf "\n" 962 } 963 964 # run `less` with line numbers, ANSI styles, and no line-wrapping 965 l() { 966 less -KNiCRS "$@" 967 } 968 969 # Like A Book groups lines as 2 side-by-side pages, the same way books 970 # do it; uses my own cmd-line app `book` 971 # lab() { 972 # book "$(($(tput lines) - 1))" "$@" | less -KiCRS 973 # } 974 975 # Line xARGS: `xargs` using line separators, which handles filepaths 976 # with spaces, as long as the standard input has 1 path per line 977 largs() { 978 xargs -d "\n" "$@" 979 } 980 981 # get the last n lines, or 1 by default 982 last() { 983 tail -n "${1:-1}" "${2:--}" 984 } 985 986 # leak emits/tees input both to stdout and stderr; useful in pipes 987 leak() { 988 tee /dev/stderr 989 } 990 991 # LEAK Orange emits/tees input both to stdout and stderr, coloring orange 992 # what it emits to stderr using an ANSI-style; this cmd is useful to `debug` 993 # pipes involving several steps 994 leako() { 995 awk ' 996 { 997 print 998 fflush() 999 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;5;166m") 1000 printf "\x1b[38;5;166m%s\x1b[0m\n", $0 > "/dev/stderr" 1001 fflush("/dev/stderr") 1002 }' 1003 } 1004 1005 # run `less` with line numbers, ANSI styles, and no line-wrapping 1006 least() { 1007 less -KNiCRS "$@" 1008 } 1009 1010 # List Files, coloring folders and links 1011 lf() { 1012 ls -al --color=never --time-style iso "$@" | awk ' 1013 /^d/ { printf "\x1b[38;5;33m%s\x1b[0m\n", $0; next } 1014 /^l/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; next } 1015 1 1016 ' 1017 } 1018 1019 # Less with Header runs `less` with line numbers, ANSI styles, no line-wraps, 1020 # and using the first line as a sticky-header, so it always shows on top 1021 lh() { 1022 less --header=1 -KNiCRS "$@" 1023 } 1024 1025 # regroup adjacent lines into n-item tab-separated lines 1026 lineup() { 1027 local n 1028 n="${1:-0}" 1029 shift 1030 1031 if [ "${n}" -le 0 ]; then 1032 awk ' 1033 NR > 1 { printf "\t" } 1034 { printf "%s", $0 } 1035 END { if (NR > 0) print "" }' "$@" 1036 return "$?" 1037 fi 1038 1039 awk -v n="${n}" ' 1040 NR % n != 1 { printf "\t" } 1041 { printf "%s", $0 } 1042 NR % n == 0 { print "" } 1043 END { if (NR % n != 0) print "" }' "$@" 1044 } 1045 1046 # list files, coloring folders and links 1047 listfiles() { 1048 ls -al --color=never --time-style iso "$@" | awk ' 1049 /^d/ { printf "\x1b[38;5;33m%s\x1b[0m\n", $0; next } 1050 /^l/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; next } 1051 1 1052 ' 1053 } 1054 1055 # LOcal SERver webserves files in a folder as localhost, using the port 1056 # number given, or port 8080 by default 1057 loser() { 1058 printf "\x1b[38;5;26mserving files in ${2:-$(pwd)}\x1b[0m\n" >&2 1059 python -m http.server "${1:-8080}" -d "${2:-.}" 1060 } 1061 1062 # make all text lowercase 1063 lower() { 1064 awk '{ print tolower($0) }' "$@" 1065 } 1066 1067 # match the regex given, or match non-empty(ish) lines by default 1068 m() { 1069 local regex 1070 regex="${1:-[^ *]\r?$}" 1071 shift 1072 grep -E "${regex}" "$@" 1073 } 1074 1075 # match the regex given, or match non-empty(ish) lines by default 1076 match() { 1077 local regex 1078 regex="${1:-[^ *]\r?$}" 1079 shift 1080 grep -E "${regex}" "$@" 1081 } 1082 1083 # merge stderr into stdout without any keyboard-dancing 1084 merrge() { 1085 "$@" 2>&1 1086 } 1087 1088 # Number all lines starting from 1, turning all CRLF into single LF bytes, 1089 # and ensuring lines aren't accidentally joined when changing input sources 1090 n() { 1091 # awk '{ printf "%6d %s\n", NR, $0; fflush() }' "$@" 1092 awk '{ printf "%d\t%s\n", NR, $0; fflush() }' "$@" 1093 } 1094 1095 # Number all lines starting from 0, turning all CRLF into single LF bytes, 1096 # and ensuring lines aren't accidentally joined when changing input sources 1097 n0() { 1098 # awk '{ printf "%6d %s\n", NR - 1, $0; fflush() }' "$@" 1099 awk '{ printf "%d\t%s\n", NR - 1, $0; fflush() }' "$@" 1100 } 1101 1102 # Number all lines starting from 1, turning all CRLF into single LF bytes, 1103 # and ensuring lines aren't accidentally joined when changing input sources 1104 n1() { 1105 # awk '{ printf "%6d %s\n", NR, $0; fflush() }' "$@" 1106 awk '{ printf "%d\t%s\n", NR, $0; fflush() }' "$@" 1107 } 1108 1109 # try to limit lines up to n symbols per line, or 80 by default 1110 narrow() { 1111 fold -s -w "${1:-80}" | sed -E 's- +$--' 1112 } 1113 1114 # Nice File Sizes 1115 nfs() { 1116 # turn arg-list into single-item lines 1117 awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" | 1118 # calculate file-sizes, and reverse-sort results 1119 xargs -d '\n' wc -c | sort -rn | 1120 # start output with a header-like line, and add a MiB field 1121 awk 'BEGIN { printf "%5s %9s %8s name\n", "n", "bytes", "MiB" } 1122 { printf "%5d %9d %8.2f %s\n", NR - 1, $1, $1 / 1048576, $2 }' | 1123 # make zeros in the MiB field stand out with a special color 1124 awk '{ gsub(/ 0.00 /, "\x1b[38;5;103m 0.00 \x1b[0m"); print }' | 1125 # make table breathe with empty lines, so tall outputs are readable 1126 awk '(NR - 2) % 5 == 1 && NR > 1 { print "" } 1' 1127 } 1128 1129 # emit nothing to output and/or discard everything from input 1130 nil() { 1131 if [ -p /dev/stdin ]; then 1132 cat > /dev/null 1133 else 1134 head -c 0 1135 fi 1136 } 1137 1138 # ignore stderr without any keyboard-dancing 1139 noerr() { 1140 "$@" 2> /dev/null 1141 } 1142 1143 # show the current date and time 1144 now() { 1145 date +'%Y-%m-%d %H:%M:%S' 1146 } 1147 1148 # keep only the nth line from the input, if it has at least that many lines 1149 nth() { 1150 local n 1151 n="${1}" 1152 shift 1153 awk -v n="${n}" 'BEGIN { if (n < 1) exit } NR == n { print; exit }' "$@" 1154 } 1155 1156 # Orange LEAK emits/tees input both to stdout and stderr, coloring orange 1157 # what it emits to stderr using an ANSI-style; this cmd is useful to `debug` 1158 # pipes involving several steps 1159 oleak() { 1160 awk ' 1161 { 1162 print 1163 fflush() 1164 gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;5;166m") 1165 printf "\x1b[38;5;166m%s\x1b[0m\n", $0 > "/dev/stderr" 1166 fflush("/dev/stderr") 1167 }' 1168 } 1169 1170 # Plain ignores ANSI terminal styling 1171 p() { 1172 awk ' 1173 { 1174 # ignore notifications (code 9) and hyperlinks (code 8) 1175 gsub(/\x1b\](8|9);[^\x07]*\x07/, "") 1176 # ignore cursor-movers and style-changers 1177 gsub(/\x1b\[([0-9]*[A-HJKST]|[0-9;]*m)/, "") 1178 1179 print 1180 fflush() 1181 }' "$@" 1182 } 1183 1184 # Print AWK expressions 1185 pawk() { 1186 local arg 1187 local shown 1188 for arg in "$@"; do 1189 shown="END { print $(echo "${arg}" | sed 's-"-\\"-g') }" 1190 printf "\x1b[48;5;253m\x1b[38;5;26m%-80s\x1b[0m\n" \ 1191 "awk \"${shown}\" < /dev/null" >&2 1192 awk "END { print ${arg} }" < /dev/null 1193 done 1194 } 1195 1196 # ignore ANSI terminal styling 1197 plain() { 1198 awk ' 1199 { 1200 # ignore notifications (code 9) and hyperlinks (code 8) 1201 gsub(/\x1b\](8|9);[^\x07]*\x07/, "") 1202 # ignore cursor-movers and style-changers 1203 gsub(/\x1b\[([0-9]*[A-HJKST]|[0-9;]*m)/, "") 1204 1205 print 1206 fflush() 1207 }' "$@" 1208 } 1209 1210 # reset ANSI styles at the end of each line 1211 plainend() { 1212 awk '{ printf "%s\x1b[0m\n", $0; fflush() }' "$@" 1213 } 1214 1215 # PLay INput handles playable/multimedia streams from stdin 1216 plin() { 1217 mpv - 1218 } 1219 1220 # Quiet cURL 1221 qurl() { 1222 curl -s "$@" 1223 } 1224 1225 # reflow/trim lines of prose (text) to improve its legibility: it 1226 # seems especially useful when the text is pasted from web-pages 1227 # being viewed in reader mode 1228 reprose() { 1229 awk 'FNR == 1 && NR > 1 { print "" } 1' "$@" | fold -s | 1230 sed -E 's- *\r?$--' 1231 } 1232 1233 # REPeat a STRing n times, or 80 times by default 1234 repstr() { 1235 awk -v what="${1}" -v times="${2:-80}" ' 1236 BEGIN { 1237 if (length(what) == 0) exit 0 1238 for (i = 1; i <= times; i++) printf "%s", what 1239 printf "\n" 1240 }' < /dev/null 1241 } 1242 1243 # RUN a command IN the folder given as the first argument 1244 runin() { 1245 local prev 1246 local res 1247 1248 prev="${OLDPWD}" 1249 cd "${1}" || return 1 1250 1251 shift 1252 "$@" 1253 res="$?" 1254 1255 cd - || return 1 1256 OLDPWD="${prev}" 1257 return "${res}" 1258 } 1259 1260 # run Sed (extended) 1261 s() { 1262 sed -E "$@" 1263 } 1264 1265 # show a unique-looking separator line; useful to run between commands 1266 # which output walls of text 1267 sep() { 1268 printf "\x1b[48;5;253m" 1269 printf "·························································" 1270 printf "·······················" 1271 printf "\x1b[0m\n" 1272 } 1273 1274 # start a local webserver from the current folder, using the port number 1275 # given, or port 8080 by default 1276 serve() { 1277 printf "\x1b[38;5;26mserving files in ${2:-$(pwd)}\x1b[0m\n" >&2 1278 python -m http.server "${1:-8080}" -d "${2:-.}" 1279 } 1280 1281 # SET DIFFerence sorts its 2 inputs, then finds lines not in the 2nd input 1282 setdiff() { 1283 # comm -23 <(sort "$1") <(sort "$2") 1284 # dash doesn't support the process-sub syntax 1285 (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1286 } 1287 1288 # SET INtersection, sorts its 2 inputs, then finds common lines 1289 setin() { 1290 # comm -12 <(sort "$1") <(sort "$2") 1291 # dash doesn't support the process-sub syntax 1292 (sort "$1" | (sort "$2" | (comm -12 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1293 } 1294 1295 # SET SUBtraction sorts its 2 inputs, then finds lines not in the 2nd input 1296 setsub() { 1297 # comm -23 <(sort "$1") <(sort "$2") 1298 # dash doesn't support the process-sub syntax 1299 (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) 1300 } 1301 1302 # show a command, then run it 1303 showrun() { 1304 printf "\x1b[7m%s\x1b[0m\n" "$*" && "$@" 1305 } 1306 1307 # SKIP the first n lines, or the 1st line by default 1308 skip() { 1309 tail -n +$(("${1:-1}" + 1)) "${2:--}" 1310 } 1311 1312 # SKIP the FIRST n lines, or the 1st line by default 1313 skipfirst() { 1314 tail -n +$(("${1:-1}" + 1)) "${2:--}" 1315 } 1316 1317 # skip/ignore the last n lines, or only the very last line by default 1318 skiplast() { 1319 head -n -"${1:-1}" "${2:--}" 1320 } 1321 1322 # dig into a folder recursively for files under n bytes 1323 smallfiles() { 1324 find "${2:-.}" -type f -size -"${1:-1000000}"c 1325 } 1326 1327 # show the reverse-SOrted SIzes of various files 1328 sosi() { 1329 wc -c "$@" | sort -rn 1330 } 1331 1332 # ignore leading spaces, trailing spaces, even runs of multiple spaces 1333 # in the middle of lines, as well as trailing carriage returns 1334 squeeze() { 1335 awk ' 1336 { 1337 gsub(/ +/, " ") 1338 gsub(/ *\t */, "\t") 1339 gsub(/(^ +)|( *\r?$)/, "") 1340 print 1341 }' "$@" 1342 } 1343 1344 # ssv2tsv turns each run of 1+ spaces into a single tab, while ignoring 1345 # leading spaces in each line 1346 ssv2tsv() { 1347 awk 1 "$@" | sed -E 's-^ +--; s- +-\t-g' 1348 } 1349 1350 # underline every 5th line 1351 stripe() { 1352 awk ' 1353 NR % 5 == 0 && NR != 1 { printf "\x1b[4m%s\x1b[0m\n", $0; next } 1354 1' "$@" 1355 } 1356 1357 # Trim leading/trailing spaces and trailing carriage-returns 1358 t() { 1359 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | 1360 sed -E 's-^ +--; s- *\r?$--' 1361 } 1362 1363 # Tab AWK: TSV-specific I/O settings for `awk` 1364 tawk() { 1365 awk -F "\t" -v OFS="\t" "$@" 1366 } 1367 1368 # Trim End ignores trailing spaces and possibly a carriage return in all 1369 # lines; also, this command ensures separate lines from different inputs 1370 # will never join by accident 1371 te() { 1372 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's- *\r?$--' 1373 } 1374 1375 # show current date in a specifc format, which is both people-friendly 1376 # and machine/tool/search/automation-friendly 1377 today() { 1378 date +'%Y-%m-%d %a %b %d' 1379 } 1380 1381 # show all files directly in the folder given, without looking any deeper 1382 topfiles() { 1383 local arg 1384 for arg in "${@:-.}"; do 1385 find "${arg}" -maxdepth 1 -type f 1386 done 1387 } 1388 1389 # show all folders directly in the folder given, without looking any deeper 1390 topfolders() { 1391 local arg 1392 for arg in "${@:-.}"; do 1393 find "${arg}" -maxdepth 1 -type d | awk 'NR > 1' 1394 done 1395 } 1396 1397 # ignore leading spaces, trailing spaces, and carriage returns on lines 1398 trim() { 1399 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | 1400 sed -E 's-^ +--; s- *\r?$--' 1401 } 1402 1403 # ignore trailing spaces and possibly a carriage return in all lines; 1404 # also, this command ensures separate lines from different inputs will 1405 # never join by accident 1406 trimend() { 1407 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's- *\r?$--' 1408 } 1409 1410 # ignore the prefix given from input lines which start with it; input 1411 # lines which don't start with the prefix given will stay unchanged 1412 trimprefix() { 1413 local prefix 1414 prefix="${1:-}" 1415 shift 1416 1417 awk -v pre="${prefix}" ' 1418 index($0, pre) == 1 { $0 = substr($0, length(pre) + 1) } 1419 1' "$@" 1420 } 1421 1422 # ignore the suffix given from input lines which end with it; input 1423 # lines which don't end with the suffix given will stay unchanged 1424 trimsuffix() { 1425 local suffix 1426 suffix="${1:-}" 1427 shift 1428 1429 awk -v suf="${suffix}" ' 1430 { 1431 i = index($0, suf) 1432 if (i != 0 && i == length - length(suf) + 1) { 1433 $0 = substr($0, 1, length - length(suf)) 1434 } 1435 } 1436 1437 1' "$@" 1438 } 1439 1440 # ignore trailing spaces and possibly a carriage return in all lines; 1441 # also, this command ensures separate lines from different inputs will 1442 # never join by accident 1443 trimtrail() { 1444 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's- *\r?$--' 1445 } 1446 1447 # ignore trailing spaces and possibly a carriage return in all lines; 1448 # also, this command ensures separate lines from different inputs will 1449 # never join by accident 1450 trimtrails() { 1451 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's- *\r?$--' 1452 } 1453 1454 # try running a command, emitting an explicit message to standard-error 1455 # if the command given fails 1456 try() { 1457 "$@" || ( 1458 printf "%s: failure running %s\n" "$0" "$*" >&2 1459 return 255 1460 ) 1461 } 1462 1463 # TimeStamp lines satisfying an AWK condition, ignoring all other lines 1464 tsawk() { 1465 # -v line="\x1b[38;5;27m%s\x1b[0m %s\n" 1466 awk \ 1467 -v line="\x1b[48;5;255m\x1b[38;5;24m%s\x1b[0m %s\n" \ 1468 -v time="%Y-%m-%d %H:%M:%S" \ 1469 "${1:-1} { printf line, strftime(time), \$0; fflush() }" 1470 } 1471 1472 # Unixify concatenates all named input sources, ignoring trailing CRLFs 1473 # into LFs, and guaranteeing lines from different sources are accidentally 1474 # joined, by adding a line-feed when an input's last line doesn't end with 1475 # one; also, ignore leading UTF-8 BOMs on the first line of each input, as 1476 # those are useless at best 1477 u() { 1478 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's-\r$--' 1479 } 1480 1481 # decode base64 bytes 1482 unbase64() { 1483 base64 -d "$@" 1484 } 1485 1486 # deduplicate lines, keeping them in their original order 1487 unique() { 1488 awk '!c[$0]++' "$@" 1489 } 1490 1491 # concatenate all named input sources, ignoring trailing CRLFs into LFs, 1492 # and guaranteeing lines from different sources are accidentally joined, 1493 # by adding a line-feed when an input's last line doesn't end with one; 1494 # also, ignore leading UTF-8 BOMs on the first line of each input, as 1495 # those are useless at best 1496 unixify() { 1497 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's-\r$--' 1498 } 1499 1500 # turn all CRLF byte-pairs into single line-feed bytes 1501 uncrlf() { 1502 cat "$@" | sed -E 's-\r$--' 1503 } 1504 1505 # expand tabs into spaces using the tabstop given 1506 untab() { 1507 local tabstop 1508 tabstop="${1:-4}" 1509 shift 1510 expand -t "${tabstop}" "$@" 1511 } 1512 1513 # go UP n folders, or go up 1 folder by default 1514 up() { 1515 if [ "${1:-1}" -le 0 ]; then 1516 cd . 1517 return "$?" 1518 fi 1519 1520 cd "$(printf "%${1:-1}s" "" | sed 's- -../-g')" || return $? 1521 } 1522 1523 # emit input lines in reverse order, or last to first 1524 upsidedown() { 1525 awk ' 1526 { lines[NR] = $0 } 1527 END { for (i = NR; i >= 1; i--) print lines[i] }' "$@" 1528 } 1529 1530 # Underline Table: underline the first line (the header), then 1531 # underline every 5th line after that 1532 ut() { 1533 awk ' 1534 (NR - 1) % 5 == 0 { 1535 gsub(/\x1b\[0m/, "\x1b[0m\x1b[4m") 1536 printf "\x1b[4m%s\x1b[0m\n", $0 1537 next 1538 } 1539 1540 1' "$@" 1541 } 1542 1543 # View text: run `less` with ANSI styles and no line-wrapping 1544 v() { 1545 less -KiCRS "$@" 1546 } 1547 1548 # run a command, showing its success/failure right after 1549 verdict() { 1550 local code 1551 local fs 1552 local msg 1553 1554 "$@" 1555 code="$?" 1556 1557 if [ "${code}" -eq 0 ]; then 1558 fs="\n\x1b[38;5;29m%s \x1b[48;5;29m\x1b[97m succeeded \x1b[0m\n" 1559 printf "${fs}" "$*" >&2 1560 return 0 1561 fi 1562 1563 msg="failed with error code ${code}" 1564 printf "\n\x1b[31m%s \x1b[41m\x1b[97m ${msg} \x1b[0m\n" "$*" >&2 1565 return "${code}" 1566 } 1567 1568 # View with Header runs `less` without line numbers, with ANSI styles, with 1569 # no line-wrapping, and using the first line as a sticky-header, so it always 1570 # shows on top 1571 vh() { 1572 less --header=1 -KiCRS "$@" 1573 } 1574 1575 # What Are These (?) shows what the names given to it are/do 1576 wat() { 1577 local code 1578 local a 1579 local res 1580 1581 code=0 1582 for a in "$@"; do 1583 printf "\x1b[48;5;253m\x1b[38;5;26m%-80s\x1b[0m\n" "${a}" 1584 ( 1585 alias "${a}" || declare -f "${a}" || which "${a}" || type "${a}" 1586 ) 2> /dev/null 1587 res="$?" 1588 1589 if [ "${res}" -ne 0 ]; then 1590 code="${res}" 1591 printf "\x1b[31m%s not found\x1b[0m\n" "${a}" 1592 fi 1593 done 1594 1595 return "${code}" 1596 } 1597 1598 # find all files which have at least 1 line with trailing spaces/CRs, with 1599 # the option to limit the (fully-recursive) search to the files/folders given 1600 wheretrails() { 1601 rg -c '[ \r]+$' "${@:-.}" 1602 } 1603 1604 # find all files which have at least 1 line with trailing spaces/CRs, with 1605 # the option to limit the (fully-recursive) search to the files/folders given 1606 whichtrails() { 1607 rg -c '[ \r]+$' "${@:-.}" 1608 } 1609 1610 # What Is This (?) shows what the name given to it is/does 1611 wit() { 1612 ( 1613 alias "${1}" || declare -f "${1}" || which "${1}" || type "${1}" 1614 ) 2> /dev/null || ( 1615 printf "\x1b[31m%s not found\x1b[0m\n" "${1}" >&2 && return 1 1616 ) 1617 } 1618 1619 # Youtube Audio Player 1620 yap() { 1621 mpv "$(yt-dlp -f 140 --get-url $(echo ${1} | sed 's-&.*--') 2> /dev/null)" 1622 } 1623 1624 # Youtube Download 1625 yd() { 1626 yt-dlp "$@" 1627 } 1628 1629 # Youtube Download AAC audio 1630 ydaac() { 1631 yt-dlp -f 140 "$@" 1632 } 1633 1634 # Youtube Download MP4 video 1635 ydmp4() { 1636 yt-dlp -f 22 "$@" 1637 } 1638 1639 # year shows a full calendar for the current year, or for the year given 1640 year() { 1641 cal -y "$@" 1642 } 1643 1644 # show the current date in the YYYY-MM-DD format 1645 ymd() { 1646 date +'%Y-%m-%d' 1647 }