#!/bin/sh # The MIT License (MIT) # # Copyright © 2020-2025 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. Some of its commands even # seem to work on busybox's shell. case "$1" in -h|--h|-help|--help) # show help message, using the info-comment from this very script awk ' /^case / { exit } /^# +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;2;255;255;135m\e[38;2;0;0;0mDon't run this script directly: instead,\e[0m" printf "\e[48;2;255;255;135m\e[38;2;0;0;0m source it via '. clam' (no quotes).\e[0m\n" # failing during shell-startup may deny shell access, so exit # with a 0 error-code to declare success exit 0 ;; esac ;; esac # if my `playwave` tool isn't available, some sound-playing commands defined # later need a substitute if ! command -v playwave > /dev/null; then playwave() { mpv --really-quiet "${@:--}"; } fi # alias the `batcat` command (if available) to `bat` (if not already taken) if command -v batcat > /dev/null; then if ! command -v bat > /dev/null; then alias bat=batcat fi fi alias 0='bsbs 10' alias 1='bsbs 1' alias 2='bsbs 2' alias 3='bsbs 3' alias 4='bsbs 4' alias 5='bsbs 5' alias 6='bsbs 6' alias 7='bsbs 7' alias 8='bsbs 8' alias 9='bsbs 9' alias c=cat alias h=naman alias l='less -MKNiCRS' alias r='tput reset' # Better Less runs `less`, showing line numbers, among other settings alias bl='less -MKNiCRS' # run the Fuzzy Finder (fzf) in multi-choice mode, with custom keybindings alias ff='fzf -m --bind ctrl-a:select-all,ctrl-space:toggle' # Time Verbosely the command given alias tv='/usr/bin/time -v' # run `xargs`, using zero/null bytes as the extra-arguments terminator alias x0='xargs -0' # Xargs Lines, runs `xargs` using whole lines as extra arguments alias xl=xargsl # find name from the local `apt` database of installable packages aptfind() { local arg local gap=0 local options='-MKiCRS' if [ $# -eq 1 ]; then options='--header=1 -MKiCRS' fi for arg in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 printf "\e[7m%-80s\e[0m\n\n" "${arg}" # despite warnings, the `search` command has been around for years apt search "${arg}" 2> /dev/null | grep -E -A 1 "^[a-z0-9-]*${arg}" | sed -u 's/^--$//' done | less "${options}" } # APT UPdate/grade aptup() { sudo apt update && sudo apt upgrade "$@"; sudo -k; } # emit each argument given as its own line of output args() { awk 'BEGIN { for (i = 1; i < ARGC; i++) print ARGV[i]; exit }' "$@"; } # avoid/ignore lines which match any of the regexes given avoid() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { for (i in re) if ($0 ~ re[i]) { next } } { print; got++ } END { exit(got == 0) } ' "${@:-^$}" } # AWK in BLOCK/paragraph-input mode awkblock() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk -F='' -v RS='' "$@" else awk -F='' -v RS='' "$@" fi } # AWK in Paragraph-input mode alias awkpar=awkblock # AWK in TSV input/output mode awktsv() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk -F "\t" -v OFS="\t" "$@" else awk -F "\t" -v OFS="\t" "$@" fi } # Breathe lines 5: separate groups of 5 lines with empty lines b5() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk 'NR % 5 == 1 && NR != 1 { print "" } 1' "$@" else awk 'NR % 5 == 1 && NR != 1 { print "" } 1' "$@" fi } # show an ansi-styled BANNER-like line banner() { printf "\e[7m%-$(tput cols)s\e[0m\n" "$*"; } # emit a colored bar which can help visually separate different outputs bar() { [ "${1:-80}" -gt 0 ] && printf "\e[48;2;218;218;218m%${1:-80}s\e[0m\n" "" } # Bullets with AWK shows 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 bawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift printf "value\ttally\tbullets\n" awk ' { low = lower = tolower($0) } { tally['"${code}"']++ } 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 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" | playwave } # play a repeating synthetic-bell-like sound lasting the number of seconds # given, or for 1 second by default; uses my tool `waveout` bell() { local f='sin(880 * tau * u) * exp(-10 * u)' waveout "${1:-1}" "${2:-1} * $f" | playwave } # Breathe Header 5: add an empty line after the first one (the header), # then separate groups of 5 lines with empty lines between them bh5() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk '(NR - 1) % 5 == 1 { print "" } 1' "$@" else awk '(NR - 1) % 5 == 1 { print "" } 1' "$@" fi } # recursively find all files with at least the number of bytes given; when # not given a minimum byte-count, the default is 100 binary megabytes bigfiles() { local n n="$(echo "${1:-104857600}" | sed -E 's-_--g; s-\.[0-9]+$--')" [ $# -gt 0 ] && shift local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -type f -size "$n"c -o -size +"$n"c \ -exec realpath {} + done } # Better LESS runs `less`, showing line numbers, among other settings alias bless='less -MKNiCRS' # Breathe Lines 5: separate groups of 5 lines with empty lines alias bl5=b5 # emit a line with a repeating block-like symbol in it blocks() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -█-g'; } # Book-like MANual, lays out `man` docs as pairs of side-by-side pages; uses # my tool `bsbs` alias bman=bookman # BOOK-like MANual, lays out `man` docs as pairs of side-by-side pages; uses # my tool `bsbs` bookman() { local w w="$(tput cols)" w="$((w / 2 - 4))" if [ "$w" -lt 65 ]; then w=65 fi MANWIDTH="$w" man "$@" | bsbs 2 } # split lines using the separator given, turning them into single-item lines breakdown() { local sep="${1:- }" [ $# -gt 0 ] && shift local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} -F "${sep}" '{ for (i = 1; i <= NF; i++) print $i }' "$@" } # play a busy-phone-line sound lasting the number of seconds given, or for 1 # second by default; uses my tool `waveout` busy() { local f='min(1, exp(-90*(u-0.5))) * (sin(480*tau*t) + sin(620*tau*t)) / 2' waveout "${1:-1}" "${2:-1} * $f" | playwave } # CAlculator with Nice numbers runs my tool `ca` and colors results with # my tool `nn`, alternating styles to make long numbers easier to read can() { local arg for arg in "$@"; do [ $# -gt 1 ] && printf "\e[7m%s\e[0m\n" "${arg}" >&2 ca "${arg}" | nn --gray done } # uppercase the first letter on each line, and lowercase all later letters capitalize() { sed -E -u 's-^(.*)-\L\1-; s-^(.)-\u\1-'; } # conCATenate Lines guarantees no lines are ever accidentally joined # across inputs, always emitting a line-feed at the end of every line # alias catl='awk 1' # conCATenate Lines guarantees no lines are ever accidentally joined # across inputs, always emitting a line-feed at the end of every line catl() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk 1 "$@" else awk 1 "$@" fi } # Count with AWK: count the times the AWK expression/condition given is true cawk() { local cond="${1:-1}" [ $# -gt 0 ] && shift awk ' { low = lower = tolower($0) } '"${cond}"' { count++; c = count } END { print count } ' "$@" } # Compile C Optimized alias cco='cc -Wall -O3 -s -march=native -mtune=native -flto' # center-align lines of text, using the current screen width center() { awk -v width="$(tput cols)" ' { gsub(/\r$/, "") lines[NR] = $0 s = $0 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "", s) # ANSI style-changers l = length(s) if (maxlen < l) maxlen = l } END { n = (width - maxlen) / 2 if (n % 1) n = n - (n % 1) fmt = sprintf("%%%ds%%s\n", (n > 0) ? n : 0) for (i = 1; i <= NR; i++) printf fmt, "", lines[i] } ' "$@" } # Color file-EXTensions, or any substring which looks like one cext() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { palette[n++] = "\x1b[38;2;0;95;215m" # blue palette[n++] = "\x1b[38;2;215;95;0m" # orange palette[n++] = "\x1b[38;2;135;95;255m" # purple palette[n++] = "\x1b[38;2;0;175;215m" # cyan palette[n++] = "\x1b[38;2;255;135;255m" # pink palette[n++] = "\x1b[38;2;0;135;95m" # green palette[n++] = "\x1b[38;2;204;0;0m" # red palette[n++] = "\x1b[38;2;168;168;168m" # gray palcount = length(palette) n = 0 } { # ignore cursor-movers and style-changers # gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") rest = $0 while (match(rest, /\.[A-Za-z][A-Za-z0-9_-]*/)) { printf "%s", substr(rest, 1, RSTART - 1) ext = substr(rest, RSTART, RLENGTH) rest = substr(rest, RSTART + RLENGTH) style = ext2style[ext] if (style == "") { style = palette[n % palcount] ext2style[ext] = style n++ } printf "%s%s\x1b[0m", style, ext } print rest } ' "$@" } # Colored Go Test on the folder given; uses my command `jawk` cgt() { go test "${1:-.}" 2>&1 | jawk '/^ok/' '/^[-]* ?FAIL/' '/^\?/'; } # Colored Json Query runs the `jq` app, allowing an optional filepath as the # data source, and even an optional transformation formula cjq() { jq -C "${2:-.}" "${1:--}"; } # clean the screen, after running the command given clean() { local res if [ -p /dev/stdout ]; then "$@" return $? fi tput smcup "$@" res=$? tput rmcup return "${res}" } # Colored Live/Line-buffered RipGrep ensures results show up immediately, # also emitting colors when piped alias clrg='rg --color=always --line-buffered' # CLear Screen alias cls='tput reset' # Compile C Plus Plus Optimized alias cppo='c++ -Wall -O3 -s -march=native -mtune=native -flto' # Colored RipGrep ensures app `rg` emits colors when piped alias crg='rg --color=always --line-buffered' # Compile Rust Optimized cro() { rustc -C lto=true -C codegen-units=1 -C debuginfo=0 -C strip=symbols \ -C opt-level=3 "$@" } # 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 --paging=never "$@" | # make colors readable even on light backgrounds sed -e 's-\x1b\[38;5;70m-\x1b[38;5;28m-g' \ -e 's-\x1b\[38;5;214m-\x1b[38;5;208m-g' \ -e 's-\x1b\[38;5;243m-\x1b[38;5;103m-g' \ -e 's-\x1b\[38;5;238m-\x1b[38;5;245m-g' \ -e 's-\x1b\[38;5;228m-\x1b[48;5;228m-g' | less -MKiCRS } # 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 | # make colors readable even on light backgrounds sed -e 's-\x1b\[38;5;70m-\x1b[38;5;28m-g' \ -e 's-\x1b\[38;5;214m-\x1b[38;5;208m-g' \ -e 's-\x1b\[38;5;243m-\x1b[38;5;103m-g' \ -e 's-\x1b\[38;5;238m-\x1b[38;5;245m-g' \ -e 's-\x1b\[38;5;228m-\x1b[48;5;228m-g' | less -MKiCRS } # CURL Silent spares you the progress bar, but still tells you about errors alias curls='curl --silent --show-error' # listen to streaming DANCE music dance() { printf "streaming \e[7mDance Wave Retro\e[0m\n" mpv --really-quiet https://retro.dancewave.online/retrodance.mp3 } # emit a line with a repeating dash-like symbol in it dashes() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed 's- -—-g'; } # Dim (lines) with AWK dawk() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local cond="${1:-1}" [ $# -gt 0 ] && shift ${command} ' { low = lower = tolower($0) } '"${cond}"' { gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;168;168;168m") printf "\x1b[38;2;168;168;168m%s\x1b[0m\n", $0 next } 1 ' "$@" } # DEcode BASE64-encoded data, or even base64-encoded data-URIs, by ignoring # the leading data-URI declaration, if present debase64() { sed -E 's-^data:.{0,50};base64,--' "${1:--}" | base64 -d; } # remove indentations from lines dedent() { awk ' { if (match($0, /^ +/) && (n == 0 || n > RLENGTH)) n = RLENGTH lines[NR] = $0 } END { if (n == 0) exit for (i = 1; i <= NR; i++) print substr(lines[i], n + 1) } ' "$@" } # DEDUPlicate prevents lines from appearing more than once dedup() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk '!c[$0]++' "$@" else awk '!c[$0]++' "$@" fi } # dictionary-DEFine the word given, using an online service alias def=define # dictionary-define the word given, using an online service define() { local arg local gap=0 local options='-MKiCRS' if [ $# -eq 0 ]; then printf "\e[38;2;204;0;0mdefine: no words given\e[0m\n" >&2 return 1 fi if [ $# -eq 1 ]; then options='--header=1 -MKiCRS' fi for arg in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 printf "%s\n" "${word}" curl --silent --show-error "dict://dict.org/d:${arg}" 2>&1 | grep '^ ' done | less "${options}" } dehtmlify() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' { gsub(/<\/?[^>]+>/, "") gsub(/&/, "&") gsub(/</, "<") gsub(/>/, ">") gsub(/^ +| *\r?$/, "") gsub(/ +/, " ") print } ' "$@" } # turn JSON Lines into a proper json array dejsonl() { jq -s -M "${@:-.}"; } # expand tabs each into up to the number of space given, or 4 by default detab() { expand -t "${1:-4}"; } # ignore trailing spaces, as well as trailing carriage returns detrail() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk '{ gsub(/ *\r?$/, ""); print }' "$@" else awk '{ gsub(/ *\r?$/, ""); print }' "$@" fi } # turn UTF-16 data into UTF-8 alias deutf16='iconv -f utf16 -t utf8' # DICtionary-define the word given locally dic() { local arg local gap=0 local options='-MKiCRS' if [ $# -eq 0 ]; then printf "\e[38;2;204;0;0mdic: no words given\e[0m\n" >&2 return 1 fi if [ $# -eq 1 ]; then options='--header=1 -MKiCRS' fi for arg in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 printf "\e[7m%-80s\e[0m\n" "${arg}" dict "${arg}" 2>&1 | awk ' NR == 1 && /^No definitions found for / { err = 1 } err { printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0; next } 1 ' done | less "${options}" } # DIM (lines) with AWK alias dimawk=dawk # 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 } c = 1 - a / b if (0 <= c && c <= 1) printf "%f\n%f\n%f\n", a / b, b / a, c else printf "%f\n%f\n", a / b, b / a exit }' } # get/fetch data from the filename or URI given; named `dog` because dogs can # `fetch` things for you dog() { if [ $# -gt 1 ]; then printf "\e[31mdogs only have 1 mouth to fetch with\e[0m\n" >&2 return 1 fi if [ -e "$1" ]; then cat "$1" return $? fi case "${1:--}" in -) cat -;; file://*|https://*|http://*) curl --show-error -s "$1";; ftp://*|ftps://*|sftp://*) curl --show-error -s "$1";; dict://*|telnet://*) curl --show-error -s "$1";; data:*) echo "$1" | sed -E 's-^data:.{0,50};base64,--' | base64 -d;; *) curl --show-error -s "https://$1";; esac 2> /dev/null || { printf "\e[31mcan't fetch %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[38;2;78;154;6m%s\e[0m \e[38;2;52;101;164m%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 date/time center-aligned printf "%20s\e[38;2;78;154;6m%s\e[0m \e[38;2;52;101;164m%s\e[0m\n\n" \ "" "$(date +'%a %b %d')" "$(date +%T)" # debian linux has a different `cal` app which highlights the day if [ -e "/usr/bin/ncal" ]; then # fix debian/ncal's weird way to highlight the current day ncal -C -3 | sed -E 's/_\x08(.)/\x1b[7m\1\x1b[0m/g' else cal -3 fi } | less -MKiCRS } # edit plain-text files alias edit=micro # EDit RUN shell commands, using an interactive editor; uses my tool `leak` edrun() { # dash doesn't support the process-sub syntax # . <( micro -readonly true -filetype shell | leak --inv ) micro -readonly true -filetype shell | leak --inv | . /dev/fd/0 } # show all empty files in a folder, digging recursively emptyfiles() { local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -type f -empty -exec realpath {} + done } # show all empty folders in a folder, digging recursively emptyfolders() { local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -type d -empty done } # ENV with 0/null-terminated lines on stdout alias env0='env -0' # ENV Change folder, runs the command given in the folder given (first) alias envc='env -C' # Extended Plain Interactive Grep alias epig='ugrep --color=never -Q -E' # Editor Read-Only alias ero='micro -readonly true' # Expand 4 turns each tab into up to 4 spaces alias expand4='expand -t 4' # convert EURos into CAnadian Dollars, using the latest official exchange # rates from the bank of canada; during weekends, the latest rate may be # from a few days ago; the default amount of euros to convert is 1, when # not given eur2cad() { local site='https://www.bankofcanada.ca/valet/observations/group' local csv_rates="${site}/FX_RATES_DAILY/csv" local url="${csv_rates}?start_date=$(date -d '3 days ago' +'%Y-%m-%d')" curl -s "${url}" | awk -F, -v amount="$(echo "${1:-1}" | sed 's-_--g')" ' /EUR/ { for (i = 1; i <= NF; i++) if($i ~ /EUR/) j = i } END { gsub(/"/, "", $j); if (j != 0) printf "%.2f\n", amount * $j } ' } # fetch/web-request all URIs given, using protcol HTTPS when none is given fetch() { local arg for arg in "$@"; do case "${arg}" in file://*|https://*|http://*|ftp://*|ftps://*|sftp://*) curl --silent --show-error "${arg}";; dict://*|telnet://*) curl --silent --show-error "${arg}";; data:*) echo "${arg}" | sed -E 's-^data:.{0,50};base64,--' | base64 -d;; *) curl --silent --show-error "https://${arg}";; esac done } # Fuzzy Finder (fzf) with Preview in multi-choice mode, with custom keybindings ffp() { if [ -e "/usr/bin/chafa" ]; then fzf -m --bind ctrl-a:select-all,ctrl-space:toggle \ --preview 'chafa {} 2> /dev/null || less -MKNiCRS {}' "$@" else fzf -m --bind ctrl-a:select-all,ctrl-space:toggle \ --preview 'less -MKNiCRS {}' "$@" fi } alias filemime='file --mime-type' # show all files in folders, digging recursively files() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local arg for arg in "${@:-.}"; do if [ -f "${arg}" ]; then printf "%s\n" "$(realpath "${arg}")" continue fi if [ -d "${arg}" ]; then if echo "${arg}" | grep -q '/$'; then stdbuf -oL find "${arg}" -type f -exec realpath {} + else stdbuf -oL find "${arg}/" -type f -exec realpath {} + fi fi done | ${command} '!c[$0]++; END { exit(NR == 0) }' } # recursively find all files with fewer bytes than the number given filesunder() { local n n="$(echo "${1:-4097}" | sed -E 's-_--g; s-\.[0-9]+$--')" [ $# -gt 0 ] && shift local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -type f -size -"$n"c done } # get the first n lines, or 1 by default first() { 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; trailing spaces # are also ignored fixlines() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { gsub(/ *\r?$/, ""); print } ' "$@" } # show all folders in other folders, digging recursively folders() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then continue fi if echo "${arg}" | grep -q '/$'; then stdbuf -oL find "${arg}" -mindepth 1 -type d -exec realpath {} + else stdbuf -oL find "${arg}/" -mindepth 1 -type d -exec realpath {} + fi done | ${command} '!c[$0]++; END { exit(NR == 0) }' } # show total sizes for the all folders given, digging recursively foldersizes() { local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi du "${arg}" | sort -rnk1 done | awk '{ $1 *= 1024; print }' | sed -u 's-^ *--; s- *-\t-1' } # start from the line number given, skipping all previous ones fromline() { tail -n +"${1:-1}" "${2:--}"; } # convert FeeT into meters ft() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 0.3048 * $0 }' } # convert FeeT² (squared) into meters² ft2() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 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 }" } # convert GALlons into liters gal() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 3.785411784 * $0 }' } # convert binary GigaBytes into bytes gb() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.4f\n", 1073741824 * $0 }' | sed 's-\.00*$--' } # Gawk Bignum Print gbp() { gawk --bignum "BEGIN { print $1; exit }"; } # glue/stick together various lines, only emitting a line-feed at the end; an # optional argument is the output-item-separator, which is empty by default glue() { local sep="${1:-}" [ $# -gt 0 ] && shift awk -v sep="${sep}" ' NR > 1 { printf "%s", sep } { gsub(/\r/, ""); printf "%s", $0 } END { if (NR > 0) print "" } ' "$@" } # GO Build Stripped: a common use-case for the go compiler gobs() { go build -ldflags "-s -w" -trimpath "$@"; } # GO DEPendencieS: show all dependencies in a go project godeps() { go list -f '{{ join .Deps "\n" }}' "$@"; } # GO IMPortS: show all imports in a go project goimps() { go list -f '{{ join .Imports "\n" }}' "$@"; } # go to the folder picked using an interactive TUI; uses my tool `bf` goto() { local where where="$(bf "${1:-.}")" if [ $? -ne 0 ]; then return 0 fi where="$(realpath "${where}")" if [ ! -d "${where}" ]; then where="$(dirname "${where}")" fi cd "${where}" || return } # GRAY AWK styles lines satisfying an AWK condition/expression red alias grayawk=dawk # Style lines using a GRAY-colored BACKground grayback() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' { gsub(/\x1b\[0m/, "\x1b[0m\x1b[48;2;218;218;218m") printf "\x1b[48;2;218;218;218m%s\x1b[0m\n", $0 } ' "$@" } # emit copies of input lines to stdout and to stderr, styling the latter gray grayleak() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' { print gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") printf "\x1b[38;2;168;168;168m%s\x1b[0m\n", $0 > "/dev/stderr" } ' "$@" } # GRoup via AWK groups lines using common results of the AWK expression given grawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' { low = lower = tolower($0) } { k = '"${code}"' if (!(k in groups)) ordkeys[++oklen] = k groups[k][length(groups[k]) + 1] = $0 } END { for (i = 1; i <= oklen; i++) { k = ordkeys[i] n = length(groups[k]) for (j = 1; j <= n; j++) print groups[k][j] } } ' "$@" } # Global extended regex SUBstitute, using the AWK function of the same name: # arguments are used as regex/replacement pairs, in that order gsub() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { for (i = 1; i < ARGC; i++) { args[++n] = ARGV[i] delete ARGV[i] } } { for (i = 1; i <= n; i += 2) gsub(args[i], args[i + 1]) print } ' "$@" } # show Help laid out on 2 side-by-side columns; uses my tool `bsbs` h2() { naman "$@" | bsbs 2; } # Highlight (lines) with AWK hawk() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local cond="${1:-1}" [ $# -gt 0 ] && shift ${command} ' { low = lower = tolower($0) } '"${cond}"' { gsub(/\x1b\[0m/, "\x1b[0m\x1b[7m") printf "\x1b[7m%s\x1b[0m\n", $0 next } 1 ' "$@" } # play a heartbeat-like sound lasting the number of seconds given, or for 1 # second by default; uses my tool `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" | playwave } # 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 | awk '{ gsub(/ +/, ""); printf "%s", $0 } END { printf "\n" }' } # 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 -e 's-\x1b\[38;5;70m-\x1b[38;5;28m-g' \ -e 's-\x1b\[38;5;214m-\x1b[38;5;208m-g' \ -e 's-\x1b\[38;5;243m-\x1b[38;5;103m-g' \ -e 's-\x1b\[38;5;238m-\x1b[38;5;245m-g' \ -e 's-\x1b\[38;5;228m-\x1b[48;5;228m-g' | less -MKiCRS } # convert seconds into a colon-separated Hours-Minutes-Seconds triple hms() { echo "${@:-0}" | sed -E '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 }' } alias hold='less -MKiCRS' # find all hyperlinks inside HREF attributes in the input text href() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { e = "href=\"[^\"]+\"" } { for (s = $0; match(s, e); s = substr(s, RSTART + RLENGTH)) { print substr(s, RSTART + 6, RLENGTH - 7) } } ' "$@" } # find all hyperlinks inside HREF attributes in the input text alias hrefs=href # avoid/ignore lines which case-insensitively match any of the regexes given iavoid() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { if (IGNORECASE == "") { m = "this variant of AWK lacks case-insensitive regex-matching" printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr" exit 125 } IGNORECASE = 1 for (i = 1; i < ARGC; i++) { e[i] = ARGV[i] delete ARGV[i] } } { for (i = 1; i < ARGC; i++) if ($0 ~ e[i]) next print got++ } END { exit(got == 0) } ' "${@:-^\r?$}" } # ignore command in a pipe: this allows quick re-editing of pipes, while # still leaving signs of previously-used steps, as a memo idem() { cat; } # ignore command in a pipe: this allows quick re-editing of pipes, while # still leaving signs of previously-used steps, as a memo ignore() { cat; } # only keep lines which case-insensitively match any of the regexes given imatch() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { if (IGNORECASE == "") { m = "this variant of AWK lacks case-insensitive regex-matching" printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr" exit 125 } IGNORECASE = 1 for (i = 1; i < ARGC; i++) { e[i] = ARGV[i] delete ARGV[i] } } { for (i = 1; i < ARGC; i++) { if ($0 ~ e[i]) { print got++ next } } } END { exit(got == 0) } ' "${@:-[^\r]}" } # start each non-empty line with extra n spaces indent() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { n = ARGV[1] + 0 delete ARGV[1] fmt = sprintf("%%%ds%%s\n", (n > 0) ? n : 0) } /^\r?$/ { print ""; next } { gsub(/\r$/, ""); printf(fmt, "", $0) } ' "$@" } # emit each word-like item from each input line on its own line; when a file # has tabs on its first line, items are split using tabs alone, which allows # items to have spaces in them items() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' FNR == 1 { FS = ($0 ~ /\t/) ? "\t" : " "; $0 = $0 } { gsub(/\r$/, ""); for (i = 1; i <= NF; i++) print $i } ' "$@" } # Judge with AWK colors lines using up to 3 (optional) AWK conditions, namely # `good` (green), `bad` (red), and `meh` (gray) jawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local good="${1:-0}" local bad="${2:-0}" local meh="${3:-0}" [ $# -gt 0 ] && shift [ $# -gt 0 ] && shift [ $# -gt 0 ] && shift ${command} ' BEGIN { # normal good-style is green, colorblind-friendly good-style is blue good_style = ENVIRON["COLORBLIND"] != 0 ? "\x1b[38;2;0;95;215m" : "\x1b[38;2;0;135;95m" good_fmt = good_style "%s\x1b[0m\n" good_reset = "\x1b[0m" good_style } { low = lower = tolower($0) } '"${good}"' { gsub(/\x1b\[0m/, good_reset) printf good_fmt, $0 next } '"${bad}"' { gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;204;0;0m") printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0 next } '"${meh}"' { gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;168;168;168m") printf "\x1b[38;2;168;168;168m%s\x1b[0m\n", $0 next } 1 ' "$@" } # listen to streaming JAZZ music jazz() { printf "streaming \e[7mSmooth Jazz Instrumental\e[0m\n" mpv --quiet https://stream.zeno.fm/00rt0rdm7k8uv } alias jl=jsonl # show a `dad` JOKE from the web, sometimes even a very funny one joke() { curl --silent --show-error https://icanhazdadjoke.com | fold -s | awk '{ gsub(/ *\r?$/, ""); print }' } # shrink/compact JSON using the `jq` app, allowing an optional filepath, and # even an optional transformation formula after that jq0() { jq -c -M "${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 jq2() { jq --indent 2 -M "${2:-.}" "${1:--}"; } # JSON Query Lines turns JSON top-level arrays into multiple individually-JSON # lines using the `jq` app, keeping all other top-level values as single line # JSON outputs, allowing an optional filepath as the data source jql() { jq -c -C ".[]" "${1:--}"; } # jsonkeys() { cat "${1:--}" | zj . .keys | jsonl | awk '!c[$0]++'; } jsonkeys() { tjp '[e.keys() for e in v] if isinstance(v, (list, tuple)) else v.keys()' \ "${1:--}" | jsonl | awk '!c[$0]++' } # 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; } # convert binary KiloBytes into bytes kb() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 1024 * $0 }' | sed 's-\.00*$--' } # find the LAN (local-area network) IP address for this device alias lanip='hostname -I' # play a stereotypical once-a-second laser sound for the number of seconds # given, or for 1 second (once) by default; uses my tool `waveout` laser() { local f='sin(100 * tau * exp(-40 * u))' waveout "${1:-1}" "${2:-1} * $f" | playwave } # get the last n lines, or 1 by default last() { tail -n "${1:-1}" "${2:--}"; } # convert pounds (LB) into kilograms lb() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 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 }" } # run `less`, showing line numbers, among other settings alias least='less -MKNiCRS' # 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 1 runs `less` with line numbers, ANSI styles, without # line-wraps, and using the first line as a sticky-header, so it always # shows on top alias lh1='less --header=1 -MKNiCRS' # Less with Header 2 runs `less` with line numbers, ANSI styles, without # line-wraps, and using the first 2 lines as a sticky-header, so they # always show on top alias lh2='less --header=2 -MKNiCRS' # ensure lines are never accidentally joined across files, by always emitting # a line-feed at the end of each line alias lines=catl # regroup adjacent lines into n-item tab-separated lines lineup() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local n="${1:-0}" [ $# -gt 0 ] && shift if [ "$n" -le 0 ]; then ${command} ' NR > 1 { printf "\t" } { printf "%s", $0 } END { if (NR > 0) print "" } ' "$@" return $? fi ${command} -v n="$n" ' NR % n != 1 && n > 1 { printf "\t" } { printf "%s", $0 } NR % n == 0 { print "" } END { if (NR % n != 0) print "" } ' "$@" } # try to run the command given using line-buffering for its (standard) output alias livelines='stdbuf -oL' # LOAD data from the filename or URI given; uses my tool `get` alias load=get # LOcal SERver webserves files in a folder as localhost, using the port # number given, or port 8080 by default loser() { printf "\e[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 python3 -m http.server "${1:-8080}" -d "${2:-.}" } # LOWercase all ASCII symbols alias low=tolower # LOWERcase all ASCII symbols alias lower=tolower # LiSt MAN pages lsman() { man -k "${1:-.}"; } alias lspages='ls -s --block-size=4096' # Listen To Youtube alias lty=yap # MAKE IN folder alias makein=mif # only keep lines which match any of the regexes given match() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { for (i = 1; i < ARGC; i++) { re[i] = ARGV[i]; delete ARGV[i] } } { for (i in re) if ($0 ~ re[i]) { print; got++; next } } END { exit(got == 0) } ' "${@:-.}" } # convert binary MegaBytes into bytes mb() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 1048576 * $0 }' | sed 's-\.00*$--' } # Multi-Core MAKE runs `make` using all cores mcmake() { make -j "$(nproc)" "$@"; } # Multi-Core MaKe runs `make` using all cores alias mcmk=mcmake # merge stderr into stdout, which is useful for piped commands merrge() { "${@:-cat /dev/null}" 2>&1; } metajq() { # https://github.com/stedolan/jq/issues/243#issuecomment-48470943 jq -r ' [ path(..) | map(if type == "number" then "[]" else tostring end) | join(".") | split(".[]") | join("[]") ] | unique | map("." + .) | .[] ' "$@" } # convert MIles into kilometers mi() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 1.609344 * $0 }' } # convert MIles² (squared) into kilometers² mi2() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 2.5899881103360 * $0 }' } # Make In Folder mif() { local folder folder="${1:-.}" [ $# -gt 0 ] && shift env -C "${folder}" make "$@" } # MINimize DECimalS ignores all trailing decimal zeros in numbers, and even # the decimal dots themselves, when decimals in a number are all zeros # mindecs() { # sed -u -E 's-([0-9]+)\.0+\W-\1-g; s-([0-9]+\.[0-9]*[1-9])0+\W-\1-g' # } # run `less`, showing line numbers, among other settings alias most='less -MKNiCRS' # convert Miles Per Hour into kilometers per hour mph() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 1.609344 * $0 }' } # Number all lines counting from 0, using a tab right after each line number n0() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL nl -b a -w 1 -v 0 "$@" else nl -b a -w 1 -v 0 "$@" fi } # Number all lines counting from 1, using a tab right after each line number n1() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL nl -b a -w 1 -v 1 "$@" else nl -b a -w 1 -v 1 "$@" fi } # NArrow MANual, keeps `man` narrow, even if the window/tab is wide when run naman() { local w w="$(tput cols)" w="$((w / 2 - 4))" if [ "$w" -lt 80 ]; then w=80 fi MANWIDTH="$w" man "$@" } # Not AND sorts its 2 inputs, then finds lines not in common nand() { # comm -3 <(sort "$1") <(sort "$2") # dash doesn't support the process-sub syntax (sort "$1" | (sort "$2" | (comm -3 /dev/fd/3 /dev/fd/4) 4<&0) 3<&0) } # Nice DEFine dictionary-defines the words given, using an online service ndef() { local arg local gap=0 local options='-MKiCRS' if [ $# -eq 0 ]; then printf "\e[38;2;204;0;0mndef: no words given\e[0m\n" >&2 return 1 fi if [ $# -eq 1 ]; then options='--header=1 -MKiCRS' fi for arg in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 printf "\e[7m%-80s\e[0m\n" "${arg}" curl --silent "dict://dict.org/d:${arg}" | awk ' { gsub(/\r$/, "") } /^151 / { printf "\x1b[38;2;52;101;164m%s\x1b[0m\n", $0 next } /^[1-9][0-9]{2} / { printf "\x1b[38;2;128;128;128m%s\x1b[0m\n", $0 next } 1 ' done | less "${options}" } # listen to streaming NEW WAVE music newwave() { printf "streaming \e[7mNew Wave radio\e[0m\n" mpv --quiet https://puma.streemlion.com:2910/stream } # emit nothing to output and/or discard everything from input alias nil=null # Nice Json Query colors JSON data using the `jq` app alias njq=cjq # Nice JSON Lines colors data using the `jq` app, allowing an optional filepath # as the data source njsonl() { jq -c -C ".[]" "${1:--}"; } # Narrow MANual, keeps `man` narrow, even if the window/tab is wide when run alias nman=naman # convert Nautical MIles into kilometers nmi() { echo "${@:-1}" | sed -E 's-_--g; s- +-\n-g' | awk '/./ { printf "%.2f\n", 1.852 * $0 }' } # play a white-noise sound lasting the number of seconds given, or for 1 # second by default; uses my tool `waveout` noise() { waveout "${1:-1}" "${2:-0.05} * random()" | playwave; } # show the current date and time now() { date +'%Y-%m-%d %H:%M:%S'; } # Nice Print Python result; uses my tool `nn` npp() { local arg for arg in "$@"; do python -c "print(${arg})" done | nn --gray } # Nice Size, using my tool `nn` ns() { wc -c "$@" | nn --gray; } # Nice Systemctl Status nss() { systemctl status "$@" 2>&1 | sed 's-\x1b\[[^A-Za-z][A-Za-z]--g' | sed -E \ -e 's-(^[^ ] )([^ ]+\.service)-\1\x1b[7m\2\x1b[0m-' \ -e 's- (enabled)- \x1b[38;2;0;135;95m\x1b[7m\1\x1b[0m-g' \ -e 's- (disabled)- \x1b[38;2;215;95;0m\x1b[7m\1\x1b[0m-g' \ -e 's- (active \(running\))- \x1b[38;2;0;135;95m\x1b[7m\1\x1b[0m-g' \ -e 's- (inactive \(dead\))- \x1b[38;2;204;0;0m\x1b[7m\1\x1b[0m-g' \ -e 's-^(Unit .* could not .*)$-\x1b[38;2;204;0;0m\x1b[7m\1\x1b[0m\n-' \ -e 's-(\[WARN\].*)$-\x1b[38;2;215;95;0m\x1b[7m\1\x1b[0m\n-' \ -e 's-(\[ERR\].*)$-\x1b[38;2;204;0;0m\x1b[7m\1\x1b[0m\n-' | less -MKiCRS } # Nice TimeStamp nts() { ts '%Y-%m-%d %H:%M:%S' | sed -u \ 's-^-\x1b[48;2;218;218;218m\x1b[38;2;0;95;153m-; s- -\x1b[0m\t-2' } # emit nothing to output and/or discard everything from input null() { if [ $# -gt 0 ]; then "$@" > /dev/null else cat < /dev/null fi } # Nice Weather Forecast gets weather forecasts, using ANSI styles and almost # filling the terminal's current width nwf() { local gap=0 local width="$(($(tput cols) - 2))" local place for place in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 printf "\e[7m%-${width}s\e[0m\n" "${place}" printf "%s~%s\r\n\r\n" "${place}" "${width}" | curl --silent --show-error telnet://graph.no:79 | sed -u -E \ -e 's/ *\r?$//' \ -e '/^\[/d' \ -e 's/^ *-= *([^=]+) +=- *$/\1\n/' \ -e 's/-/\x1b[38;2;196;160;0m●\x1b[0m/g' \ -e 's/^( +)\x1b\[38;2;196;160;0m●\x1b\[0m/\1-/g' \ -e 's/\|/\x1b[38;2;52;101;164m█\x1b[0m/g' \ -e 's/#/\x1b[38;2;218;218;218m█\x1b[0m/g' \ -e 's/([=\^][=\^]*)/\x1b[38;2;164;164;164m\1\x1b[0m/g' \ -e 's/\*/○/g' \ -e 's/_/\x1b[48;2;216;200;0m_\x1b[0m/g' \ -e 's/([0-9][0-9]\/[0-9][0-9])/\x1b[7m\1\x1b[0m/g' | awk 1 done | less -MKiCRS } # Print Awk expressions pa() { local arg for arg in "$@"; do awk "BEGIN { print ${arg}; exit }" done } # Print AWK expression for each input line pawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk "{ print ${code} }" "$@" else awk "{ print ${code} }" "$@" fi } # Plain Interactive Grep alias pig='ugrep --color=never -Q -E' # make text plain, by ignoring ANSI terminal styling plain() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk '{ gsub(/\x1b\[[0-9;]*[A-Za-z]/, ""); print }' "$@" else awk '{ gsub(/\x1b\[[0-9;]*[A-Za-z]/, ""); print }' "$@" fi } # play audio/video media play() { mpv "${@:--}"; } # Print Python result pp() { local arg for arg in "$@"; do python -c "print(${arg})" done } # PRecede (input) ECHO, prepends a first line to stdin lines precho() { echo "$@" && cat /dev/stdin; } # LABEL/precede data with an ANSI-styled line prelabel() { printf "\e[7m%-*s\e[0m\n" "$(($(tput cols) - 2))" "$*"; cat -; } # PREcede (input) MEMO, prepends a first highlighted line to stdin lines prememo() { printf "\e[7m%s\e[0m\n" "$*"; cat -; } # 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] } if (ARGC > 1) printf "\n" exit } ' "$@" cat - } # Plain RipGrep alias prg='rg --color=never' # show/list all current processes processes() { local res res="$(ps aux)" echo "${res}" | awk '!/ps aux$/' | sed -E \ -e 's- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1' \ -e 's- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1; s- +-\t-1' } # Quick Compile C Optimized alias qcco='cc -Wall -O3 -s -march=native -mtune=native -flto' # Quick Compile C Plus Plus Optimized alias qcppo='c++ -Wall -O3 -s -march=native -mtune=native -flto' # Quiet MPV qmpv() { mpv --quiet "${@:--}"; } # ignore stderr, without any ugly keyboard-dancing quiet() { "$@" 2> /dev/null; } # Red AWK styles lines satisfying an AWK condition/expression red rawk() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local cond="${1:-1}" [ $# -gt 0 ] && shift ${command} ' { low = lower = tolower($0) } '"${cond}"' { gsub(/\x1b\[0m/, "\x1b[0m\x1b[38;2;204;0;0m") printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0 next } 1 ' "$@" } # keep only lines between the 2 line numbers given, inclusively rangelines() { { [ $# -eq 2 ] || [ $# -eq 3 ]; } && [ "${1}" -le "${2}" ] && { tail -n +"${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$--' | xargs man } # play a ready-phone-line sound lasting the number of seconds given, or for 1 # second by default; uses my tool `waveout` ready() { local f='0.5 * sin(350 * tau * t) + 0.5 * sin(450 * tau * t)' waveout "${1:-1}" "${2:-1} * $f" | playwave } # RED AWK styles lines satisfying an AWK condition/expression red alias redawk=rawk # emit copies of input lines to stdout and to stderr, styling the latter red redleak() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' { print gsub(/\x1b\[[0-9;]*[A-Za-z]/, "") printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0 > "/dev/stderr" } ' "$@" } # 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 command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi local w="${1:-80}" [ $# -gt 0 ] && shift ${command} ' FNR == 1 && NR > 1 { print "" } { gsub(/\r$/, ""); print } ' "$@" | fold -s -w "$w" | sed -u -E 's- *\r?$--' } # REPeat STRing emits a line with a repeating string in it, given both a # string and a number in either order repstr() { awk ' BEGIN { if (ARGV[2] ~ /^[+-]?[0-9]+$/) { symbol = ARGV[1] times = ARGV[2] + 0 } else { symbol = ARGV[2] times = ARGV[1] + 0 } if (times < 0) exit if (symbol == "") symbol = "-" s = sprintf("%*s", times, "") gsub(/ /, symbol, s) print s exit } ' "$@" } # Run In Folder alias rif='env -C' # play a ringtone-style sound lasting the number of seconds given, or for 1 # second by default; uses my tool `waveout` ringtone() { local f='sin(2048 * tau * t) * exp(-50 * (t % 0.1))' waveout "${1:-1}" "${2:-1} * $f" | playwave } # Read-Only Editor alias roe='micro -readonly true' # Read-Only Micro (text editor) alias rom='micro -readonly true' # Read-Only Top alias rot='htop --readonly' # show a RULER-like width-measuring line ruler() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" "" | sed -E \ 's- {10}-····╵····│-g; s- -·-g; s-·····-····╵-' } # RUN IN folder alias runin='env -C' # Summarize via AWK calculates some numeric statistics from an AWK expression sawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift awk ' BEGIN { numeric = ints = pos = zero = neg = 0 inf = "+inf" + 0 min = inf max = -inf sum = 0 mean = 0 prod = 1 } { v = '"${code}"' if (v !~ /^ *(0|[0-9]+|[0-9]*\.[0-9]+) *$/) next v = v + 0 numeric++ ints += v % 1 == 0 if (v > 0) pos++ else if (v < 0) neg++ else if (v == 0) zero++ min = min < v ? min : v max = max > v ? max : v sum += v prod *= v lnSum += v <= 0 ? -inf : log(v) # advance welford`s algorithm d1 = v - mean mean += d1 / numeric d2 = v - mean meanSq += d1 * d2 } END { sum = mean * numeric if (numeric == 0) lnSum = -inf # separate name-value pairs using tabs, and prepare a # pipeable command which ignores all-zero decimals OFS = "\t" print "numeric", numeric if (numeric > 0) { print "min", sprintf("%f", min) print "max", sprintf("%f", max) print "sum", sprintf("%f", sum) print "mean", sprintf("%f", mean) print "geomean", (zero == 0 && neg == 0) ? sprintf("%f", exp(lnSum / numeric)) : "" print "sd", sprintf("%f", sqrt(meanSq / numeric)) print "product", sprintf("%g", prod) } else { print "min", "" print "max", "" print "sum", "" print "mean", "" print "geomean", "" print "sd", "" print "product", "" } print "integer", ints print "positive", pos print "zero", zero print "negative", neg } ' "$@" | sed -E 's-([0-9]+)\.0+$-\1-g; s-([0-9]+\.[0-9]*[1-9])0+$-\1-g' } # voice-synthesize plain-text; only works on the windows subsystem for linux say() { stdbuf -oL 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 } # SystemCTL; `sysctl` is already taken for a separate/unrelated app sctl() { systemctl "$@" 2>&1 | less -MKiCRS --header=1; } # Silent CURL spares you the progress bar, but still tells you about errors alias scurl='curl --silent --show-error' # show a unique-looking SEParator line; useful to run between commands # which output walls of text sep() { [ "${1:-80}" -gt 0 ] && printf "\e[48;2;218;218;218m%${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[7mserving files in %s\e[0m\n" "${2:-$(pwd)}" >&2 python3 -m http.server "${1:-8080}" -d "${2:-.}" } # 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 a command, then RUN it showrun() { { printf "\e[7m%s\e[0m\n" "$*"; "$@"; } | less -MKiCRS --header=1; } # skip the first n lines, or the 1st line by default skip() { tail -n +$(("${1:-1}" + 1)) "${2:--}"; } # skip the last n lines, or the last line by default skiplast() { head -n -"${1:-1}" "${2:--}"; } # SLOW/delay lines from the standard-input, waiting the number of seconds # given for each line, or waiting 1 second by default slow() { local seconds="${1:-1}" ( IFS="$(printf "\n")" while read -r line; do sleep "${seconds}" printf "%s\n" "${line}" done ) } # Show Latest Podcasts, using my tools `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() { local n n="$(echo "${1:-4097}" | sed -E 's-_--g; s-\.[0-9]+$--')" [ $# -gt 0 ] && shift local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -type f -size -"$n"c -exec realpath {} + done } # Stdbuf Output Line-buffered alias sol='stdbuf -oL' # 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 $*" ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { gsub(/\r$/, "") } NR == 1 { print; fflush() } NR >= 2 { print | sort } ' } # SORt Tab-Separated Values: emit the first line as is, sorting all lines after # that, using the `sort` command in TSV (tab-separated values) mode, passing # all/any arguments/options to it sortsv() { awk -v sort="sort -t \"$(printf '\t')\" $*" ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { gsub(/\r$/, "") } NR == 1 { print; fflush() } NR >= 2 { print | sort } ' } # emit a line with the number of spaces given in it spaces() { [ "${1:-80}" -gt 0 ] && printf "%${1:-80}s\n" ""; } # ignore leading spaces, trailing spaces, even runs of multiple spaces # in the middle of lines, as well as trailing carriage returns squeeze() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { gsub(/^ +| *\r?$/, "") gsub(/ *\t */, "\t") gsub(/ +/, " ") print } ' "$@" } # SQUeeze horizontal spaces and STOMP vertical gaps squomp() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } /^\r?$/ { empty = 1; next } empty { if (n > 0) print ""; empty = 0 } { gsub(/^ +| *\r?$/, "") gsub(/ *\t */, "\t") gsub(/ +/, " ") print; n++ } ' "$@" } # STOMP vertical gaps, turning runs of empty lines into single empty lines stomp() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' /^\r?$/ { empty = 1; next } empty { if (n > 0) print ""; empty = 0 } { print; n++ } ' "$@" } substr() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi if [ $# -lt 2 ]; then printf "missing 1-based start index, and substring length\n" >&2 exit 1 fi ${command} '{ print substr($0, '"$1"', '"$2"') }' } # turn SUDo privileges OFF right away: arguments also cause `sudo` to run with # what's given, before relinquishing existing privileges sudoff() { local code=0 if [ $# -gt 0 ]; then sudo "$@" code=$? fi sudo -k return "${code}" } # add a final sums row after all input lines sums() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' { gsub(/\r$/, "") } NR == 1 { FS = ($0 ~ /\t/) ? "\t" : " "; $0 = $0 } { if (n < NF) n = NF for (i = 1; i <= NF; i++) sums[i] += $i print } END { for (i = 1; i <= n; i++) { if (i > 1) printf(FS) printf("%s", sums[i]) } if (n > 0) printf "\n" } ' "$@" } # show a reverse-sorted tally of all lines read, where ties are sorted # alphabetically # tally() { # awk -v sortcmd="sort -t \"$(printf '\t')\" -rnk2 -k1d" ' # # reassure users by instantly showing the header # BEGIN { print "value\ttally"; fflush() } # { gsub(/\r$/, ""); t[$0]++ } # END { for (k in t) { printf("%s\t%d\n", k, t[k]) | sortcmd } } # ' "$@" # } # Tally (lines) with AWK tawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift awk -v sortcmd="sort -t '\t' -rnk1" ' BEGIN { print "tally\tvalue"; fflush() } { v = '"${code}"' if (!tally[v]++) ordkeys[++oklen] = v } END { for (i = 1; i <= oklen; i++) { k = ordkeys[i] printf "%d\t%s\n", tally[k], k | sortcmd } } ' "$@" } # Titled conCATenate Lines highlights each filename, before emitting its lines tcatl() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' FNR == 1 { printf "\x1b[7m%s\x1b[0m\n", FILENAME } 1 ' "$@" } # Simulate the cadence of old-fashioned TELETYPE machines teletype() { awk ' { gsub(/\r$/, "") n = length($0) for (i = 1; i <= n; i++) { if (code = system("sleep 0.015")) exit code printf "%s", substr($0, i, 1); fflush() } if (code = system("sleep 0.75")) exit code printf "\n"; fflush() } # END { if (NR > 0 && code != 0) printf "\n" } ' "$@" } # lookup examples of command-line tools from the `tldr pages` tldr() { local arg local gap=0 local options='-MKiCRS' local base='https://raw.githubusercontent.com/tldr-pages/tldr/main/pages' if [ $# -eq 0 ]; then printf "\e[38;2;204;0;0mtldr: no names given\e[0m\n" >&2 return 1 fi if [ $# -eq 1 ]; then options='--header=1 -MKiCRS' fi for arg in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 if echo "${arg}" | grep -q -v / ; then arg="common/${arg}" fi printf "\e[7m%-80s\e[0m\n" "${arg}" curl --silent --show-error "${base}/${arg}.md" 2>&1 | awk ' NR == 1 && /^404: / { printf "\x1b[38;2;204;0;0m%s\x1b[0m\n", $0 next } NR <= 2 || /^>/ { printf "\x1b[38;2;164;164;164m%s\x1b[0m\n", $0 next } /^`/ { printf "\x1b[48;2;224;224;224m%s\x1b[0m\n", $0; next } 1 ' done | less "${options}" } # show current date in a specifc format 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() { if [ -p 1 ] || [ -t 1 ]; then stdbuf -oL awk '{ print tolower($0) }' "$@" else awk '{ print tolower($0) }' "$@" fi } # play a tone/sine-wave sound lasting the number of seconds given, or for 1 # second by default: after the optional duration, the next optional arguments # are the volume and the tone-frequency; uses my tool `waveout` tone() { waveout "${1:-1}" "${2:-1} * sin(${3:-440} * tau * t)" | playwave; } # get the processes currently using the most cpu topcpu() { local n="${1:-10}" [ "$n" -gt 0 ] && ps aux | awk ' NR == 1 { print; fflush() } NR > 1 { print | "sort -rnk3" } ' | head -n "$(("$n" + 1))" } # show all files directly in the folder given, without looking any deeper topfiles() { local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -maxdepth 1 -type f -exec realpath {} + done } # show all folders directly in the folder given, without looking any deeper topfolders() { local arg for arg in "${@:-.}"; do if [ ! -d "${arg}" ]; then printf "\e[38;2;204;0;0mno folder named %s\e[0m\n" "${arg}" >&2 return 1 fi stdbuf -oL find "${arg}" -mindepth 1 -maxdepth 1 -type d \ -exec realpath {} + done } # get the processes currently using the most memory topmemory() { local n="${1:-10}" [ "$n" -gt 0 ] && ps aux | awk ' NR == 1 { print; fflush() } NR > 1 { print | "sort -rnk6" } ' | head -n "$(("$n" + 1))" } # transpose (switch) rows and columns from tables transpose() { awk ' { gsub(/\r$/, "") } FNR == 1 { FS = ($0 ~ /\t/) ? "\t" : " "; $0 = $0 } { for (i = 1; i <= NF; i++) lines[i][NR] = $i if (maxitems < NF) maxitems = NF } END { for (j = 1; j <= maxitems; j++) { for (i = 1; i <= NR; i++) { if (i > 1) printf "\t" printf "%s", lines[j][i] } printf "\n" } } ' "$@" } # try running a command, emitting an explicit message to standard-error # if the command given fails try() { local code "$@" code=$? if [ "${code}" -ne 0 ]; then printf "\n\e[38;2;204;0;0m%s \e[48;2;204;0;0m\e[38;2;255;255;255m failed with error code %d \e[0m\n" "$*" "${code}" fi >&2 return "${code}" } # Unique via AWK, avoids lines duplicating the expression given uawk() { local code="${1:-\$0}" [ $# -gt 0 ] && shift local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { for (i = 1; i < ARGC; i++) if (f[ARGV[i]]++) delete ARGV[i] } !c['"${code}"']++ ' "$@" } # Underline Every 5 lines: make groups of 5 lines stand out by underlining # the last line of each such group ue5() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' NR % 5 == 0 && NR != 1 { gsub(/\x1b\[0m/, "\x1b[0m\x1b[4m") printf("\x1b[4m%s\x1b[0m\n", $0) next } 1 ' "$@" } # deduplicate lines, keeping them in their original order # alias unique=dedup # skip the first/leading n bytes unleaded() { tail -c +$(("$1" + 1)) "${2:--}"; } # go UP n folders, or go up 1 folder by default up() { if [ "${1:-1}" -le 0 ]; then cd . else cd "$(printf "%${1:-1}s" "" | sed 's- -../-g')" || return $? fi } # convert United States Dollars into CAnadian Dollars, using the latest # official exchange rates from the bank of canada; during weekends, the # latest rate may be from a few days ago; the default amount of usd to # convert is 1, when not given usd2cad() { local site='https://www.bankofcanada.ca/valet/observations/group' local csv_rates="${site}/FX_RATES_DAILY/csv" local url="${csv_rates}?start_date=$(date -d '3 days ago' +'%Y-%m-%d')" curl -s "${url}" | awk -F, -v amount="$(echo "${1:-1}" | sed 's-_--g')" ' /USD/ { for (i = 1; i <= NF; i++) if($i ~ /USD/) j = i } END { gsub(/"/, "", $j); if (j != 0) printf "%.2f\n", amount * $j } ' } # View with `less` # alias v='less -MKiCRS' # run a command, showing its success/failure right after verdict() { local code "$@" code=$? if [ "${code}" -eq 0 ]; then if [ "${COLORBLIND}" = 1 ]; then # color-blind-friendly version using a blue `success` message printf "\n\e[38;2;0;95;215m%s \e[48;2;0;95;215m\e[38;2;255;255;255m succeeded \e[0m\n" "$*" else printf "\n\e[38;2;0;135;95m%s \e[48;2;0;135;95m\e[38;2;255;255;255m succeeded \e[0m\n" "$*" fi else printf "\n\e[38;2;204;0;0m%s \e[48;2;204;0;0m\e[38;2;255;255;255m failed with error code %d \e[0m\n" "$*" "${code}" fi >&2 return "${code}" } # run `cppcheck` with even stricter options alias vetc='cppcheck --enable=portability --enable=style --check-level=exhaustive' # run `cppcheck` with even stricter options, also checking for c89 compliance alias vetc89='cppcheck --enable=portability --enable=style --check-level=exhaustive --std=c89' # run `cppcheck` with even stricter options alias vetcpp='cppcheck --enable=portability --enable=style --check-level=exhaustive' alias vetsh=vetshell # check shell scripts for common gotchas, avoiding complaints about using # the `local` keyword, which is widely supported in practice alias vetshell='shellcheck -e 3043' # View with Header 1 runs `less` without line numbers, ANSI styles, without # line-wraps, and using the first line as a sticky-header, so it always shows # on top alias vh1='less --header=1 -MKiCRS' # View with Header 2 runs `less` without line numbers, ANSI styles, without # line-wraps, and using the first 2 lines as sticky-headers, so they always # show on top alias vh2='less --header=2 -MKiCRS' # View Nice Table / Very Nice Table; uses my tools `nt` and `nn` vnt() { nl -b a -w 1 -v 0 "$@" | nt | nn --gray | awk '(NR - 1) % 5 == 1 { print "" } 1' | less -MKiCRS --header=1 } # run a command using an empty environment alias void='env -i' # View Text, turning documents into plain-text if needed vt() { local arg local gap=0 local options='-MKiCRS' if [ $# -eq 1 ]; then options='--header=1 -MKiCRS' fi if [ $# -eq 0 ]; then pandoc -s -t plain - 2>&1 | less -MKiCRS else for arg in "$@"; do [ "${gap}" -eq 1 ] && printf "\n" gap=1 printf "\e[7m%-80s\e[0m\n" "${arg}" pandoc -s -t plain "${arg}" 2>&1 | awk 1 done | less "${options}" fi } # turn plain-text from latin-1 into UTF-8; the name is from `vulgarization`, # which is the mutation of languages away from latin during the middle ages alias vulgarize='iconv -f latin-1 -t utf-8' # What Are These (?) shows what the names given to it are/do wat() { local arg local gap=0 if [ $# -eq 0 ]; then echo "$0" return 0 fi for arg in "$@"; do [ "${gap}" -gt 0 ] && printf "\n" gap=1 printf "\e[7m%-80s\e[0m\n" "${arg}" while alias "${arg}" > /dev/null 2> /dev/null; do arg="$(alias "${arg}" | sed -E "s-^[^=]+=['\"](.+)['\"]\$-\\1-")" done if echo "${arg}" | grep -q ' '; then printf "%s\n" "${arg}" continue fi if type "${arg}" > /dev/null 2> /dev/null; then type "${arg}" | awk 'NR == 1 && / is a function$/ { next } 1' continue fi if which "${arg}" > /dev/null 2> /dev/null; then which "${arg}" continue fi if whence -f "${arg}" > /dev/null 2> /dev/null; then whence -f "${arg}" continue fi printf "\e[38;2;204;0;0m%s not found\e[0m\n" "${arg}" done | less -MKiCRS } # Word-Count TSV, runs the `wc` app using all stats, emitting tab-separated # lines instead wctsv() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi printf "file\tbytes\tlines\tcharacters\twords\tlongest\n" stdbuf -oL wc -cmlLw "${@:--}" | sed -E -u \ 's-^ *([^ ]*) *([^ ]*) *([^ ]*) *([^ ]*) *([^ ]*) *([^\r]*)$-\6\t\4\t\1\t\3\t\2\t\5-' | ${command} ' NR > 1 { print prev } { prev = $0 } END { if (NR == 1 || !/^total\t/) print } ' } # find all WEB/hyperLINKS (https:// and http://) in the input text weblinks() { local command='awk' if [ -p 1 ] || [ -t 1 ]; then command='stdbuf -oL awk' fi ${command} ' BEGIN { e = "https?://[A-Za-z0-9+_.:%-]+(/[A-Za-z0-9+_.%/,#?&=-]*)*" } { # match all links in the current line for (s = $0; match(s, e); s = substr(s, RSTART + RLENGTH)) { print substr(s, RSTART, RLENGTH) } } ' "$@" } # recursively find all files with trailing spaces/CRs alias wheretrails=whichtrails # recursively find all files with trailing spaces/CRs whichtrails() { rg -c --line-buffered '[ \r]+$' "${@:-.}"; } winpath() { local arg for arg in "$@"; do realpath "${arg}" | sed -E 's-^/mnt/(.)-\1:-' done } # Windows Subsystem for Linux OFF alias wsloff='wsl.exe --shutdown' # XARGS Lines, runs `xargs` using whole lines as extra arguments xargsl() { local buffering='' if [ -p 1 ] || [ -t 1 ]; then buffering='stdbuf -oL' fi ${buffering} awk -v ORS='\000' ' FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } { gsub(/\r$/, ""); print } ' | ${buffering} xargs -0 "$@" } # Youtube Audio Player yap() { # some youtube URIs end with extra playlist/tracker parameters local url="$(echo "$1" | sed 's-&.*--')" mpv "$(yt-dlp -x --audio-format best --get-url "${url}" 2> /dev/null)" } # show a calendar for the current YEAR, or for the year given year() { { # show the current date/time center-aligned printf \ "%21s\e[38;2;78;154;6m%s\e[0m \e[38;2;52;101;164m%s\e[0m\n\n" \ "" "$(date +'%a %b %d %Y')" "$(date +'%H:%M')" # debian linux has a different `cal` app which highlights the day if [ -e "/usr/bin/ncal" ]; then # fix debian/ncal's weird way to highlight the current day ncal -C -y "$@" | sed -E 's/_\x08(.)/\x1b[7m\1\x1b[0m/g' else cal -y "$@" fi } | less -MKiCRS } # show the current date in the YYYY-MM-DD format ymd() { date +'%Y-%m-%d'; }