File: shshsh.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 # shshsh
  27 # SHort SHell SHortcuts: 1..3-letter commands for 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 shshsh` or
  35 # `. shshsh` (no quotes either way), either directly or at shell startup.
  36 #
  37 # These shell functions rely on tools which are almost always available.
  38 #
  39 #
  40 # Full list of funcs/commands added, with the memo-word each 1-letter name
  41 # stands for
  42 #
  43 # a    awk        run `awk`
  44 # b    blow       blow/expand tabs into spaces, using the tabstop given, or 4
  45 # bar  bar        emit a styled bar to visually separate different outputs
  46 # bh              Breathe Header makes lines breathe starting from the 2nd one
  47 # bl              Breathe Lines regularly adds extra empty lines every few
  48 # c    cat        run `cat`, which is useful, despite claims to the contrary
  49 # cjq             Colored JQ
  50 # cls             CLear Screen
  51 # crg             Colored RipGrep
  52 # cs              Color Syntax, using the `bat` CLI app
  53 # csf             Color Syntax for a whole Folder, even its subfolders
  54 # d    date       run `date`
  55 # def  define     DEFine the word given, using an online service
  56 # dic  dictionary DICtionary definitions, using an online service
  57 # div  divide     DIVide 2 numbers 3 ways, including the complement
  58 # dt   date/time  show the current Date and Time, and the 3 `current` months
  59 # e    each       run command using Each stdin line as a stdin-redirected file
  60 # eg              Extended-mode Grep
  61 # es              Extended-mode Sed
  62 # f    files      show all Files in a folder, digging recursively
  63 # ff              run the Fuzzy Finder (fzf) in multi-choice mode, and more...
  64 # fl   fix lines  Fix Lines, by ensuring they're unix-like
  65 # fz   FuZzy      run the FuZzy finder (fzf) in multi-choice mode, and more...
  66 # g    grep       run `grep` in extended mode
  67 # gbm             Good, Bad, Meh colors lines using 1..3 awk conditions
  68 # get  get        GET/fetch data from the filename or URI given
  69 # h    help       show help messages for the command given
  70 # hl              Header Less runs `less` and always shows the 1st line on top
  71 # hv              Header View runs `less` and always shows the 1st line on top
  72 # i    index      index/number all lines, starting from 0
  73 # j    join       join lines with tabs, optionally as n-item lines
  74 # k    kill       Kill processes, using all ID numbers given
  75 # l    less       run `less`, with line numbers, scrolling, and ANSI styles
  76 # lf              List Files, coloring folders and links
  77 # lh              Less with Header runs `less` and always shows the 1st line
  78 # m    match      case-sensitively match the extended-mode regex given
  79 # n    number     Number all lines, starting from the number given, or from 1
  80 # nil             emit nothing to stdout and/or discard everything from stdin
  81 # now  now        show the current date and time
  82 # o    output     unify output, by merging stderr into stdout
  83 # p    plain      ignore ANSI terminal styling, resulting in proper plain-text
  84 # q    quiet      ignore stderr
  85 # r    reflow     reflow text/prose
  86 # ro   read-only  view Read-Only, using a text editor
  87 # s    sed        run `sed` in extended mode
  88 # t    trim       trim leading/trailing spaces and carriage-returns
  89 # try  try        try running the command given
  90 # u    unique     avoid repeating lines, unlike the defective `uniq`
  91 # ul              Unique Lines
  92 # up   up         go UP n folders, or go up 1 folder by default
  93 # v    view       run `less`, enabling scrolling and ANSI styles
  94 # vh              View with Header runs `less` and always shows the 1st line
  95 # w    web        fetch a URI with `wget`, without saving the result to a file
  96 # wat             What Are These (?) shows what the names given to it are/do
  97 # wt              Which Trails finds all files with lines ending in spaces/CRs
  98 # x    xargs      run `xargs`, using whole lines as extra arguments
  99 # y    year       show a calendar for the current year, or for the year given
 100 # yap             Youtube Audio Player
 101 # yd              Youtube Download
 102 # ymd             show the current date in the YYYY-MM-DD format
 103 # z    zip        run `gzip`
 104 
 105 
 106 # handle help options
 107 case "$1" in
 108     -h|--h|-help|--help)
 109         awk '/^# +shshsh/, /^$/ { gsub(/^# ?/, ""); print }' "$0"
 110         exit 0
 111     ;;
 112 esac
 113 
 114 
 115 # use a simple shell prompt
 116 # PS1="\$ "
 117 # PS2="> "
 118 
 119 # use a simple shell prompt, showing the current folder in the title
 120 # PS1="\[\e]0;\w\a\]\$ "
 121 # PS2="> "
 122 
 123 # use a simple shell prompt, showing the current folder both in the prompt
 124 # itself, and in the title
 125 # PS1="\[\e]0;\w\a\]\w\$ "
 126 # PS2="> "
 127 
 128 # prevent `less` from saving commands
 129 # export LESSHISTFILE="-"
 130 # export LESSSECURE=1
 131 
 132 # prevent the shell from saving commands
 133 # unset HISTFILE
 134 
 135 
 136 # Awk
 137 a() { awk "$@"; }
 138 
 139 # Blow tabs into spaces, using the tabstop given, or 4 by default
 140 b() {
 141     local tabstop="${1:-4}"
 142     shift
 143     expand -t "${tabstop}" "$@"
 144 }
 145 
 146 # emit a colored bar which can help visually separate different outputs
 147 bar() { printf "\e[48;5;253m%${1:-80}s\e[0m\n" " "; }
 148 
 149 # Breathe Header: add an empty line after the first one (the header), then
 150 # separate groups of 5 lines (by default) with empty lines between them
 151 bh() {
 152     local n="${1:-5}"
 153     shift
 154     awk -v n="$n" '(NR - 1) % n == 1 && NR > 1 { print "" } 1' "$@"
 155 }
 156 
 157 # Breathe Lines: separate groups of 5 lines (by default) with empty lines
 158 bl() {
 159     local n="${1:-5}"
 160     shift
 161     awk -v n="$n" 'NR % n == 1 && NR != 1 { print "" } 1' "$@"
 162 }
 163 
 164 # Cat
 165 c() { cat "$@"; }
 166 
 167 # CLear Screen
 168 cls() { clear; }
 169 
 170 # Colored JQ
 171 cjq() { jq -C "$@"; }
 172 
 173 # Colored RipGrep: ensures app `rg` emits colors when piped
 174 crg() { rg --color=always "$@"; }
 175 
 176 # Color Syntax: run syntax-coloring app `bat` without line-wrapping
 177 cs() {
 178     local cmd="bat"
 179     # debian linux uses a different name for the `bat` app
 180     if [ -e "/usr/bin/batcat" ]; then
 181         cmd="batcat"
 182     fi
 183 
 184     "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \
 185         --wrap=never --color=always "$@" |
 186     sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS
 187 }
 188 
 189 # Color Syntax of all files in a Folder, showing line numbers
 190 csf() {
 191     local cmd="bat"
 192     # debian linux uses a different name for the `bat` app
 193     if [ -e "/usr/bin/batcat" ]; then
 194         cmd="batcat"
 195     fi
 196 
 197     find "${1:-.}" -type f -print0 | xargs --null "$cmd" \
 198         --style=plain,header,numbers --theme='Monokai Extended Light' \
 199         --wrap=never --color=always |
 200     sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS
 201 }
 202 
 203 # Date
 204 d() { date "$@"; }
 205 
 206 # DEFine the word given, using an online service
 207 def() {
 208     curl -s "dict://dict.org/d:$*" | awk '
 209         /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next }
 210         /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next }
 211         1'
 212 }
 213 
 214 # DICtionary definitions, using an online service
 215 dic() {
 216     curl -s "dict://dict.org/d:$*" | awk '
 217         /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next }
 218         /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next }
 219         1'
 220 }
 221 
 222 # DIVide 2 numbers 3 ways, including the complement
 223 div() {
 224     awk -v a="${1:-1}" -v b="${2:-1}" '
 225     BEGIN {
 226         gsub(/_/, "", a)
 227         gsub(/_/, "", b)
 228         if (a > b) { c = a; a = b; b = c; }
 229         print a / b
 230         print b / a
 231         print 1 - a / b
 232         exit
 233     }'
 234 }
 235 
 236 # show the current Date and Time, and the 3 `current` months
 237 dt() {
 238     # debian linux has a different `cal` app which highlights the day
 239     if [ -e "/usr/bin/ncal" ]; then
 240         ncal -C -3
 241     else
 242         cal -3
 243     fi
 244 
 245     # show the current time center-aligned
 246     # printf "%28s\e[34m%s\e[0m\n" " " "$(date +'%T')"
 247     printf "%22s\e[32m%s\e[0m  \e[34m%s\e[0m\n" " " \
 248         "$(date +'%a %b %d')" "$(date +'%T')"
 249 }
 250 
 251 # run command using each stdin line as a stdin-redirected file
 252 # e() {
 253 #     local arg
 254 #     IFS=""
 255 #     while read -r arg; do
 256 #         "$@" < "${arg}"
 257 #     done
 258 # }
 259 
 260 # Extended-mode Grep
 261 eg() { grep -E "$@"; }
 262 
 263 # Extended-mode Sed
 264 es() { sed -E "$@"; }
 265 
 266 # Find all files recursively in all folders given, or the current one
 267 f() {
 268     local arg
 269     for arg in "${@:-.}"; do
 270         find "${arg}" -type f
 271     done
 272 }
 273 
 274 # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings
 275 ff() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; }
 276 
 277 # Fix lines, ignoring leading UTF-8_BOMs (byte-order-marks) on each input's
 278 # first line, turning all end-of-line CRLF byte-pairs into single line-feeds,
 279 # and ensuring each input's last line ends with a line-feed
 280 fl() {
 281     awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's-\r$--'
 282 }
 283 
 284 # run the FuZzy finder (fzf) in multi-choice mode, with custom keybindings
 285 fz() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; }
 286 
 287 # run Grep in extended mode
 288 g() { grep -E "$@"; }
 289 
 290 # GET/fetch data from the filename or URI given
 291 get() {
 292     case "$1" in
 293         http://*|https://*|file://*|ftp://*|ftps://*|sftp://*|dict://*)
 294             # curl -s "$1"
 295             curl -s "$1" || (
 296                 printf "\e[31mcan't get %s\e[0m\n" "$1" >&2
 297                 return 1
 298             )
 299         ;;
 300         *)
 301             cat "$1"
 302         ;;
 303     esac
 304 }
 305 
 306 # Good, Bad, Meh colors lines using 1..3 awk conditions
 307 gbm() {
 308     local good="${1:-0}"
 309     shift
 310     local bad="${1:-0}"
 311     shift
 312     local meh="${1:-0}"
 313     shift
 314 
 315     awk "
 316         ${good} {
 317             # code to use a color-blind-friendlier blue, instead of green
 318             # gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;26m\")
 319             # printf \"\\x1b[38;5;26m%s\\x1b[0m\\n\", \$0
 320             gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;29m\")
 321             printf \"\\x1b[38;5;29m%s\\x1b[0m\\n\", \$0
 322             next
 323         }
 324 
 325         ${bad} {
 326             gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;1m\")
 327             printf \"\\x1b[38;5;1m%s\\x1b[0m\\n\", \$0
 328             next
 329         }
 330 
 331         ${meh} {
 332             gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;248m\")
 333             printf \"\\x1b[38;5;248m%s\\x1b[0m\\n\", \$0
 334             next
 335         }
 336 
 337         { print }
 338     " "$@"
 339 }
 340 
 341 # Help for the command given
 342 h() { man "${1}" || ("${1}" "${2:---help}" 2>&1 | less -JMKiCRS); }
 343 
 344 # Header Less runs `less` with line numbers, ANSI styles, no line-wrapping,
 345 # and using the first line as a sticky-header, so it always shows on top
 346 hl() { less --header=1 -JMKNiCRS "$@"; }
 347 
 348 # Header View runs `less` without line numbers, with ANSI styles, with no
 349 # line-wrapping, and using the first line as a sticky-header, so it always
 350 # shows on top
 351 hv() { less --header=1 -JMKiCRS "$@"; }
 352 
 353 # Index/number all lines, starting from 0
 354 i() { awk '{ printf "%d\t%s\n", NR - 1, $0 }' "$@"; }
 355 
 356 # Join lines with tabs, optionally as n-item lines
 357 j() {
 358     local n="${1:-0}"
 359     shift
 360 
 361     if [ "${n}" -le 0 ]; then
 362         awk '
 363             NR > 1 { printf "\t" }
 364             { printf "%s", $0 }
 365             END { if (NR > 0) print "" }' "$@"
 366         return "$?"
 367     fi
 368 
 369     awk -v n="${n}" '
 370         NR % n != 1 { printf "\t" }
 371         { printf "%s", $0 }
 372         NR % n == 0 { print "" }
 373         END { if (NR % n != 0) print "" }' "$@"
 374 }
 375 
 376 # Kill processes, using all ID numbers given
 377 # k() { kill -9 "$@"; }
 378 
 379 # run `less`, showing line numbers, among other settings
 380 l() { less -JMKNiCRS "$@"; }
 381 
 382 # list files, coloring folders and links
 383 lf() {
 384     ls -al --file-type --color=never --time-style iso "$@" | awk '
 385         /^d/ { printf "\x1b[38;5;33m%s\x1b[0m\n", $0; next }
 386         /^l/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; next }
 387         1'
 388 }
 389 
 390 # Less with Header runs `less` with line numbers, ANSI styles, no line-wraps,
 391 # and using the first line as a sticky-header, so it always shows on top
 392 lh() { less --header=1 -JMKNiCRS "$@"; }
 393 
 394 # Match regex given, or non-empty lines by default
 395 m() {
 396     local regex="${1:-[^ *]\r?$}"
 397     shift
 398     grep -E "${regex}" "$@"
 399 }
 400 
 401 # Number all lines, starting from the number given, or 1 by default
 402 n() {
 403     local n="${1:-1}"
 404     shift
 405     awk -v n="$n" '{ printf "%d\t%s\n", NR - 1 + n, $0 }' "$@"
 406 }
 407 
 408 # emit nothing to output and/or discard everything from input
 409 nil() {
 410     if [ -p /dev/stdin ]; then
 411         cat > /dev/null
 412     else
 413         head -c 0
 414     fi
 415 }
 416 
 417 # show the current date and time
 418 now() { date +'%Y-%m-%d %H:%M:%S'; }
 419 
 420 # unify Output, by merging stderr into stdout
 421 o() { "$@" 2>&1; }
 422 
 423 # Plain text
 424 p() {
 425     awk '
 426     {
 427         # ignore notifications (code 9) and hyperlinks (code 8)
 428         gsub(/\x1b\](8|9);[^\x07]*\x07/, "")
 429         # ignore cursor-movers and style-changers
 430         gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "")
 431 
 432         print
 433     }' "$@"
 434 }
 435 
 436 # make stderr `Quiet`, by ignoring it
 437 q() { "$@" 2> /dev/null; }
 438 
 439 # Reflow text/prose
 440 r() {
 441     local width="${1:-80}"
 442     shift
 443     awk '
 444         FNR == 1 { gsub(/^\xef\xbb\xbf/, "") }
 445         FNR == 1 && NR > 1 { print "" }
 446         1' "$@" | sed -E 's-\r$--; s- +$- -' |
 447             fold -s -w="$width" | sed -E 's- +$--'
 448 }
 449 
 450 # view in Read-Only mode, using a text editor
 451 ro() { micro --readonly true "$@"; }
 452 
 453 # run Sed in extended mode
 454 s() { sed -E "$@"; }
 455 
 456 # Trim leading/trailing spaces in lines, even trim trailing carriage-returns;
 457 # also ignore leading UTF-8_BOMs (byte-order-marks) on each input's first line
 458 t() {
 459     awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" |
 460         sed -E 's-^ +--; s- *\r?$--'
 461 }
 462 
 463 # try running a command, emitting an explicit message to standard-error
 464 # if the command given fails
 465 try() {
 466     "$@" || (
 467         printf "%s: failure running %s\n" "$0" "$*" >&2
 468         return 255
 469     )
 470 }
 471 
 472 # Unique lines, which actually works unlike the purposely-defective `uniq`
 473 u() { awk '!c[$0]++' "$@"; }
 474 
 475 # Unique Lines
 476 ul() { awk '!c[$0]++' "$@"; }
 477 
 478 # go UP n folders, or go up 1 folder by default
 479 up() {
 480     if [ "${1:-1}" -le 0 ]; then
 481         cd .
 482         return "$?"
 483     fi
 484 
 485     cd "$(printf "%${1:-1}s" "" | sed 's- -../-g')" || return $?
 486 }
 487 
 488 # View with `less`
 489 v() { less -JMKiCRS "$@"; }
 490 
 491 # View with Header runs `less` without line numbers, with ANSI styles, with
 492 # no line-wrapping, and using the first line as a sticky-header, so it always
 493 # shows on top
 494 vh() { less --header=1 -JMKiCRS "$@"; }
 495 
 496 # fetch a URI with `wget`, without saving the result to a file
 497 w() {
 498     # wget -O - "$@"
 499     # curl --no-progress-meter "$@"
 500     wget --no-verbose -O - "$@"
 501 }
 502 
 503 # What Are These (?) shows what the names given to it are/do
 504 wat() {
 505     local a
 506     local res
 507     local code=0
 508 
 509     for a in "$@"; do
 510         printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "${a}"
 511         (
 512             alias "${a}" || declare -f "${a}" || which "${a}" || type "${a}"
 513         ) 2> /dev/null
 514         res="$?"
 515 
 516         if [ "${res}" -ne 0 ]; then
 517             code="${res}"
 518             printf "\e[31m%s not found\e[0m\n" "${a}"
 519         fi
 520     done
 521 
 522     return "${code}"
 523 }
 524 
 525 # Which/where Trails finds all files with lines ending in spaces/CRs
 526 wt() { rg -c '[ \r]+$' "${@:-.}"; }
 527 
 528 # run `xargs`, using whole lines as extra arguments
 529 x() { xargs -d '\n' "$@"; }
 530 
 531 # run `xargs`, using whole lines as extra arguments
 532 xl() { xargs -d '\n' "$@"; }
 533 
 534 # show a calendar for the current year, or for the year given
 535 y() {
 536     # debian linux has a different `cal` app which highlights the day
 537     if [ -e "/usr/bin/ncal" ]; then
 538         ncal -C -y "$@"
 539     else
 540         cal -y "$@"
 541     fi
 542 }
 543 
 544 # Youtube Audio Player
 545 yap() {
 546     local url="$(echo "${1}" | sed 's-&.*--')"
 547     mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)"
 548 }
 549 
 550 # Youtube Download
 551 yd() { yt-dlp "$@"; }
 552 
 553 # show the current date in the YYYY-MM-DD format
 554 ymd() { date +'%Y-%m-%d'; }
 555 
 556 # zip/unzip, using the `gzip` format
 557 z() { gzip "$@"; }