File: shshsh.sh 1 #!/bin/sh 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2020-2025 pacman64 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the “Software”), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be included in 15 # all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 # SOFTWARE. 24 25 26 # 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 case "$1" in 107 -h|--h|-help|--help) 108 awk '/^# +shshsh/, /^$/ { gsub(/^# ?/, ""); print }' "$0" 109 exit 0 110 ;; 111 esac 112 113 114 # use a simple shell prompt 115 # PS1="\$ " 116 # PS2="> " 117 118 # use a simple shell prompt, showing the current folder in the title 119 # PS1="\[\e]0;\w\a\]\$ " 120 # PS2="> " 121 122 # use a simple shell prompt, showing the current folder both in the prompt 123 # itself, and in the title 124 # PS1="\[\e]0;\w\a\]\w\$ " 125 # PS2="> " 126 127 # prevent `less` from saving commands 128 # export LESSHISTFILE="-" 129 # export LESSSECURE=1 130 131 # prevent the shell from saving commands 132 # unset HISTFILE 133 134 135 # Awk 136 a() { awk "$@"; } 137 138 # Blow tabs into spaces, using the tabstop given, or 4 by default 139 b() { 140 local tabstop="${1:-4}" 141 shift 142 expand -t "${tabstop}" "$@" 143 } 144 145 # emit a colored bar which can help visually separate different outputs 146 bar() { printf "\e[48;5;253m%${1:-80}s\e[0m\n" " "; } 147 148 # Breathe Header: add an empty line after the first one (the header), then 149 # separate groups of 5 lines (by default) with empty lines between them 150 bh() { 151 local n="${1:-5}" 152 shift 153 awk -v n="$n" '(NR - 1) % n == 1 && NR > 1 { print "" } 1' "$@" 154 } 155 156 # Breathe Lines: separate groups of 5 lines (by default) with empty lines 157 bl() { 158 local n="${1:-5}" 159 shift 160 awk -v n="$n" 'NR % n == 1 && NR != 1 { print "" } 1' "$@" 161 } 162 163 # Cat 164 c() { cat "$@"; } 165 166 # CLear Screen 167 cls() { clear; } 168 169 # Colored JQ 170 cjq() { jq -C "$@"; } 171 172 # Colored RipGrep: ensures app `rg` emits colors when piped 173 crg() { rg --color=always "$@"; } 174 175 # Color Syntax: run syntax-coloring app `bat` without line-wrapping 176 cs() { 177 local cmd="bat" 178 # debian linux uses a different name for the `bat` app 179 if [ -e "/usr/bin/batcat" ]; then 180 cmd="batcat" 181 fi 182 183 "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ 184 --wrap=never --color=always "$@" | 185 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 186 } 187 188 # Color Syntax of all files in a Folder, showing line numbers 189 csf() { 190 local cmd="bat" 191 # debian linux uses a different name for the `bat` app 192 if [ -e "/usr/bin/batcat" ]; then 193 cmd="batcat" 194 fi 195 196 find "${1:-.}" -type f -print0 | xargs --null "$cmd" \ 197 --style=plain,header,numbers --theme='Monokai Extended Light' \ 198 --wrap=never --color=always | 199 sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS 200 } 201 202 # Date 203 d() { date "$@"; } 204 205 # DEFine the word given, using an online service 206 def() { 207 curl -s "dict://dict.org/d:$*" | awk ' 208 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 209 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 210 1' 211 } 212 213 # DICtionary definitions, using an online service 214 dic() { 215 curl -s "dict://dict.org/d:$*" | awk ' 216 /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } 217 /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } 218 1' 219 } 220 221 # DIVide 2 numbers 3 ways, including the complement 222 div() { 223 awk -v a="${1:-1}" -v b="${2:-1}" ' 224 BEGIN { 225 gsub(/_/, "", a) 226 gsub(/_/, "", b) 227 if (a > b) { c = a; a = b; b = c; } 228 print a / b 229 print b / a 230 print 1 - a / b 231 exit 232 }' 233 } 234 235 # show the current Date and Time, and the 3 `current` months 236 dt() { 237 # debian linux has a different `cal` app which highlights the day 238 if [ -e "/usr/bin/ncal" ]; then 239 ncal -C -3 240 else 241 cal -3 242 fi 243 244 # show the current time center-aligned 245 # printf "%28s\e[34m%s\e[0m\n" " " "$(date +'%T')" 246 printf "%22s\e[32m%s\e[0m \e[34m%s\e[0m\n" " " \ 247 "$(date +'%a %b %d')" "$(date +'%T')" 248 } 249 250 # run command using each stdin line as a stdin-redirected file 251 # e() { 252 # local arg 253 # IFS="" 254 # while read -r arg; do 255 # "$@" < "${arg}" 256 # done 257 # } 258 259 # Extended-mode Grep 260 eg() { grep -E "$@"; } 261 262 # Extended-mode Sed 263 es() { sed -E "$@"; } 264 265 # Find all files recursively in all folders given, or the current one 266 f() { 267 local arg 268 for arg in "${@:-.}"; do 269 find "${arg}" -type f 270 done 271 } 272 273 # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings 274 ff() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } 275 276 # Fix lines, ignoring leading UTF-8_BOMs (byte-order-marks) on each input's 277 # first line, turning all end-of-line CRLF byte-pairs into single line-feeds, 278 # and ensuring each input's last line ends with a line-feed 279 fl() { 280 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | sed -E 's-\r$--' 281 } 282 283 # run the FuZzy finder (fzf) in multi-choice mode, with custom keybindings 284 fz() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } 285 286 # run Grep in extended mode 287 g() { grep -E "$@"; } 288 289 # GET/fetch data from the filename or URI given 290 get() { 291 case "$1" in 292 http://*|https://*|file://*|ftp://*|ftps://*|sftp://*|dict://*) 293 # curl -s "$1" 294 curl -s "$1" || ( 295 printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 296 return 1 297 ) 298 ;; 299 *) 300 cat "$1" 301 ;; 302 esac 303 } 304 305 # Good, Bad, Meh colors lines using 1..3 awk conditions 306 gbm() { 307 local good="${1:-0}" 308 shift 309 local bad="${1:-0}" 310 shift 311 local meh="${1:-0}" 312 shift 313 314 awk " 315 ${good} { 316 # code to use a color-blind-friendlier blue, instead of green 317 # gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;26m\") 318 # printf \"\\x1b[38;5;26m%s\\x1b[0m\\n\", \$0 319 gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;29m\") 320 printf \"\\x1b[38;5;29m%s\\x1b[0m\\n\", \$0 321 next 322 } 323 324 ${bad} { 325 gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;1m\") 326 printf \"\\x1b[38;5;1m%s\\x1b[0m\\n\", \$0 327 next 328 } 329 330 ${meh} { 331 gsub(/\\x1b\\[0m/, \"\\x1b[0m\\x1b[38;5;248m\") 332 printf \"\\x1b[38;5;248m%s\\x1b[0m\\n\", \$0 333 next 334 } 335 336 { print } 337 " "$@" 338 } 339 340 # Help for the command given 341 h() { man "${1}" || ("${1}" "${2:---help}" 2>&1 | less -JMKiCRS); } 342 343 # Header Less runs `less` with line numbers, ANSI styles, no line-wrapping, 344 # and using the first line as a sticky-header, so it always shows on top 345 hl() { less --header=1 -JMKNiCRS "$@"; } 346 347 # Header View runs `less` without line numbers, with ANSI styles, with no 348 # line-wrapping, and using the first line as a sticky-header, so it always 349 # shows on top 350 hv() { less --header=1 -JMKiCRS "$@"; } 351 352 # Index/number all lines, starting from 0 353 i() { awk '{ printf "%d\t%s\n", NR - 1, $0 }' "$@"; } 354 355 # Join lines with tabs, optionally as n-item lines 356 j() { 357 local n="${1:-0}" 358 shift 359 360 if [ "${n}" -le 0 ]; then 361 awk ' 362 NR > 1 { printf "\t" } 363 { printf "%s", $0 } 364 END { if (NR > 0) print "" }' "$@" 365 return "$?" 366 fi 367 368 awk -v n="${n}" ' 369 NR % n != 1 { printf "\t" } 370 { printf "%s", $0 } 371 NR % n == 0 { print "" } 372 END { if (NR % n != 0) print "" }' "$@" 373 } 374 375 # Kill processes, using all ID numbers given 376 # k() { kill -9 "$@"; } 377 378 # run `less`, showing line numbers, among other settings 379 l() { less -JMKNiCRS "$@"; } 380 381 # list files, coloring folders and links 382 lf() { 383 ls -al --file-type --color=never --time-style iso "$@" | awk ' 384 /^d/ { printf "\x1b[38;5;33m%s\x1b[0m\n", $0; next } 385 /^l/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; next } 386 1' 387 } 388 389 # Less with Header runs `less` with line numbers, ANSI styles, no line-wraps, 390 # and using the first line as a sticky-header, so it always shows on top 391 lh() { less --header=1 -JMKNiCRS "$@"; } 392 393 # Match regex given, or non-empty lines by default 394 m() { 395 local regex="${1:-[^ *]\r?$}" 396 shift 397 grep -E "${regex}" "$@" 398 } 399 400 # Number all lines, starting from the number given, or 1 by default 401 n() { 402 local n="${1:-1}" 403 shift 404 awk -v n="$n" '{ printf "%d\t%s\n", NR - 1 + n, $0 }' "$@" 405 } 406 407 # emit nothing to output and/or discard everything from input 408 nil() { 409 if [ -p /dev/stdin ]; then 410 cat > /dev/null 411 else 412 head -c 0 413 fi 414 } 415 416 # show the current date and time 417 now() { date +'%Y-%m-%d %H:%M:%S'; } 418 419 # unify Output, by merging stderr into stdout 420 o() { "$@" 2>&1; } 421 422 # Plain text 423 p() { 424 awk ' 425 { 426 # ignore cursor-movers and style-changers 427 gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") 428 429 print 430 }' "$@" 431 } 432 433 # make stderr `Quiet`, by ignoring it 434 q() { "$@" 2> /dev/null; } 435 436 # Reflow text/prose 437 r() { 438 local width="${1:-80}" 439 shift 440 awk ' 441 FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 442 FNR == 1 && NR > 1 { print "" } 443 1' "$@" | sed -E 's-\r$--; s- +$- -' | 444 fold -s -w="$width" | sed -E 's- +$--' 445 } 446 447 # view in Read-Only mode, using a text editor 448 ro() { micro --readonly true "$@"; } 449 450 # run Sed in extended mode 451 s() { sed -E "$@"; } 452 453 # Trim leading/trailing spaces in lines, even trim trailing carriage-returns; 454 # also ignore leading UTF-8_BOMs (byte-order-marks) on each input's first line 455 t() { 456 awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } 1' "$@" | 457 sed -E 's-^ +--; s- *\r?$--' 458 } 459 460 # try running a command, emitting an explicit message to standard-error 461 # if the command given fails 462 try() { 463 "$@" || ( 464 printf "%s: failure running %s\n" "$0" "$*" >&2 465 return 255 466 ) 467 } 468 469 # Unique lines, which actually works unlike the purposely-defective `uniq` 470 u() { awk '!c[$0]++' "$@"; } 471 472 # Unique Lines 473 ul() { awk '!c[$0]++' "$@"; } 474 475 # go UP n folders, or go up 1 folder by default 476 up() { 477 if [ "${1:-1}" -le 0 ]; then 478 cd . 479 return "$?" 480 fi 481 482 cd "$(printf "%${1:-1}s" "" | sed 's- -../-g')" || return $? 483 } 484 485 # View with `less` 486 v() { less -JMKiCRS "$@"; } 487 488 # View with Header runs `less` without line numbers, with ANSI styles, with 489 # no line-wrapping, and using the first line as a sticky-header, so it always 490 # shows on top 491 vh() { less --header=1 -JMKiCRS "$@"; } 492 493 # fetch a URI with `wget`, without saving the result to a file 494 w() { 495 # wget -O - "$@" 496 # curl --no-progress-meter "$@" 497 wget --no-verbose -O - "$@" 498 } 499 500 # What Are These (?) shows what the names given to it are/do 501 wat() { 502 local a 503 local res 504 local code=0 505 506 for a in "$@"; do 507 printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "${a}" 508 ( 509 alias "${a}" || declare -f "${a}" || which "${a}" || type "${a}" 510 ) 2> /dev/null 511 res="$?" 512 513 if [ "${res}" -ne 0 ]; then 514 code="${res}" 515 printf "\e[31m%s not found\e[0m\n" "${a}" 516 fi 517 done 518 519 return "${code}" 520 } 521 522 # Which/where Trails finds all files with lines ending in spaces/CRs 523 wt() { rg -c '[ \r]+$' "${@:-.}"; } 524 525 # run `xargs`, using whole lines as extra arguments 526 x() { xargs -d '\n' "$@"; } 527 528 # run `xargs`, using whole lines as extra arguments 529 xl() { xargs -d '\n' "$@"; } 530 531 # show a calendar for the current year, or for the year given 532 y() { 533 # debian linux has a different `cal` app which highlights the day 534 if [ -e "/usr/bin/ncal" ]; then 535 ncal -C -y "$@" 536 else 537 cal -y "$@" 538 fi 539 } 540 541 # Youtube Audio Player 542 yap() { 543 local url="$(echo "${1}" | sed 's-&.*--')" 544 mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" 545 } 546 547 # Youtube Download 548 yd() { yt-dlp "$@"; } 549 550 # show the current date in the YYYY-MM-DD format 551 ymd() { date +'%Y-%m-%d'; } 552 553 # zip/unzip, using the `gzip` format 554 z() { gzip "$@"; }