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 "$@"; }