#!/bin/sh # The MIT License (MIT) # # Copyright © 2024 pacman64 # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # clam # # Command-Line Augmentation Module (clam): get the best out of your shell # # # This is a collection of arguably useful shell functions and shortcuts: # some of these extra commands can be real time/effort savers, ideally # letting you concentrate on getting things done. # # Some of these commands depend on my other scripts from the `pac-tools`, # others either rely on widely-preinstalled command-line apps, or ones # which are available on most of the major command-line `package` managers. # # Among these commands, you'll notice a preference for lines whose items # are tab-separated instead of space-separated, and unix-style lines, which # always end with a line-feed, instead of a CRLF byte-pair. This convention # makes plain-text data-streams less ambiguous and generally easier to work # with, especially when passing them along pipes. # # To use this script, you're supposed to `source` it, so its definitions # stay for your whole shell session: for that, you can run `source clam` or # `. clam` (no quotes either way), either directly or at shell startup. # # This script is compatible with `bash`, `zsh`, and even `dash`, which is # debian linux's default non-interactive shell. It even seems to work with # busybox's shell. # handle help options case "$1" in -h|--h|-help|--help) # show help message, using the info-comment from this very script awk '/^# +clam/, /^$/ { gsub(/^# ?/, ""); print }' "$0" exit 0 ;; esac # dash doesn't support regex-matching syntax, forcing to use case statements case "$0" in -bash|-dash|-sh|bash|dash|sh) # script is being sourced with bash or dash, which is good :;; *) case "$ZSH_EVAL_CONTEXT" in *:file) # script is being sourced with zsh, which is good :;; *) # script is being run normally, which is a waste of time printf "\e[48;5;228m\e[30mDon't run this script, source it instead: to do that,\e[0m\n" printf "\e[48;5;228m\e[30mrun 'source clam' or '. clam' (no quotes either way).\e[0m\n" # failing during shell-startup may deny shell access, so don't # exit with a non-0 error code ;; esac ;; esac # use a simple shell prompt; this setting works on both bash and zsh # export PS1="\$ " # export PS2="> " # use a simple shell prompt, showing the current folder as the tab label; # this setting is bash-specific # export PS1="\[\e]0;\w\a\]$ " # export PS2="> " # prevent `less` from saving/remembering commands # export LESSHISTFILE="-" # export LESSSECURE=1 # prevent the shell from saving/remembering commands across live sessions # unset HISTFILE # avoid saving nodejs command-history to file # export NODE_REPL_HISTORY="" # export NODE_REPL_HISTORY_SIZE=0 # in the future, this may be how you tell `go` to mind its own biz # export GOTELEMETRY=off # alias all-files='allfiles' # alias all-folders='allfolders' # alias begin-tsv='begintsv' # alias big-files='bigfiles' # alias blow-tabs='blowtabs' # alias but-first='butfirst' # alias but-last='butlast' # alias chop-decs='chopdecs' # alias chop-lf='choplf' # alias first-bytes='firstbytes' # alias first-lines='firstlines' # alias from-line='fromline' # alias last-bytes='lastbytes' # alias last-lines='lastlines' # alias line-up='lineup' # alias pre-tsv='pretsv' # alias range-lines='rangelines' # alias ring-tone='ringtone' # alias show-run='showrun' # alias skip-bytes='skipbytes' # alias skip-lines='skiplines' # alias small-files='smallfiles' # alias sort-rest='sortrest' # alias to-line='toline' # alias to-lower='tolower' # alias to-upper='toupper' # alias top-files='topfiles' # alias top-folders='topfolders' # alias trim-end='trimend' # alias trim-ends='trimends' # alias trim-trail='trimtrail' # alias trim-trails='trimtrails' # alias ts-awk='tsawk' # alias tsv-dedup='tsvdedup' # alias where-trails='wheretrails' # alias which-trails='whichtrails' # alias yd-aac='ydaac' # alias yd-mp4='ydmp4' # n-Column-layout shortcuts, using my script `sbs` (Side By Side) # 1c() { sbs 1 "$@"; } # 2c() { sbs 2 "$@"; } # 3c() { sbs 3 "$@"; } # 4c() { sbs 4 "$@"; } # 5c() { sbs 5 "$@"; } # 6c() { sbs 6 "$@"; } # 7c() { sbs 7 "$@"; } # 8c() { sbs 8 "$@"; } # 9c() { sbs 9 "$@"; } # n-Column-layout shortcuts, using my script `sbs` (Side By Side) c1() { sbs 1 "$@"; } c2() { sbs 2 "$@"; } c3() { sbs 3 "$@"; } c4() { sbs 4 "$@"; } c5() { sbs 5 "$@"; } c6() { sbs 6 "$@"; } c7() { sbs 7 "$@"; } c8() { sbs 8 "$@"; } c9() { sbs 9 "$@"; } # shortcuts for my script `ca`, an arbitrary-precision calculator, using # various decimal precisions ca0() { ca "scale=0; $*"; } ca1() { ca "scale=1; $*"; } ca2() { ca "scale=2; $*"; } ca3() { ca "scale=3; $*"; } ca4() { ca "scale=4; $*"; } ca5() { ca "scale=5; $*"; } ca6() { ca "scale=6; $*"; } ca7() { ca "scale=7; $*"; } ca8() { ca "scale=8; $*"; } ca9() { ca "scale=9; $*"; } ca10() { ca "scale=10; $*"; } ca20() { ca "scale=20; $*"; } ca30() { ca "scale=30; $*"; } ca40() { ca "scale=40; $*"; } ca50() { ca "scale=50; $*"; } ca60() { ca "scale=60; $*"; } ca70() { ca "scale=70; $*"; } ca80() { ca "scale=80; $*"; } ca90() { ca "scale=90; $*"; } # expand Tabs up to n spaces each t1() { expand -t 1 "$@"; } t2() { expand -t 2 "$@"; } t3() { expand -t 3 "$@"; } t4() { expand -t 4 "$@"; } t5() { expand -t 5 "$@"; } t6() { expand -t 6 "$@"; } t7() { expand -t 7 "$@"; } t8() { expand -t 8 "$@"; } t9() { expand -t 9 "$@"; } # expand TABs up to n spaces each tab1() { expand -t 1 "$@"; } tab2() { expand -t 2 "$@"; } tab3() { expand -t 3 "$@"; } tab4() { expand -t 4 "$@"; } tab5() { expand -t 5 "$@"; } tab6() { expand -t 6 "$@"; } tab7() { expand -t 7 "$@"; } tab8() { expand -t 8 "$@"; } tab9() { expand -t 9 "$@"; } a() { awk "$@"; } # show all files in a folder, digging recursively allfiles() { local arg for arg in "${@:-.}"; do find "${arg}" -type f done } # show all folders in a folder, digging recursively allfolders() { local arg for arg in "${@:-.}"; do find "${arg}" -type d | awk 'NR > 1' done } # show only the lines which appear on all the inputs given and() { awk ' FNR == 1 { filenum++ } { lines[n++] = $0 if (last[$0] != filenum) { last[$0] = filenum tally[$0]++ } } END { n = ARGC - 1 for (i = 1; i <= NR; i++) { l = lines[i] if (tally[l] == n) { tally[l] = -1 print l } } }' "$@" } # emit each argument given as its own line of output args() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@"; } # keep visible ASCII bytes as they are, turning (almost) all other bytes into # ASCII spaces, by default; tabs, line-feeds, carriage-returns, and escapes # are also kept as given, to avoid messing up lines and ANSI-styles # asciify() { tr -c '\11\12\15\33\41-\176' "${1:- }"; } # turn UTF-8 into visible pseudo-ASCII, where variants of latin letters become # their basic ASCII counterparts, and where non-ASCII symbols become question # marks, one question mark for each code-point byte asciify() { iconv -f utf-8 -t ascii//translit "$@"; } # avoid/ignore lines which match any of the regexes given avoid() { awk ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { for (i in re) if ($0 ~ re[i]) { next } } { print; fflush() }' "${@:-^$}" } b() { bc "$@"; } # emit a line with a repeating ball-like symbol in it balls() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -●-g'; } # show an ansi-styled BANNER-like line # banner() { printf "\e[48;5;253m%s\e[0m\n" "$*"; } # show an ansi-styled BANNER-like line banner() { printf "\e[7m%s\e[0m\n" "$*"; } # emit a colored bar which can help visually separate different outputs bar() { [ "${1:-80}" -gt 0 ] && printf "\e[48;5;253m%${1:-80}s\e[0m\n" " "; } # process Blocks/paragraphs of non-empty lines with AWK bawk() { awk -F='' -v RS='' "$@"; } # Background-colored ECHO becho() { printf "\e[48;5;253m%s\e[0m\n" "$*"; } # play a repeating and annoying high-pitched beep sound a few times a second, # lasting the number of seconds given, or for 1 second by default; uses my # script `waveout` beeps() { local f='sin(2_000 * tau * t) * (t % 0.5 < 0.0625)' waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # start by joining all arguments given as a tab-separated-items line of output, # followed by all lines from stdin verbatim begintsv() { awk 'BEGIN { for (i = 1; i < ARGC; i++) { if (i > 1) { printf "\t" } printf "%s", ARGV[i] delete ARGV[i] } printf "\n" } { print; fflush() }' "$@" } # play a repeating synthetic-bell-like sound lasting the number of seconds # given, or for 1 second by default; uses my script `waveout` bell() { local f='sin(880*tau*u) * exp(-10*u)' waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # play a repeating sound with synthetic-bells, lasting the number of seconds # given, or for 1 second by default; uses my script `waveout` bells() { local f="sum(sin(880*tau*v)*exp(-10*v) for v in (u, (u-0.25)%1)) / 2" waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # Breathe Header: add an empty line after the first one (the header), then # separate groups of 5 lines (by default) with empty lines between them bh() { local n="${1:-5}" [ $# -gt 0 ] && shift awk -v n="$n" ' (NR - 1) % n == 1 && NR > 1 { print "" } { print; fflush() }' "$@" } # recursively find all files with at least the number of bytes given bigfiles() { local n n="$(echo "${1}" | sed 's-_--g')" find "${2:-.}" -size "$n"c -o -size +"$n"c } # Breathe Lines: separate groups of 5 lines (by default) with empty lines bl() { local n="${1:-5}" [ $# -gt 0 ] && shift awk -v n="$n" ' NR % n == 1 && NR != 1 { print "" } { print; fflush() }' "$@" } # process BLocks/paragraphs of non-empty lines with AWK blawk() { awk -F='' -v RS='' "$@"; } # Blue LEAK emits/tees input both to stdout and stderr, coloring blue what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` # pipes involving several steps bleak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[38;5;26m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # emit a line with a repeating block-like symbol in it blocks() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -█-g'; } # expand all tabs into runs of spaces blow() { local tabstop="${1:-4}" [ $# -gt 0 ] && shift expand -t "${tabstop}" "$@" } # expand all tabs into runs of spaces blowtabs() { local tabstop="${1:-4}" [ $# -gt 0 ] && shift expand -t "${tabstop}" "$@" } # BREATHE lines: separate groups of 5 lines (by default) with empty lines breathe() { local n="${1:-5}" [ $# -gt 0 ] && shift awk -v n="$n" ' NR % n == 1 && NR != 1 { print "" } { print; fflush() }' "$@" } # show a reverse-sorted tally of all lines read, where ties are sorted # alphabetically, and where trailing bullets are added to quickly make # the tally counts comparable at a glance bully() { printf "value\ttally\tbullets\n" awk ' { tally[$0]++ } END { # find the max tally, which is needed to build the bullets-string max = 0 for (k in tally) { if (max < tally[k]) max = tally[k] } # make enough bullets for all tallies: this loop makes growing the # string a task with complexity O(n * log n), instead of a naive # O(n**2), which can slow-down things when tallies are high enough bullets = "•" for (n = max; n > 1; n /= 2) { bullets = bullets bullets } # emit unsorted output lines to the sort cmd, which will emit the # final reverse-sorted tally lines for (k in tally) { s = substr(bullets, 1, tally[k]) printf "%s\t%d\t%s\n", k, tally[k], s } } ' "$@" | sort -t "$(printf "\t")" -rnk2 -k1d } # play a busy-phone-line sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` busy() { # local f='(u < 0.5) * (sin(480*tau * t) + sin(620*tau * t)) / 2' local f='min(1, exp(-90*(u-0.5))) * (sin(480*tau*t) + sin(620*tau*t)) / 2' # local f='(sin(350*tau*t) + sin(450*tau*t)) / 2 * min(1, exp(-90*(u-0.5)))' waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # keep all BUT the FIRST (skip) n lines, or skip just the 1st line by default butfirst() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } # keep all BUT the LAST n lines, or skip just the last line by default butlast() { head -n -"${1:-1}" "${2:--}"; } c() { cat "$@"; } # CAlculator with Nice numbers runs my script `ca` and colors results with # my script `nn`, alternating styles to make long numbers easier to read can() { ca "$@" | nn; } # conCATenate Lines catl() { awk ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { print; fflush() }' "$@" | sed -E 's-\r$--' } # Csv AWK: CSV-specific input settings for `awk` # cawk() { awk --csv -v OFS="\t" "$@"; } # Csv AWK: CSV-specific input settings for `awk` cawk() { awk --csv "$@"; } # Colored Go Test on the folder given; uses my script `gbm` cgt() { go test "${1:-.}" 2>&1 | gbm '/^ok/' '/^[-]* ?FAIL/' '/^\?/'; } # lookup docs/examples using the curl-friendly website cheat.sh cheat() { local cmd="bat" # debian linux uses a different name for the `bat` app if [ -e "/usr/bin/batcat" ]; then cmd="batcat" fi if [ -z "$1" ]; then curl -s cheat.sh/ | awk '1 { gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, ""); print }' return fi # all but the top-level page can be shell-syntax colored curl -s "cheat.sh/$1" | awk '1 { gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, ""); print }' | "$cmd" -l sh \ --style=plain --theme='Monokai Extended Light' \ --wrap=never --color=always - | sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' } # CHOP DECimalS ignores all trailing decimal zeros in numbers, even the # decimal dots themselves, when decimals in a number are all zeros chopdecs() { awk 1 "$@" | sed -E 's-(\.[0-9]+[1-9]+)0+$-\1-g; s-([0-9]+)\.0*$-\1-g' } # ignore final life-feed from text, if it's the very last byte; also ignore # all trailing carriage-returns choplf() { awk ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } NR > 1 { print ""; fflush() } { gsub(/\r$/, ""); printf "%s", $0 } ' "$@" } # Color Json using the `jq` app, allowing an optional filepath as the data # source, and even an optional transformation formula cj() { jq -C "${2:-.}" "${1:--}"; } # show a live digital clock clock() { watch -n 1 echo 'Press Ctrl + C to quit this clock'; } # CLear Screen, like the old dos command of the same name cls() { clear; } # COunt COndition: count how many times the AWK expression given is true coco() { local cond="${1:-1}" [ $# -gt 0 ] && shift awk " { low = lower = tolower(\$0) } ${cond} { count++ } END { print count } " "$@" } # COlor SYntax: run syntax-coloring app `bat` without line-wrapping cosy() { local cmd="bat" # debian linux uses a different name for the `bat` app if [ -e "/usr/bin/batcat" ]; then cmd="batcat" fi "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ --wrap=never --color=always "$@" | sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS } # COlor SYntax of all files in a FOlder, showing line numbers cosyfo() { local cmd="bat" # debian linux uses a different name for the `bat` app if [ -e "/usr/bin/batcat" ]; then cmd="batcat" fi find "${1:-.}" -type f -print0 | xargs --null "$cmd" \ --style=plain,header,numbers --theme='Monokai Extended Light' \ --wrap=never --color=always | sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS } # Colored RipGrep ensures app `rg` emits colors when piped crg() { rg --color=always "$@"; } # emit a line with a repeating cross-like symbol in it crosses() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -×-g' } # Color Syntax: run syntax-coloring app `bat` without line-wrapping cs() { local cmd="bat" # debian linux uses a different name for the `bat` app if [ -e "/usr/bin/batcat" ]; then cmd="batcat" fi "$cmd" --style=plain,header,numbers --theme='Monokai Extended Light' \ --wrap=never --color=always "$@" | sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS } # Color Syntax of all files in a Folder, showing line numbers csf() { local cmd="bat" # debian linux uses a different name for the `bat` app if [ -e "/usr/bin/batcat" ]; then cmd="batcat" fi find "${1:-.}" -type f -print0 | xargs --null "$cmd" \ --style=plain,header,numbers --theme='Monokai Extended Light' \ --wrap=never --color=always | sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS } # Change Units turns common US units into international ones; uses my # scripts `bu` (Better Units) and `nn` (Nice Numbers) cu() { bu "$@" | awk ' NF == 5 { print $(NF-1), $NF } NF == 4 && $NF == "s" { print $(NF-1), $NF } NF == 4 && $NF != "s" { print $NF } ' | nn } # run CURL in Silent/quiet mode curls() { curl -s "$@"; } # Date and time d() { printf "\e[32m%s\e[0m \e[34m%s\e[0m\n" "$(date +'%a %b %d')" "$(date +%T)" } # Date and time # d() { date "$@"; } # emit a line with a repeating ball-like symbol in it dashes() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -—-g'; } # DEcode BASE-64-encoded data debase64() { base64 -d "$@"; } # DEDUPlicate prevents lines from appearing more than once dedup() { awk '!c[$0]++' "$@"; } # DEDUPlicatE prevents lines from appearing more than once dedupe() { awk '!c[$0]++' "$@"; } # dictionary-DEFine the word given, using an online service def() { curl -s "dict://dict.org/d:$*" | sed 's-\r--g' | awk ' { gsub(/\r/, "") } /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } { print; fflush() }' | less -JMKiCRS } # dictionary-define the word given, using an online service define() { curl -s "dict://dict.org/d:$*" | sed 's-\r--g' | awk ' { gsub(/\r/, "") } /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } { print; fflush() }' | less -JMKiCRS } # DEcompress GZip-encoded data degz() { zcat "$@"; } # DEcompress GZIP-encoded data degzip() { zcat "$@"; } # turn json lines into a proper json array dejsonl() { jq -s -M "${@:-.}"; } # delay lines being piped from standard input; the delay is given as a number # of seconds, and is by default 1, when not given delaylines() { ( IFS="$(printf "\n")" while read -r l; do sleep "${1:-1}" printf "%s\n" "${l}" done ) } # turn lines of Space-Separated Values into lines of Tab-Separated Values dessv() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?--; s- +-\t-g'; } # turn UTF-16 data into UTF-8 deutf16() { iconv -f utf16 -t utf8 "$@"; } # lookup words using an online DICtionary dic() { curl -s "dict://dict.org/d:$*" | awk ' { gsub(/\r/, "") } /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } { print; fflush() }' | less -JMKiCRS } # lookup words using an online DICTionary dict() { curl -s "dict://dict.org/d:$*" | awk ' { gsub(/\r/, "") } /^151 / { printf "\x1b[38;5;4m%s\x1b[0m\n", $0; next } /^[1-9][0-9]{2} / { printf "\x1b[38;5;244m%s\x1b[0m\n", $0; next } { print; fflush() }' | less -JMKiCRS } # DIVide 2 numbers 3 ways, including the complement div() { awk -v a="${1:-1}" -v b="${2:-1}" ' BEGIN { gsub(/_/, "", a); gsub(/_/, "", b) if (a > b) { c = a; a = b; b = c; } printf "%f\n%f\n%f\n", a / b, b / a, 1 - a / b exit }' } # get/fetch data from the filename or URI given; named `dog` because dogs can # `fetch` things for you dog() { case "$1" in http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) [ -e "$1" ] && cat "$1" || curl -s "$1";; file://*) [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; *) cat "$1";; esac 2> /dev/null || { printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 return 1 } } # emit a line with a repeating dot-like symbol in it dots() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed 's- -·-g'; } # show the current Date and Time dt() { printf "\e[32m%s\e[0m \e[34m%s\e[0m\n" "$(date +'%a %b %d')" "$(date +%T)" } # show the current Date, Time, and a Calendar with the 3 `current` months dtc() { # show the current time center-aligned printf "%22s\e[32m%s\e[0m \e[34m%s\e[0m\n" \ " " "$(date +'%a %b %d')" "$(date +%T)" printf "\n" # debian linux has a different `cal` app which highlights the day if [ -e "/usr/bin/ncal" ]; then ncal -C -3 else cal -3 fi } # Download from Youtube dy() { yt-dlp "$@"; } # Download Youtube AAC audio dyaac() { yt-dlp -f 140 "$@"; } # Download Youtube MP4 video dymp4() { yt-dlp -f 22 "$@"; } # Evaluate Awk expression ea() { awk "BEGIN { print ${1:-0}; exit }"; } # Evaluate Awk expression # ea() { awk --bignum "BEGIN { print ${1:-0}; exit }"; } # run a command for each of the files given as lines from standard input each() { ( IFS="$(printf "\n")" while read -r name; do "$@" < "${name}" err="$?" if [ "${err}" -ne 0 ]; then exit "${err}" fi done ) } # ECHO Background, using gray-colored background echob() { printf "\e[48;5;253m%s\e[0m\n" "$*"; } # Green ECHO, using a green style gecho() { printf "\e[38;5;29m%s\e[0m\n" "$*"; } # ECHO Highlighted, using inverted style echoh() { printf "\e[7m%s\e[0m\n" "$*"; } # ECHO Inverted, using inverted style echoi() { printf "\e[7m%s\e[0m\n" "$*"; } # ECHO Lines emits each argument given as its own line of output echol() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" } # edit plain-text files # edit() { micro "$@"; } # edit plain-text files # edit() { tilde "$@"; } # Extended-mode Grep, enabling its full regex syntax # eg() { grep -E "$@"; } # Extended-mode Grep, enabling its full regex syntax eg() { grep -E --line-buffered "$@"; } # Echo Lines emits each argument given as its own line of output el() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@"; } # Evaluate Nodejs expression en() { local expr="${1:-null}" expr="$(echo "${expr}" | sed 's-\\-\\\\-g; s-`-\`-g')" node -e "console.log(${expr})" | sed 's-\x1b\[[^A-Za-z]+[A-Za-z]--g' } # Evaluate Python expression ep() { python -c "print(${1:-None})"; } # Evaluate Ruby expression er() { ruby -e "puts ${1:-nil}"; } # Editor Read-Only ero() { micro -readonly true "$@"; } # Extended-mode Sed, enabling its full regex syntax es() { sed -E -u "$@"; } # f() { find "$@"; } # run the fuzzy Finder (fzf) in multi-choice mode, with custom keybindings f() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } # convert Fahrenheit into Celsius # f2c() { # echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | # awk '{ print ($0 - 32) * 9.0/5.0 }' # } # convert FAHRenheit into celsius fahr() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print ($0 - 32) * 9.0/5.0 }' } # convert fahrenheit into celsius fahrenheit() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print ($0 - 32) * 9.0/5.0 }' } # Faint (lines) with AWK, using a gray color fawk() { local cond="${1:-1}" [ $# -gt 0 ] && shift awk "${cond} { gsub(/\\x1b\\[0m/, \"\x1b[0m\\x1b[38;5;248m\") printf \"\\x1b[38;5;248m%s\\x1b[0m\\n\", \$0 fflush() next } { print fflush() }" "$@" } # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings ff() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } # deduplicate lines by the field given, keeping them in their original order fieldedup() { local n="${1:-0}" [ $# -gt 0 ] && shift awk "!c[(\$${n} >= 0) ? \$${n} : \$(NF + ${n} - 1)]++" "$@" } # show all files in a folder, digging recursively files() { local arg for arg in "${@:-.}"; do find "${arg}" -type f done } # get the first n lines, or 1 by default first() { head -n "${1:-1}" "${2:--}"; } # limit data up to the first n bytes firstbytes() { head -c "$1" "${2:--}"; } # get the first n lines, or 1 by default firstlines() { head -n "${1:-1}" "${2:--}"; } # fix lines, ignoring leading UTF-8_BOMs (byte-order-marks) on each input's # first line, turning all end-of-line CRLF byte-pairs into single line-feeds, # and ensuring each input's last line ends with a line-feed fixlines() { awk ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { print; fflush() }' "$@" | sed -E 's-\r$--' } # Faint LEAK emits/tees input both to stdout and stderr, coloring gray what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps fleak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[38;5;248m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # Faster Nice Hex tries to speed-up my script `nh` via `pypy` fnh() { pypy3 "$(which nh)" "$@"; } # show all folders in a folder, digging recursively folders() { local arg for arg in "${@:-.}"; do find "${arg}" -type d | awk 'NR > 1' done } # make text lines look "FRAMEd" by padding them to the current terminal # width and using ANSI-style background colors; uses my script `tl` frame() { local color="${1:-green}" [ $# -gt 0 ] && shift tl "${color}back(f'{plain(line):$(tput cols)}')" "$@" } # start from the line number given, skipping all previous ones fromline() { tail -n +"${1:-1}" "${2:--}"; } # convert FeeT into meters ft() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.3048 * $0 }' } # convert FeeT² (squared) into meters² ft2() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.09290304 * $0 }' } # convert a mix of FeeT and INches into meters ftin() { local ft="${1:-0}" ft="$(echo "${ft}" | sed 's-_--g')" local in="${2:-0}" in="$(echo "${in}" | sed 's-_--g')" awk "BEGIN { print 0.3048 * ${ft} + 0.0254 * ${in}; exit }" } # Faster Transform Json, by using pypy instead of standard python ftj() { pypy3 "$(which tj)" "$@"; } # Faster Transform Lines, by using pypy instead of standard python ftl() { pypy3 "$(which tl)" "$@"; } # run the FuZzy finder (fzf) in multi-choice mode, with custom keybindings fz() { fzf -m --bind ctrl-a:select-all,ctrl-space:toggle "$@"; } # Get/fetch data from the filename or URI given # g() { # case "$1" in # http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) # [ -e "$1" ] && cat "$1" || curl -s "$1";; # file://*) # [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; # *) # cat "$1";; # esac 2> /dev/null || { # printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 # return 1 # } # } # run `grep` in extended mode, enabling its full regex syntax # g() { grep -E "$@"; } # run `grep` in extended mode, enabling its full regex syntax g() { grep -E --line-buffered "$@"; } # convert GALlons into liters gal() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 3.785411784 * $0 }' } # GCCGO Build Stripped: a niche use-case for the go/gccgo compilers gccgobs() { go build -compiler gccgo -gccgoflags "-s -O2" -trimpath "$@"; } # Green-colored ECHO gecho() { printf "\e[38;5;29m%s\e[0m\n" "$*"; } # GET/fetch data from the filename or URI given # get() { # case "$1" in # http://*|https://*|ftp://*) [ -e "$1" ] && cat "$1" || wget -O - "$1";; # *) cat "$1";; # esac 2> /dev/null || { # printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 # return 1 # } # } # GET/fetch data from the filename or URI given get() { case "$1" in http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) [ -e "$1" ] && cat "$1" || curl -s "$1";; file://*) [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; *) cat "$1";; esac 2> /dev/null || { printf "\e[31mcan't get %s\e[0m\n" "$1" >&2 return 1 } } # Green LEAK emits/tees input both to stdout and stderr, coloring green what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps gleak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[38;5;29m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # GO Build Stripped: a common use-case for the go compiler gobs() { go build -ldflags "-s -w" -trimpath "$@"; } # GO DEPendencies: shows all dependencies in a go project # godep() { go list -f '{{ join .Deps "\n" }}' "$@"; } # GO DEPendencieS: shows all dependencies in a go project godeps() { go list -f '{{ join .Deps "\n" }}' "$@"; } # GO IMPorts: show all imports in a go project goimp() { go list -f '{{ join .Imports "\n" }}' "$@"; } # GO IMPortS: show all imports in a go project goimps() { go list -f '{{ join .Imports "\n" }}' "$@"; } # Grep, Recursive Interactive and Plain grip() { ugrep -r -Q --color=never -E "$@"; } # transform lines using AWK's `gsub` (Global-regex SUBstitution) function gsub() { local what="$1" local with="$2" [ $# -gt 0 ] && shift [ $# -gt 0 ] && shift awk "{ gsub(/${what}/, \"${with}\"); print; fflush() }" "$@" } # show Help for the command given h() { "$@" --help || "$@" -h || "$@" --h || "$@" -help || "$@" help; } # Highlight (lines) with AWK hawk() { local cond="${1:-1}" [ $# -gt 0 ] && shift awk "${cond} { gsub(/\\x1b\\[0m/, \"\x1b[0m\\x1b[7m\") printf \"\\x1b[7m%s\\x1b[0m\\n\", \$0 fflush() next } { print fflush() }" "$@" } # play a heartbeat-like sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` # heartbeat() { # local f='sum(sin(10*tau*exp(-20*v))*exp(-2*v) for v in (u, (u-0.25)%1))/2' # waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - # } # play a heartbeat-like sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` heartbeat() { local a='sin(v[0]*tau*exp(-20*v[1]))*exp(-2*v[1])' local b='((12, u), (8, (u-0.25)%1))' local f="sum($a for v in $b) / 2" waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # Highlighted-style ECHO hecho() { printf "\e[7m%s\e[0m\n" "$*"; } # show each byte as a pair of HEXadecimal (base-16) symbols hexify() { cat "$@" | od -x -A n | sed 's- --g' | awk '{ printf "%s", $0 } END { printf "\n" }' } # HIghlighted-style ECHO hiecho() { printf "\e[7m%s\e[0m\n" "$*"; } highlight() { awk '{ gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") printf "\x1b[7m%s\x1b[0m\n", $0 fflush() }' "$@" } # HIghlight LEAK emits/tees input both to stdout and stderr, highlighting what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps hileak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } hilite() { awk '{ gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") printf "\x1b[7m%s\x1b[0m\n", $0 fflush() }' "$@" } # Header Less runs `less` with line numbers, ANSI styles, no line-wrapping, # and using the first line as a sticky-header, so it always shows on top hl() { less --header=1 -JMKNiCRS "$@"; } # Highlight LEAK emits/tees input both to stdout and stderr, highlighting what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps hleak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # Help Me Remember my custom shell commands hmr() { local cmd="bat" # debian linux uses a different name for the `bat` app if [ -e "/usr/bin/batcat" ]; then cmd="batcat" fi "$cmd" \ --style=plain,header,numbers --theme='Monokai Extended Light' \ --wrap=never --color=always "$(which clam)" | sed 's-\x1b\[38;5;70m-\x1b\[38;5;28m-g' | less -JMKiCRS } # convert seconds into a colon-separated Hours-Minutes-Seconds triple hms() { echo "${@:-0}" | sed 's-_--g; s- *-\n-g' | awk '{ x = $0 h = (x - x % 3600) / 3600 m = (x % 3600) / 60 s = x % 60 printf "%02d:%02d:%05.2f\n", h, m, s }' } # Header View runs `less` without line numbers, with ANSI styles, with no # line-wrapping, and using the first line as a sticky-header, so it always # shows on top hv() { less --header=1 -JMKiCRS "$@"; } # Index all lines starting from 0, using a tab right after each line number i() { nl -b a -w 1 -v 0 "$@"; } # avoid/ignore lines which case-insensitively match any of the regexes given iavoid() { awk ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { l = tolower($0); for (i in re) if (l ~ re[i]) { next } } { print; fflush() }' "${@:-^$}" } # pipe-identity operation which ignores all arguments given, which is useful # to quickly disable a step along a long pipe of commands (`idem` is latin # for `same`) idem() { cat; } # pipe-IDENtity operation which ignores all arguments given, which is useful # to quickly disable a step along a long pipe of commands iden() { cat; } # pipe-identity operation which ignores all arguments given, which is useful # to quickly disable a step along a long pipe of commands identity() { cat; } # Inverted-style ECHO iecho() { printf "\e[7m%s\e[0m\n" "$*"; } # Invert LEAK emits/tees input both to stdout and stderr, inverse-styling what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps ileak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[7m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # only keep lines which case-insensitively match any of the regexes given imatch() { awk ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { l = tolower($0) for (i in re) if (l ~ re[i]) { print; fflush(); next } }' "${@:-.}" } # emit each word-like item from each input line on its own line items() { awk '{ for (i = 1; i <= NF; i++) print $i; fflush() }' "$@"; } # shrink/compact Json data, allowing an optional filepath # j0() { python -m json.tool --compact "${1:--}"; } # shrink/compact Json using the `jq` app, allowing an optional filepath, and # even an optional transformation formula after that # j0() { jq -c -M "${2:-.}" "${1:--}"; } # show Json data on multiple lines, using 2 spaces for each indentation level, # allowing an optional filepath # j2() { python -m json.tool --indent 2 "${1:--}"; } # show Json data on multiple lines, using 2 spaces for each indentation level, # allowing an optional filepath, and even an optional transformation formula # after that # j2() { jq --indent 2 -M "${2:-.}" "${1:--}"; } # listen to streaming JAZZ music # jazz() { # printf "streaming \e[7mSmooth Jazz Instrumental\e[0m\n" # mpv https://stream.zeno.fm/00rt0rdm7k8uv # } # listen to streaming JAZZ music jazz() { printf "streaming \e[7mSmooth Jazz Instrumental\e[0m\n" mpv --quiet https://stream.zeno.fm/00rt0rdm7k8uv } # show a `dad` JOKE from the web, sometimes even a very funny one joke() { curl -s https://icanhazdadjoke.com | fold -s | sed -E 's- *\r?$--' # plain-text output from previous cmd doesn't end with a line-feed printf "\n" } # shrink/compact JSON data, allowing an optional filepath # json0() { python -m json.tool --compact "${1:--}"; } # shrink/compact JSON using the `jq` app, allowing an optional filepath, and # even an optional transformation formula after that json0() { jq -c -M "${2:-.}" "${1:--}"; } # show JSON data on multiple lines, using 2 spaces for each indentation level, # allowing an optional filepath # json2() { python -m json.tool --indent 2 "${1:--}"; } # show JSON data on multiple lines, using 2 spaces for each indentation level, # allowing an optional filepath, and even an optional transformation formula # after that json2() { jq --indent 2 -M "${2:-.}" "${1:--}"; } # emit the given number of random/junk bytes, or 1024 junk bytes by default junk() { head -c "$(echo "${1:-1024}" | sed 's-_--g')" /dev/urandom; } # run `less`, showing line numbers, among other settings l() { less -JMKNiCRS "$@"; } # Like A Book groups lines as 2 side-by-side pages, the same way books # do it; uses my script `book` lab() { book "$(($(tput lines) - 1))" "$@" | less -JMKiCRS; } # Line xARGS: `xargs` using line separators, which handles filepaths # with spaces, as long as the standard input has 1 path per line largs() { xargs -d '\n' "$@"; } # get the last n lines, or 1 by default # last() { tail -n "${1:-1}" "${2:--}"; } # get up to the last given number of bytes lastbytes() { tail -c "${1:-1}" "${2:--}"; } # get the last n lines, or 1 by default lastlines() { tail -n "${1:-1}" "${2:--}"; } # turn UTF-8 into its latin-like subset, where variants of latin letters stay # as given, and where all other symbols become question marks, one question # mark for each code-point byte latinize() { iconv -f utf-8 -t latin-1//translit "$@" | iconv -f latin-1 -t utf-8 } # convert pounds (LB) into kilograms lb() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.45359237 * $0 }' } # convert pounds (LBS) into kilograms lbs() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 0.45359237 * $0 }' } # convert a mix of pounds (LB) and weight-ounces (OZ) into kilograms lboz() { local lb="${1:-0}" lb="$(echo "${lb}" | sed 's-_--g')" local oz="${2:-0}" oz="$(echo "${oz}" | sed 's-_--g')" awk "BEGIN { print 0.45359237 * ${lb} + 0.028349523 * ${oz}; exit }" } # leak stdin to stderr, before also copying it to stdout: its main use is to # inspect/debug the intermediate stages of a `pipelined` shell command # leak() { # awk '{ # gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") # printf "\x1b[38;5;248m%s\x1b[0m\n", $0 > "/dev/stderr" # print # fflush() # }' "$@" # } # LEAK Orange emits/tees input both to stdout and stderr, coloring orange what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps leako() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[38;5;166m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # run `less`, showing line numbers, among other settings least() { less -JMKNiCRS "$@"; } # Lines ECHO emits each argument given as its own line of output lecho() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" } # limit stops at the first n bytes, or 1024 bytes by default limit() { head -c "$(echo "${1:-1024}" | sed 's-_--g')" "${2:--}"; } # Less with Header runs `less` with line numbers, ANSI styles, no line-wraps, # and using the first line as a sticky-header, so it always shows on top lh() { less --header=1 -JMKNiCRS "$@"; } # regroup adjacent lines into n-item tab-separated lines lineup() { local n="${1:-0}" [ $# -gt 0 ] && shift if [ "$n" -le 0 ]; then awk ' NR > 1 { printf "\t" } { printf "%s", $0 } END { if (NR > 0) print "" } ' "$@" return $? fi awk -v n="$n" ' NR % n != 1 { printf "\t" } { printf "%s", $0 } NR % n == 0 { print ""; fflush() } END { if (NR % n != 0) print "" } ' "$@" } # LOAD data from the filename or URI given load() { case "$1" in http://*|https://*|ftp://*|ftps://*|sftp://*|dict://*) [ -e "$1" ] && cat "$1" || curl -s "$1";; file://*) [ -e "$1" ] && cat "$1" || cat "$(echo "$1" | sed 's-file://--1')";; *) cat "$1";; esac 2> /dev/null || { printf "\e[31mcan't load %s\e[0m\n" "$1" >&2 return 1 } } # LOwercase line, check (awk) COndition: on each success, the original line # is output with its original letter-casing, as its lower-cased version is # only a convenience meant for the condition loco() { local cond="${1:-1}" [ $# -gt 0 ] && shift awk " { line = orig = original = \$0 low = lower = tolower(\$0) \$0 = lower } ${cond} { print line; fflush() } " "$@" } # LOcal SERver webserves files in a folder as localhost, using the port # number given, or port 8080 by default loser() { # printf "\e[38;5;26mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 python3 -m http.server "${1:-8080}" -d "${2:-.}" } # LOWERcase all ASCII symbols lower() { awk '{ print tolower($0); fflush() }' "$@"; } # LOWERCASE all ASCII symbols lowercase() { awk '{ print tolower($0); fflush() }' "$@"; } # LiSt files, showing 4096-byte filesystem Page counts lsp() { ls -s --block-size=4096 "$@"; } # Listen To Youtube lty() { local url # some youtube URIs end with extra playlist/tracker parameters url="$(echo "$1" | sed 's-&.*--')" # mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" } # Match lines with any of the regexes given m() { awk ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { for (i in re) if ($0 ~ re[i]) { print; fflush(); next } }' "${@:-.}" } # only keep lines which match any of the regexes given match() { awk ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { for (i in re) if ($0 ~ re[i]) { print; fflush(); next } }' "${@:-.}" } # merge stderr into stdout, without any ugly keyboard-dancing merrge() { "$@" 2>&1; } # convert MIles into kilometers mi() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 1.609344 * $0 }' } # Make In Folder # mif() { # pushd "${1:-.}" > /dev/null || return # [ $# -gt 0 ] && shift # make "$@" # popd > /dev/null || return # } # convert MIles² (squared) into kilometers² mi2() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 2.5899881103360 * $0 }' } # convert Miles Per Hour into kilometers per hour mph() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 1.609344 * $0 }' } # Number all lines, starting from the number given, or 1 by default # n() { # local n="${1:-1}" # [ $# -gt 0 ] && shift # awk -v n="$n" '{ printf "%d\t%s\n", NR - 1 + n, $0; fflush() }' "$@" # } # Number all lines, starting from the number given, or 1 by default # n() { # local n="${1:-1}" # [ $# -gt 0 ] && shift # nl -b a -w 1 -v "$n" "$@" # } # Number all lines, using a tab right after each line number n() { nl -b a -w 1 "$@"; } # Nice Byte Count, using my scripts `nn` and `cext` nbc() { wc -c "$@" | nn | cext; } # Nice numbers CAlculator runs my script `ca` and colors results with my # script `nn`, alternating styles to make long numbers easier to read nca() { ca "$@" | nn; } # No (standard) Error ignores stderr, without any ugly keyboard-dancing ne() { "$@" 2> /dev/null; } # No (standard) ERRor ignores stderr, without any ugly keyboard-dancing nerr() { "$@" 2> /dev/null; } # Nice File Sizes, using my scripts `nn` and `cext` nfs() { # turn arg-list into single-item lines awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@" | # calculate file-sizes, and reverse-sort results xargs -d '\n' wc -c | sort -rn | # start output with a header-like line, and add a MiB field awk 'BEGIN { printf "%6s %10s %8s name\n", "n", "bytes", "MiB" } { printf "%6d %10d %8.2f %s\n", NR - 1, $1, $1 / 1048576, $2 }' | # make zeros in the MiB field stand out with a special color awk '{ gsub(/ 0.00 /, "\x1b[38;5;103m 0.00 \x1b[0m"); print }' | # make numbers nice, alternating styles along 3-digit groups nn | # color-code file extensions cext | # make table breathe with empty lines, so tall outputs are readable awk '(NR - 2) % 5 == 1 && NR > 1 { print "" } 1' } # Nice Hex Faster tries to speed-up my script `nh` via `pypy` nhf() { pypy3 "$(which nh)" "$@"; } # NIce numbers CAlculator runs my script `ca` and colors results with my # script `nn`, alternating styles to make long numbers easier to read nica() { ca "$@" | nn; } # emit nothing to output and/or discard everything from input nil() { if [ -p /dev/stdin ]; then cat > /dev/null else head -c 0 fi } # convert Nautical MIles into kilometers nmi() { echo "${@:-1}" | sed 's-_--g; s- *-\n-g' | awk '{ print 1.852 * $0 }' } # NO (standard) ERRor ignores stderr, without any ugly keyboard-dancing noerr() { "$@" 2> /dev/null; } # play a white-noise sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` noice() { waveout "${1:-1}" "${2:-0.05} * random()" | mpv --really-quiet -; } # play a white-noise sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` noise() { waveout "${1:-1}" "${2:-0.05} * random()" | mpv --really-quiet -; } # show the current date and time now() { date +'%Y-%m-%d %H:%M:%S'; } # Nice Size, using my scripts `nn` and `cext` ns() { wc -c "$@" | nn | cext; } # No Standard Error ignores stderr, without any ugly keyboard-dancing nse() { "$@" 2> /dev/null; } # Nice Transform Json, using my scripts `tj`, and `nj` # ntj() { tj "$@" | nj; } # Nice Transform Json, using my scripts `tj`, `nj`, and `nn` ntj() { tj "$@" | nj | nn; } # Nice numbers Word-Count runs `wc` and colors results with my script `nn`, # alternating styles to make long numbers easier to read nwc() { wc "$@" | nn; } # Nice Zoom Json, using my scripts `zj`, and `nj` # nzj() { zj "$@" | nj; } # Nice Zoom Json, using my scripts `zj`, `nj`, and `nn` nzj() { zj "$@" | nj | nn; } # unify Output by merging stderr into stdout, without any keyboard-dancing o() { "$@" 2>&1; } # Orange LEAK emits/tees input both to stdout and stderr, coloring orange what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps oleak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[38;5;166m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # emit each unique line only the first time, ignoring all later occurrences once() { awk '!c[$0]++' "$@"; } # open/pop-up files, GUI file-explorers for folders, and even web-browsers # for any URIs given; only works on the windows subsystem for linux # open() { # local arg # for arg in "${@:-.}"; do # cmd.exe /c start "$(echo "$arg" | sed 's-/$--; s-/-\\-g')" # done | cat # } # emit each unique line only the first time, ignoring all later occurrences; # this is equivalent to an `or` operation on sets of lines or() { awk '!c[$0]++' "$@"; } # make text Plain, by ignoring ANSI terminal styling p() { awk '{ # ignore notifications (code 9) and hyperlinks (code 8) gsub(/\x1b\](8|9);[^\x07]*\x07/, "") # ignore cursor-movers and style-changers gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") print fflush() }' "$@" } # ignore all arguments given, which is useful to quickly disable a step along # a long pipe of commands pass() { cat; } # ignore all arguments given, which is useful to quickly disable a step along # a long pipe of commands passthru() { cat; } # Paragraph AWK runs `awk` in block/paragraph/multiline input-mode pawk() { awk -F='' -v RS='' "$@"; } # Plain Interactive Grep pig() { ugrep --color=never -Q -E "$@"; } # make text plain, by ignoring ANSI terminal styling plain() { awk '{ # ignore notifications (code 9) and hyperlinks (code 8) gsub(/\x1b\](8|9);[^\x07]*\x07/, "") # ignore cursor-movers and style-changers gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") print fflush() }' "$@" } # play audio/video media, possibly using a GUI play() { mpv "${@:--}"; } # Purple LEAK emits/tees input both to stdout and stderr, coloring purple what # it emits to stderr using an ANSI-style; this cmd is useful to `debug` pipes # involving several steps pleak() { awk '{ gsub(/\x1b\[([0-9]*[A-HJKSThl]|[0-9;]*m)/, "") printf "\x1b[38;5;99m%s\x1b[0m\n", $0 > "/dev/stderr" print fflush() }' "$@" } # PLay data from standard INput plin() { mpv "$@" -; } # Paused MPV pmpv() { mpv --pause "${@:--}"; } # Print Python result pp() { python -c "print($1)"; } # PRint AWK result # prawk() { awk "BEGIN { print ${1:-0}; exit }"; } # PRint AWK result # prawk() { awk --bignum "BEGIN { print ${1:-0}; exit }"; } # start by joining all arguments given as a tab-separated-items line of output, # followed by all lines from stdin verbatim pretsv() { awk 'BEGIN { for (i = 1; i < ARGC; i++) { if (i > 1) { printf "\t" } printf "%s", ARGV[i] delete ARGV[i] } printf "\n" } { print; fflush() }' "$@" } # Plain Recursive Interactive Grep prig() { ugrep --color=never -r -Q -E "$@"; } # Print RUBY expression # pruby() { ruby -e "puts ${1:-nil}"; } # PRint pYTHON result # prython() { python -c "print(${1:-None})"; } # Play Youtube Audio pya() { local url # some youtube URIs end with extra playlist/tracker parameters url="$(echo "$1" | sed 's-&.*--')" # mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" } # Quiet ignores stderr, without any ugly keyboard-dancing q() { "$@" 2> /dev/null; } # Quiet MPV qmpv() { mpv --quiet "${@:--}"; } # ignore stderr, without any ugly keyboard-dancing quiet() { "$@" 2> /dev/null; } # Quiet cURL qurl() { curl -s "$@"; } # Reset the screen, which empties it and resets the current style r() { reset; } # keep only lines between the 2 line numbers given, inclusively rangelines() { { [ "$#" -eq 2 ] || [ "$#" -eq 3 ]; } && [ "${1}" -le "${2}" ] && { tail -n +"${1:-1}" "${3:--}" | head -n "$(("${2}" - "${1}" + 1))"; } } # RANdom MANual page ranman() { find "/usr/share/man/man${1:-1}" -type f | shuf -n 1 | xargs basename | sed 's-\.gz$--g' | xargs man } # play a ready-phone-line sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` ready() { local f='0.5 * sin(350*tau*t) + 0.5 * sin(450*tau*t)' waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # emit a line using the symbol given, REPeated a number of times (1 by default) rep() { local what="${1}" local times="${2:-1}" [ "${times}" -gt 0 ] && printf "%${times}s\n" " " | if [ "${what}" = ' ' ]; then cat elif [ "${what}" = '-' ]; then sed "s/ /${what}/g" else sed "s- -${what}-g" fi } # reflow/trim lines of prose (text) to improve its legibility: it's especially # useful when the text is pasted from web-pages being viewed in reader mode reprose() { local w="${1:-80}" [ $# -gt 0 ] && shift awk 'FNR == 1 && NR > 1 { print "" } { print; fflush() }' "$@" | fold -s -w "$w" | sed -E 's- *\r?$--' } # change color/style of lines, using my script `tl` restyle() { local style="$1" [ $# -gt 0 ] && shift tl "${style}(plain(l))" "$@" } # play a ringtone-style sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` ring() { local f='sin(2048 * tau * t) * exp(-50 * (t%0.1))' waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # play a ringtone-style sound lasting the number of seconds given, or for 1 # second by default; uses my script `waveout` ringtone() { local f='sin(2048 * tau * t) * exp(-50 * (t%0.1))' waveout "${1:-1}" "${2:-1} * $f" | mpv --really-quiet - } # Read-Only Micro (text editor) rom() { micro -readonly true "$@"; } # Right STRIP ignores trailing spaces, as well as trailing carriage returns rstrip() { awk 1 "$@" | sed -E 's- *\r?$--'; } # Right TRIM ignores trailing spaces, as well as trailing carriage returns rtrim() { awk 1 "$@" | sed -E 's- *\r?$--'; } # show a RULER-like width-measuring line ruler() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" " " | sed -E \ 's- {5}-···· -g; s/(···· ){2}/····╵····│/g; s- -·-g; s-·{5}-····╵-' } # RUN a command, after showing/anNOUNCE-ing it runnounce() { printf "\e[7m%s\e[0m\n" "$*" && "$@"; } # run `sed` in extended mode, enabling its full regex syntax s() { sed -E -u "$@"; } # voice-synthesize plain-text; only works on the windows subsystem for linux # say() { # awk 1 "$@" | powershell.exe -noprofile -command ' # Add-Type -AssemblyName System.Speech # $syn = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer # # foreach ($line in $Input) { # $syn.Speak($line) # }' | cat # } # Silent CURL scurl() { curl -s "$@"; } # show a unique-looking SEParator line; useful to run between commands # which output walls of text sep() { [ "${1:-80}" -gt 0 ] && printf "\e[48;5;253m%${1:-80}s\e[0m\n" " " | sed 's- -·-g' } # webSERVE files in a folder as localhost, using the port number given, or # port 8080 by default serve() { # printf "\e[38;5;26mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 python3 -m http.server "${1:-8080}" -d "${2:-.}" } # SET DIFference setdif() { awk ' FNR == 1 { filenum++ } { if (filenum == 1) { lines[++n] = $0 } else { avoid[$0]++ } } END { for (i = 1; i <= n; i++) { l = lines[i] if (avoid[l] == 0) { avoid[l] = 1 print l } } }' "$@" } # SET DIFFerence sorts its 2 inputs, then finds lines not in the 2nd input setdiff() { # comm -23 <(sort "$1") <(sort "$2") # dash doesn't support the process-sub syntax (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) } # SET INtersection, sorts its 2 inputs, then finds common lines setin() { # comm -12 <(sort "$1") <(sort "$2") # dash doesn't support the process-sub syntax (sort "$1" | (sort "$2" | (comm -12 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) } # SET SUBtraction sorts its 2 inputs, then finds lines not in the 2nd input setsub() { # comm -23 <(sort "$1") <(sort "$2") # dash doesn't support the process-sub syntax (sort "$1" | (sort "$2" | (comm -23 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) } # Show Files (and folders), coloring folders and links sf() { ls -al --file-type --color=never --time-style iso "$@" | awk ' /^d/ { printf "\x1b[38;5;33m%s\x1b[0m\n", $0; next } /^l/ { printf "\x1b[38;5;29m%s\x1b[0m\n", $0; next } 1' } # SHOW a command, then RUN it showrun() { printf "\e[7m%s\e[0m\n" "$*" && "$@"; } # start from the line number given, skipping all previous ones sinceline() { tail -n +"${1:-1}" "${2:--}"; } # skip the first n lines, or the 1st line by default skip() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } # skip the first n bytes skipbytes() { tail -c +$(("$1" + 1)) "${2:--}"; } # skip the last n lines, or the last line by default skiplast() { head -n -"${1:-1}" "${2:--}"; } # skip the last n bytes skiplastbytes() { head -c -"$1" "${2:--}"; } # skip the first n lines, or the 1st line by default skiplines() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } # Styled LEAK runs my script `leak` sleak() { leak "$@"; } # Show Latest Podcasts, using my scripts `podfeed` and `si` slp() { local title title="Latest Podcast Episodes as of $(date +'%F %T')" podfeed -title "${title}" "$@" | si } # recursively find all files with fewer bytes than the number given smallfiles() { find "${2:-.}" -size -"$(echo "${1}" | sed 's-_--g')"c; } # emit the first line as is, sorting all lines after that, using the # `sort` command, passing all/any arguments/options to it sortrest() { awk -v sort="sort $*" 'NR == 1 { print; fflush() } NR > 1 { print | sort }' } # ignore leading spaces, trailing spaces, even runs of multiple spaces # in the middle of lines, as well as trailing carriage returns squeeze() { awk 'FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { print; fflush() }' "$@" | sed -E 's-^ +--; s- +- -g; s- *\t *-\t-g; s- *\r?$--' } # Show a command, then Run it sr() { printf "\e[7m%s\e[0m\n" "$*" && "$@"; } # turn lines of Space-Separated Values into lines of Tab-Separated Values ssv2tsv() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?--; s- +-\t-g'; } # change color/style of lines, using my script `tl` style() { local style="$1" [ $# -gt 0 ] && shift tl "${style}(plain(l))" "$@" } # show a random command defined in `clam`, using `wat` from `clam` itself surprise() { wat "$(grep -E '^[a-z]+\(' "$(which clam)" | shuf -n 1 | sed -E 's-\(.*--')" } # show a reverse-sorted tally of all lines read, where ties are sorted # alphabetically tally() { printf "value\ttally\n" awk ' { t[$0]++ } END { for (k in t) printf "%s\t%d\n", k, t[k] } ' "$@" | sort -t "$(printf "\t")" -rnk2 -k1d } # Tab AWK: TSV-specific I/O settings for `awk` tawk() { awk -F "\t" -v OFS="\t" "$@"; } # timestamp all lines, as soon as they come from the standard input timestamp() { ts '%Y-%m-%d %H:%M:%S' | sed -u 's-^-\x1b[48;5;255m\x1b[38;5;24m-; s- -\x1b[0m\t-2' } title() { local title="${1:-no title given}" [ $# -gt 0 ] && shift printf "\x1b[7m%s\x1b[0m\n", "${title}" awk 1 "$@" } # Transform Json, using an interactive Editor for the formula/expression tje() { case "$1" in =|-nil|--nil|-none|--none|-null|--null) [ $# -gt 0 ] && shift tj -nil "$(micro -readonly true -filetype python | leak --inv)" "$@" ;; *) tj "$(micro -readonly true -filetype python | leak --inv)" "$@";; esac } # Transform Json Faster, by using pypy instead of standard python tjf() { pypy3 "$(which tj)" "$@"; } # Transform Json (Node), using an interactive Editor for the expression tjne() { case "$1" in =|-nil|--nil|-none|--none|-null|--null) [ $# -gt 0 ] && shift tjn -nil "$(micro -readonly true -filetype js | leak --inv)" "$@" ;; *) tjn "$(micro -readonly true -filetype js | leak --inv)" "$@";; esac } # Transform Lines, using an interactive Editor for the formula/expression tle() { case "$1" in =|-nil|--nil|-none|--none|-null|--null) [ $# -gt 0 ] && shift tl -nil "$(micro -readonly true -filetype python | leak --inv)" "$@" ;; *) tl "$(micro -readonly true -filetype python | leak --inv)" "$@";; esac } # Transform Lines Faster, by using pypy instead of standard python tlf() { pypy3 "$(which tl)" "$@"; } # Transform Lines (Node), using the Micro editor for the formula/expression tlne() { case "$1" in =|-nil|--nil|-none|--none|-null|--null) [ $# -gt 0 ] && shift tln -nil "$(micro -readonly true -filetype js | leak --inv)" "$@" ;; *) tln "$(micro -readonly true -filetype js | leak --inv)" "$@";; esac } # show current date in a specifc format, which is both people-friendly # and machine/tool/search/automation-friendly today() { date +'%Y-%m-%d %a %b %d'; } # get the first n lines, or 1 by default toline() { head -n "${1:-1}" "${2:--}"; } # lowercase all ASCII symbols tolower() { awk '{ print tolower($0); fflush() }' "$@"; } # show all files directly in the folder given, without looking any deeper topfiles() { local arg for arg in "${@:-.}"; do find "${arg}" -maxdepth 1 -type f done } # show all folders directly in the folder given, without looking any deeper topfolders() { local arg for arg in "${@:-.}"; do find "${arg}" -maxdepth 1 -type d | awk 'NR > 1' done } # uppercase all ASCII symbols toupper() { awk '{ print toupper($0); fflush() }' "$@"; } # ignore leading/trailing spaces, as well as trailing carriage returns trim() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?$--'; } # ignore trailing spaces, as well as trailing carriage returns trimend() { awk 1 "$@" | sed -E 's- *\r?$--'; } # ignore trailing spaces, as well as trailing carriage returns trimends() { awk 1 "$@" | sed -E 's- *\r?$--'; } # ignore trailing spaces, as well as trailing carriage returns trimtrail() { awk 1 "$@" | sed -E 's- *\r?$--'; } # ignore trailing spaces, as well as trailing carriage returns trimtrails() { awk 1 "$@" | sed -E 's- *\r?$--'; } # try running a command, emitting an explicit message to standard-error # if the command given fails try() { "$@" || { printf "\n\e[31mrunning \e[41m\e[97m %s \e[0m\e[31m failed\e[0m\n" \ "$*" >&2 return 255 } } # TimeStamp lines satisfying an AWK condition, ignoring all other lines tsawk() { awk -v line="\x1b[48;5;255m\x1b[38;5;24m%s\x1b[0m\t%s\n" \ -v time="%Y-%m-%d %H:%M:%S" \ "${1:-1} { printf line, strftime(time), \$0; fflush() }" } # run my script `realign` using tab as the only field-separator, # which means the resulting TSV items can have spaces in them tsv2ssv() { realign --tsv "$@"; } # TSV lines DEDUPlicated by the field given, keeping their original order tsvdedup() { local n="${1:-0}" [ $# -gt 0 ] && shift awk -F "\t" "!c[(\$${n} >= 0) ? \$${n} : \$(NF + ${n} - 1)]++" "$@" } # Unique deduplicates lines, keeping them in their original order u() { awk '!c[$0]++' "$@"; } # UNdo (decode) BASE-64-encoding of data unbase64() { base64 -d "$@"; } # convert away from CSV tables, using my script `decsv` uncsv() { decsv "$@"; } # UNcompress GZip-encoded data ungz() { zcat "$@"; } # UNcompress GZIP-encoded data ungzip() { zcat "$@"; } # deduplicate lines, keeping them in their original order unique() { awk '!c[$0]++' "$@"; } # concatenate all named input sources unix-style: all trailing CRLFs become # single LFs, each non-empty input will always end in a LF, so lines from # different sources are accidentally joined; also leading UTF-8 BOMs on the # first line of each input are ignored, as those are useless at best unixify() { awk ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { print; fflush() }' "$@" | sed -E 's-\r$--' } # turn json lines into a proper json array unjsonl() { jq -s -M "${@:-.}"; } # turn lines of Space-Separated Values into lines of Tab-Separated Values unssv() { awk 1 "$@" | sed -E 's-^ +--; s- *\r?--; s- +-\t-g'; } # convert away from TSV tables, using my script `detsv` untsv() { detsv "$@"; } # turn UTF-16 data into UTF-8 unutf16() { iconv -f utf16 -t utf8 "$@"; } # Unify Output by merging stderr into stdout, without any keyboard-dancing uo() { "$@" 2>&1; } # go UP n folders, or go up 1 folder by default up() { if [ "${1:-1}" -le 0 ]; then cd . return $? fi cd "$(printf "%${1:-1}s" " " | sed 's- -../-g')" || return $? } # UPPERcase all ASCII symbols upper() { awk '{ print toupper($0); fflush() }' "$@"; } # UPPERCASE all ASCII symbols uppercase() { awk '{ print toupper($0); fflush() }' "$@"; } # View with `less` v() { less -JMKiCRS "$@"; } # run a command, showing its success/failure right after verdict() { local code "$@" code=$? if [ "${code}" -eq 0 ]; then printf "\n\e[38;5;29m%s \e[48;5;29m\e[97m succeeded \e[0m\n" "$*" >&2 else printf "\n\e[31m%s \e[41m\e[97m failed with error code %d \e[0m\n" \ "$*" "${code}" >&2 fi return "${code}" } # View with Header runs `less` without line numbers, with ANSI styles, with # no line-wrapping, and using the first line as a sticky-header, so it always # shows on top vh() { less --header=1 -JMKiCRS "$@"; } # View Nice Hexadecimals; uses my script `nh` vnh() { nh "$@" | less -JMKiCRS; } # View Nice Json / Very Nice Json; uses my scripts `nj` and `nn` vnj() { nj "$@" | nn | less -JMKiCRS; } # View Nice Table / Very Nice Table; uses my scripts `nt` and `nn` vnt() { awk 1 "$@" | nl -b a -w 1 -v 0 | nt | nn | awk '(NR - 1) % 5 == 1 && NR > 1 { print "" } { print; flush() }' | less -JMKiCRS } # turn UTF-8 into its latin-like subset, where variants of latin letters stay # as given, and where all other symbols become question marks, one question # mark for each code-point byte; the name comes from `vulgarization`, which # refers to the mutation of languages away from latin, during the middle ages vulgarize() { iconv -f utf-8 -t latin-1//translit "$@" | iconv -f latin-1 -t utf-8 } # View Zoom Json, using my scripts `zj`, `nj`, and `nn` vzj() { zj "$@" | nj | nn | less -JMKiCRS; } # What Are These (?) shows what the names given to it are/do wat() { local a local code=0 for a in "$@"; do # printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "$a" printf "\e[48;5;253m%-80s\e[0m\n" "$a" # printf "\e[7m%-80s\e[0m\n" "$a" # resolve 1 alias level if alias "$a" 2> /dev/null > /dev/null; then a="$(alias "$a" | sed "s-.*=--; s-['\"]--g")" fi if echo "$a" | grep -E '[^ ]+ +[^ ]+' > /dev/null; then # resolved aliases with args/spaces in them would otherwise fail echo "$a" elif type "$a" > /dev/null 2> /dev/null; then # dash doesn't support `declare`, and `type` in bash/zsh emits # a redundant first output line, when it's a shell function type "$a" | awk ' NR == 1 && /^[a-z0-9_-]+ is a function$/ { skipped = $0; next } 1 END { if (NR < 2 && skipped) print skipped } ' else printf "\e[31m%s not found\e[0m\n" "$a" code=1 fi done return "${code}" } # Word-Count with Nice numbers runs `wc` and colors results with my script # `nn`, alternating styles to make long numbers easier to read wcn() { wc "$@" | nn; } # Word-Count Plus runs `wc` and enriches its output; uses my scripts `nn` # and `cext` wcp() { wc "$@" | sort -rn | nn | cext | awk '{ printf "%6d %s\n", NR - 1, $0; fflush() }' } # get weather forecasts, almost filling the terminal's current width weather() { finger "${*}~$(($(tput cols) - 2))@graph.no" | sed -E 's/-/@/g; s/^ +@=/ -=/; s/=@ *$/=-/' | grep -v '^\[' } # WHAT are these (?) shows what the names given to it are/do what() { local a local code=0 for a in "$@"; do # printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "$a" printf "\e[48;5;253m%-80s\e[0m\n" "$a" # printf "\e[7m%-80s\e[0m\n" "$a" # resolve 1 alias level if alias "$a" 2> /dev/null > /dev/null; then a="$(alias "$a" | sed "s-.*=--; s-['\"]--g")" fi if echo "$a" | grep -E '[^ ]+ +[^ ]+' > /dev/null; then # resolved aliases with args/spaces in them would otherwise fail echo "$a" elif type "$a" > /dev/null 2> /dev/null; then # dash doesn't support `declare`, and `type` in bash/zsh emits # a redundant first output line, when it's a shell function type "$a" | awk ' NR == 1 && /^[a-z0-9_-]+ is a function$/ { skipped = $0; next } 1 END { if (NR < 2 && skipped) print skipped } ' else printf "\e[31m%s not found\e[0m\n" "$a" code=1 fi done return "${code}" } # recursively find all files with trailing spaces/CRs wheretrails() { rg -c '[ \r]+$' "${@:-.}"; } # recursively find all files with trailing spaces/CRs whichtrails() { rg -c '[ \r]+$' "${@:-.}"; } # What Is This (?) shows what the names given to it are/do wit() { local a local code=0 for a in "$@"; do # printf "\e[48;5;253m\e[38;5;26m%-80s\e[0m\n" "$a" printf "\e[48;5;253m%-80s\e[0m\n" "$a" # printf "\e[7m%-80s\e[0m\n" "$a" # resolve 1 alias level if alias "$a" 2> /dev/null > /dev/null; then a="$(alias "$a" | sed "s-.*=--; s-['\"]--g")" fi if echo "$a" | grep -E '[^ ]+ +[^ ]+' > /dev/null; then # resolved aliases with args/spaces in them would otherwise fail echo "$a" elif type "$a" > /dev/null 2> /dev/null; then # dash doesn't support `declare`, and `type` in bash/zsh emits # a redundant first output line, when it's a shell function type "$a" | awk ' NR == 1 && /^[a-z0-9_-]+ is a function$/ { skipped = $0; next } 1 END { if (NR < 2 && skipped) print skipped } ' else printf "\e[31m%s not found\e[0m\n" "$a" code=1 fi done return "${code}" } # emit each word-like item from each input line on its own line words() { awk '{ for (i = 1; i <= NF; i++) print $i; fflush() }' "$@"; } # Where Trails recursively find all files with trailing spaces/CRs wt() { rg -c '[ \r]+$' "${@:-.}"; } # run `xargs`, using zero/null bytes as the extra-arguments terminator x0() { xargs -0 "$@"; } # run `xargs`, using whole lines as extra arguments xl() { xargs -d '\n' "$@"; } # Youtube Audio Player yap() { local url # some youtube URIs end with extra playlist/tracker parameters url="$(echo "$1" | sed 's-&.*--')" # mpv "$(yt-dlp -f 140 --get-url "${url}" 2> /dev/null)" mpv "$(yt-dlp -x --audio-format aac --get-url "${url}" 2> /dev/null)" } # Youtube Download yd() { yt-dlp "$@"; } # Youtube Download AAC audio ydaac() { yt-dlp -f 140 "$@"; } # Youtube Download MP4 video ydmp4() { yt-dlp -f 22 "$@"; } # show a calendar for the current year, or for the year given year() { # debian linux has a different `cal` app which highlights the day if [ -e "/usr/bin/ncal" ]; then ncal -C -y "$@" else cal -y "$@" fi } # show the current date in the YYYY-MM-DD format ymd() { date +'%Y-%m-%d'; } # Zoom Json Nice, using my scripts `zj` and `nj` # zjn() { zj "$@" | nj; } # Zoom Json Nice, using my scripts `zj`, `nj`, and `nn` zjn() { zj "$@" | nj | nn; }