#!/bin/bash # Copyright (C) 2019 Ian Kelling # SPDX-License-Identifier: AGPL-3.0-or-later # this gets sourced. shebang is just for file mode detection # Use source ~/.bashrc instead of doing bash -l when running a script # so this can set extdebug and avoid the bash debugger. if [[ -s /a/bin/bash-bear-trap/bash-bear ]]; then # shellcheck source=/a/bin/bash-bear-trap/bash-bear source /a/bin/bash-bear-trap/bash-bear # wtf, shellcheck doesn't allow disabling warnings in elifs else # bleh shellcheck can't handle disabling in an elif, so nesting this if. # shellcheck disable=SC2154 # set in .bashrc if [[ -s $bashrc_dir/bash-bear ]]; then # shellcheck source=/a/bin/bash-bear-trap/bash-bear source $bashrc_dir/bash-bear fi fi # In t8, it runs clear_console for login shells by default. I don't want # my console cleared. And linux ttys get cleared without this. if shopt login_shell >/dev/null && [[ -e ~/.bash_logout ]]; then rm ~/.bash_logout fi # if [[ -s /usr/share/bash-completion/completions/git ]]; then # source /usr/share/bash-completion/completions/git # fi # if [[ -s /usr/share/bash-completion/completions/gitk ]]; then # source /usr/share/bash-completion/completions/gitk # fi # for testing error catching: # t2() { # echo t2 # grep sdf sdfd # echo wtf # } # t1() { # echo t1 # t2 a b c # } # * settings CDPATH=. # remove all aliases. aliases provided by the system tend to get in the way, # for example, error happens if I try to define a function the same name as an alias unalias -a # remove gnome keyring warning messages # there is probably a more proper way, but I didnt find any easily on google # now using xfce+xmonad instead of vanilla xmonad, so disabling this #unset GNOME_KEYRING_CONTROL # use extra globing features. shopt -s extglob # include .files when globbing, but ignore files name . and .. # setting this also sets dotglob. export GLOBIGNORE="*/.:*/.." # Useful info. see man bash. PS4='$LINENO+ ' # broken with bash_completion package. Saw a bug for this once. dont anymore. # still broken in wheezy # still buggered in latest stable from the web, version 2.1 # perhaps its fixed in newer git version, which fails to make for me # this note is from 6-2014. # still broken in flidas. #shopt -s nullglob # make tab on an empty line do nothing shopt -s no_empty_cmd_completion # fix spelling errors for cd, only in interactive shell shopt -s cdspell # append history instead of overwritting it shopt -s histappend # for compatibility, per gentoo/debian bashrc shopt -s checkwinsize # attempt to save multiline single commands as single history entries. shopt -s cmdhist # enable ** shopt -s globstar # inside emacs fixes if [[ $LC_INSIDE_EMACS ]]; then # EMACS is used by bash on startup, but we dont need it anymore. # plus I hit a bug in a makefile which inherited it unset EMACS export LC_INSIDE_EMACS export PAGER=cat export MANPAGER=cat # scp completion does not work, but this doesnt fix it. todo, figure this out #complete -r scp &> /dev/null # todo, remote file completion fails, figure out how to turn it off export NODE_DISABLE_COLORS=1 # This gets rid of ugly terminal escape chars in node repl # sometime, Id like to have completion working in emacs shell for node # the offending chars can be found in lib/readline.js, # things that do like: # stream.write('\x1b[' + (x + 1) + 'G'); # We can remove them and keep readline, for example by doing this # to start a repl: #!/usr/bin/env nodejs # var readline = require('readline'); # readline.cursorTo = function(a,b,c) {}; # readline.clearScreenDown = function(a) {}; # const repl = require('repl'); # var replServer = repl.start(''); # # no prompt, or else readline complete seems to be confused, based # on our column being different? node probably needs to send # different kind of escape sequence that is not ugly. Anyways, # completion doesnt work yet even with the ugly prompt, so whatever # export NODE_NO_READLINE=1 fi export SSH_CONFIG_FILE_OVERRIDE=/root/.ssh/confighome # emacs has a different default search path than the info command. This # adds the info defaults to emacs. This is commented because after # various upgrades this is no longer a problem: for the directories that # exist on my system, emacs already includes the ones that info # searches. # # but not the reverse, because I dun # care much about the cli. The search path is only on the cli if you run # "info xxx", or in emacs if you run '(info xxx)', so not that # important and i don't bother fixing it. # # info info says this path is what was compiled, and its not documented # # anywhere. Through source grepping, i found it in files.h of the info # # source in trisquel flidas. # # # # Trailing : means for emacs to add its own stuff on to the end. # # # # A problem with this is that directories which are not readable breaks info. And of course, this hard coding is not nice. # # I removed PATH from the start, because I've never seen an info file in PATH. And removed ".", because I can just specify the full file name in that case. # # # # https://raw.githubusercontent.com/debian-tex/texinfo/master/info/filesys.h # # # # note: to split up the var like this, do: # # IFS=:; printf '%s\n' $INFOPATH # dirs=( # /usr/local/info # /usr/info # /usr/local/lib/info # /usr/lib/info # /usr/local/gnu/info # /usr/local/gnu/lib/info # /usr/gnu/info # /usr/gnu/lib/info # /opt/gnu/info # /usr/share/info # /usr/share/lib/info # /usr/local/share/info # /usr/local/share/lib/info # /usr/gnu/lib/emacs/info # /usr/local/gnu/lib/emacs/info # /usr/local/lib/emacs/info # /usr/local/emacs/info # ) # for d in ${dirs[@]}; do # if [[ -r $d ]]; then # INFOPATH="$d:$INFOPATH" # fi # done # unset d dirs # note: guix bash config does this automatically. if [[ $INFOPATH != *: ]]; then INFOPATH="$INFOPATH:" fi # info parameter expansion # # info cheat sheet: # H: see keybinds # / search, {, }: next/prev match # ctrl/alt-v scroll forward/backward within this node # l: go to previous node # info-pe() { info bash 'Basic Shell Features' 'Shell Expansions' 'Shell Parameter Expansion' } # for openwrt system that has no stty, this is easier than # guarding every time i use it. if ! type -p stty >/dev/null; then stty() { :; } fi use_color=false if [[ $- == *i* ]]; then # for readline-complete.el if [[ $LC_INSIDE_EMACS ]]; then # all for readline-complete.el stty echo bind 'set horizontal-scroll-mode on' bind 'set print-completions-horizontally on' bind '"\C-i": self-insert' else if [[ $TERM != dumb ]] && test -t 1; then use_color=true fi if [[ $KONSOLE_PROFILE_NAME ]]; then TERM=xterm-256color fi if [[ $TERM == alacritty && ! -e /usr/share/terminfo/a/alacritty ]]; then # todo: we should try installing the alacritty terminfo if it is not found # https://github.com/alacritty/alacritty/issues/2838 TERM=xterm-256color fi # copying from the alacritty example above, if [[ $TERM == xterm-kitty ]]; then if [[ ! -e /usr/share/terminfo/x/xterm-kitty ]]; then TERM=xterm-256color else if [[ -e /a/opt/kitty/shell-integration/bash/kitty.bash ]]; then KITTY_SHELL_INTEGRATION=t source /a/opt/kitty/shell-integration/bash/kitty.bash fi fi fi # todo: not sure this works in sakura #stty werase undef #bind "\C-w": kill-region # sakura == xterm-256color # konsole == xterm if [[ $TERM != xterm-kitty && $TERM == xterm* ]]; then # control + arrow keys. for other terminals, see http://unix.stackexchange.com/questions/10806/how-to-change-previous-next-word-shortcut-in-bash bind '"\e[1;5C": shell-forward-word' 2>/dev/null bind '"\e[1;5D": shell-backward-word' 2>/dev/null else # make ctrl-backspace work. for konsole, i fixed it through # /home/iank/.local/share/konsole/default.keytab stty werase ^h bind '"\eOc": shell-forward-word' bind '"\eOd": shell-backward-word' fi # i cant remember why i did this, probably to free up some keys to bind # to other things in bash. # other than C-c and C-z, the rest defined by stty -a are, at least in # gnome-terminal, overridden by bash, or disabled by the system stty lnext undef stop undef start undef fi fi case $TERM in # fixup broken backspace in chroots xterm-kitty|alacritty) chroot() { TERM=xterm-256color command chroot "$@" } ;; esac export BC_LINE_LENGTH=0 # ansible option export PROFILE_TASKS_TASK_OUTPUT_LIMIT=100 # note, if I use a machine I dont want files readable by all users, set # umask 077 # If fewer than 4 digits are entered, leading zeros are assumed # i for insensitive. the rest from # X means dont remove the current screenworth of output upon exit # R means to show colors n things # a useful flag is -F aka --quit-if-one-screen export LESS=RXij12 export SYSTEMD_LESS=$LESS export NNN_COLORS=2136 export SL_FILES_DIR=/b/ds/sl/.iank export SL_INFO_DIR=/p/sshinfo ### begin pyenv ### # this is adapted from things printed to term after install # pyenv. commented for now since I'm not actually using pyenv. # export PYENV_ROOT="$HOME/.pyenv" # command -v pyenv &>/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" # command -v pyenv &>/dev/null && eval "$(pyenv init -)" # output showed this example for pyenv-virtualenv, which i have no idea # what it is, but leaving it as a comment in case I end up doing python # dev. #eval "$(pyenv virtualenv-init -)" ### end begin pyenv ### # * include files if [[ -s $bashrc_dir/path-add-function ]]; then source $bashrc_dir/path-add-function if [[ $SSH_CLIENT ]]; then if grep -qF /home/iank/.iank/e/e /etc/exports &>/dev/null; then export EMACSDIR=/home/iank/.iank/e/e fi path-add $bashrc_dir fi fi # if someone exported $SOE (stop on error), catch errors. # # Note, on debian this results in the following warning when in ssh, # hich I haven't figured out how to fix. It doesn't happen if we source # after the shell has started # # bash: /usr/share/bashdb/bashdb-main.inc: No such file or directory # bash: warning: cannot start debugger; debugging mode disabled if [[ $SOE ]]; then if [[ -e /a/bin/bash-bear-trap/bash-bear ]]; then source /a/bin/bash-bear-trap/bash-bear fi fi mysrc() { local path dir file path=$1 dir=${path%/*} file=${path##*/} if [[ -s $path ]]; then # shellcheck disable=SC1090 # this is dynamic, shellcheck can't follow it. source $path elif [[ -s $bashrc_dir/$file ]]; then # shellcheck disable=SC1090 # this is dynamic, shellcheck can't follow it. source $bashrc_dir/$file fi } mysrc /a/bin/small-misc-bash/ll-function mysrc /a/bin/distro-functions/src/package-manager-abstractions # things to remember: # ALT-C - cd into the selected directory # CTRL-T - Paste the selected file path into the command line # # good guide to some of its basic features is the readme file # https://github.com/junegunn/fzf # if [[ -s /usr/share/doc/fzf/examples/key-bindings.bash ]]; then # source /usr/share/doc/fzf/examples/key-bindings.bash # fi # * functions # temporary functions y() { m "${@//spring/fall}" } h() { e "${@//spring/fall}" } ### begin FSF section ### # Comments before functions are meant to be good useful # documentation. If they fail at that, please improve them or send Ian a # note. ## copy bash completion # # It copies how the bash completion works from one command to other # commands. Generally just use within a .bashrc. # # Usage: ORIGINAL_COMMAND TARGET_COMMAND... # ccomp() { local c src src=$1 shift if ! c=$(complete -p $src 2>/dev/null); then _completion_loader $src &>/dev/null ||: c=$(complete -p $src 2>/dev/null) || return 0 fi # remove $src( .*|$) c=${c% "$src"} c=${c%% "$src" *} eval $c $* } ## BEGIN functions to change directory better than cd ## # # The functions: # # c: acts like cd, but stores directory history: you could alias to cd if you wanted. # b: go back # f: go forward # cl: list recent directories and optionally choose one. # # Finer details you may want to skip: # # bl: print the list of back and forward directories. # # We keep 2 stacks of directories, forward and back. Unlike with a web # browser, the forward stack is not erased when going somewhere new. # # Recent directories are stored in ~/.cdirs. # declare -a _dir_forward _dir_back c() { # normally, the top of _dir_back is our current dir. if it isn't, # put it on there, except we don't want to do that when we # just launched a shell if [[ $OLDPWD ]]; then if (( ${#_dir_back[@]} == 0 )) || [[ ${_dir_back[-1]} != "$PWD" ]]; then _dir_back+=("$PWD") fi fi command cd "$@" if (( ${#_dir_back[@]} == 0 )) || [[ ${_dir_back[-1]} != "$PWD" ]]; then _dir_back+=("$PWD") fi echo "$PWD" >> ~/.cdirs } ccomp cd c # back b() { local top_back if (( ${#_dir_back[@]} == 0 )); then echo "nothing left to go back to" >&2 return 0 fi top_back="${_dir_back[-1]}" if [[ $top_back == "$PWD" ]] && (( ${#_dir_back[@]} == 1 )); then echo "already on last back entry" >&2 return 0 fi if [[ $top_back == "$PWD" ]]; then # add to dirf if not already there if (( ${#_dir_forward[@]} == 0 )) || [[ ${_dir_forward[-1]} != "$top_back" ]]; then _dir_forward+=("$top_back") fi unset "_dir_back[-1]" command cd "${_dir_back[-1]}" else if (( ${#_dir_forward[@]} == 0 )) || [[ ${_dir_forward[-1]} != "$PWD" ]]; then _dir_forward+=("$PWD") fi command cd "$top_back" fi # Interesting feature, not sure I want it. # give us a peek at what is next in the list # if (( ${#_dir_back[@]} >= 2 )); then # printf "%s\n" "${_dir_back[-2]}" # fi # # c/b/f Implementation notes: # # The top of the back is $PWD # as long as the last directory change was due to c,b,or cl. # # Example of stack changes: # # a b c (d) ## back # a b (c) # d #back #a (b) #d c #back #(a) #d c b #forward #a (b) #d c # # a b c ## back # a b # (c) ## forward } # forward f() { local top_forward if (( ${#_dir_forward[@]} == 0 )); then echo "no forward dir left" >&2 return 0 fi top_forward="${_dir_forward[-1]}" unset "_dir_forward[-1]" c "$top_forward" # give us a peek at what is next in the list # if (( ${#_dir_forward[@]} )); then # printf "%s\n" "${_dir_forward[-1]}" # fi } # cl = cd list cl() { local i line input start local -A buttondirs alines local -a buttons dirs lines buttons=( {a..z} {2..9} ) if [[ ! -s ~/.cdirs ]]; then echo nothing in ~/.cdirs return 0 fi i=0 mapfile -t lines <~/.cdirs start=$(( ${#lines[@]} - 1 )) # we have ~33 buttons as of this writing, so lets # prune down the history every once in a while. if (( start > 500 )); then tac ~/.cdirs | awk '!seen[$0]++' | head -n 200 | tac | sponge ~/.cdirs || [[ $? == 141 ]] fi for (( j=start; j >= 0; j-- )); do line="${lines[$j]}" if [[ ! $line || ${alines[$line]} || ! -d "$line" || $line == "$PWD" || line == "$HOME" ]]; then continue fi alines[$line]=t buttondirs[${buttons[i]}]="$line" printf "%s %s\n" ${buttons[i]} "$line" # the LINES bit is for when we have a short terminal, just dont print all # the directories. alternative would be to do something like less the list. if (( i == ${#buttons[@]} - 1 )) || { [[ $LINES ]] && (( i == LINES - 3 )); }; then break fi i=$(( i + 1 )) done if (( i == 0 )); then echo "no dirs in ~/.cdirs" return 0 fi read -r -N 1 input if [[ $input != $'\n' ]]; then c "${buttondirs[$input]}" fi } # bl = back list. lists the back and forward directories. i tend to # forget this exists and use cl instead. bl() { local start i j max max=10 start=$(( ${#_dir_back[@]} - 1 )) # cleanup possible repeating of pwd if (( start >= 0 )) && [[ ${_dir_back[$start]} == "$PWD" ]]; then start=$(( start - 1 )) fi j=1 if (( start >= 0 )); then for (( i=start; i >= 0 ; i-- )); do printf "%s %s\n" $j ${_dir_back[i]} j=$(( j + 1 )) if (( j >= max )); then break fi done fi max=10 start=$(( ${#_dir_forward[@]} - 1 )) # cleanup possible repeating of pwd if (( start >= 0 )) && [[ ${_dir_forward[$start]} == "$PWD" ]]; then start=$(( start - 1 )) fi if (( start < 0 )); then return 0 fi echo -- j=1 for (( i=start; i >= 0 ; i-- )); do printf "%s %s\n" $j ${_dir_forward[i]} j=$(( j + 1 )) if (( j >= max )); then break fi done } # like running cl a cla() { local line mapfile -t lines <~/.cdirs start=$(( ${#lines[@]} - 1 )) for (( j=start; j >= 0; j-- )); do line="${lines[$j]}" if [[ ! $line || ! -d "$line" || $line == "$PWD" || line == "$HOME" ]]; then continue fi e "$line" c "$line" break done } ## END functions to change directory better than cd ## # pee do. run args as a command with output copied to syslog. # # Usage: pd [-t TAG] COMMAND... # # -t TAG Override the tag in the syslog. The default is COMMAND with # any path part is removed, eg. for /bin/cat the tag is cat. # # You can view the log via "journalctl -t TAG" pd() { local tag ret ret=0 tag=${1##*/} case $1 in -t) tag="$2"; shift 2 ;; esac echo "PWD=$PWD command: $*" | logger -t $tag "$@" |& pee cat "logger -t $tag" || ret=$? echo "exited with status=$ret" | pee cat "logger -t $tag" # this avoids any err-catch (( ret == 0 )) || return $ret } ccomp time pd # jdo = journal do. Run command as transient systemd service, tailing # its output in the journal until it completes. # # Usage: jdo COMMAND... # # Compared to pd: commands recognize this is a non-interactive shell. # The service is unaffected if our ssh connection dies, no need to run # in screen or tmux. # # Note: The last few lines of any existing entries for a unit by that # name will be output first, and there will be a few second delay at the # start of the command, and a second or so at the end. # # Note: Functions and aliases obviously won't work, we resolve the # command to a file. # # Note: requires running as root. jdo() { local cmd cmd_name jr_pid ret ret=0 cmd="$1" shift if [[ $EUID != 0 ]]; then echo "jdo: error: rerun as root" return 1 fi cmd_name=${cmd##*/} if [[ $cmd != /* ]]; then cmd=$(type -P "$cmd") fi # -q = quiet journalctl -qn2 -f -u "$cmd_name" & # Trial and error of time needed to avoid missing initial lines. # .5 was not reliable. 1 was not reliable. 2 was not reliable sleep 4 jr_pid=$! systemd-run --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$? # The sleep lets the journal output its last line # before the prompt comes up. sleep .5 kill $jr_pid &>/dev/null ||: unset jr_pid fg &>/dev/null ||: # this avoids any err-catch (( ret == 0 )) || return $ret } ccomp time jdo # standard date as used in logs datelog() { date +%Y-%m-%d "$@" } # date in log appropriate format dtl() { date "+%F %T" "$@" } # ts formatted tsf() { command ts "%F %T" "$@" } # ts log. log command to log file. # usage: tsl LOG_PATH_PREFIX COMMAND... # example: tsl /root/command # log file will be like /root/command-2024-02-10.log tsl() { local log_prefix log_path appending ret if (( $# < 2 )); then echo "tsl: error: expected >= 2 arguments, got $#" >&2 return 1 fi log_prefix="$1" if [[ $log_prefix == */* && ! -d ${log_prefix%*/} ]]; then echo "tsl: error: expected directory at ${log_prefix%*/}" >&2 return 1 fi log_path=$log_prefix-$(date +%Y-%m-%d).log appending=false if [[ -s $log_path ]]; then appending=true fi shift printf "%s\n" "CWD: $PWD, log: $log_path, running $*" | ts "%F %T" | tee -a "$log_path" ret=0 "$@" |& ts "%F %T" | tee -a "$log_path" || ret=$? printf "%s\n" "exit code $ret from command: $*" | ts "%F %T" | tee -a "$log_path" if $appending; then printf "%s\n" "note: this log file contains logs before those of previous command" | ts "%F %T" | tee -a "$log_path" fi } disk-info() { local cmds cmd mapfile -t cmds <<'EOF' tail -n +1 /proc/mdstat /etc/mdadm/mdadm.conf /etc/fstab /etc/crypttab lsblk blkid ls -la /dev/disk/by-id EOF for cmd in "${cmds[@]}"; do cat <$path <<'EOF'" cat "$f" echo EOF done } # file cut copy and paste, like the text buffers :) # I havnt tested these. _fbufferinit() { # internal use ! [[ $my_f_tempdir ]] && my_f_tempdir="$(mktemp -d)" rm -rf "${my_f_tempdir:?}"/* } fcp() { # file cp _fbufferinit cp "$@" "$my_f_tempdir"/ } fct() { # file cut _fbufferinit mv "$@" "$my_f_tempdir"/ } fpst() { # file paste [[ $2 ]] && { echo too many arguments; return 1; } target=${1:-.} cp "$my_f_tempdir"/* "$target" } _khfix-common() { local host ip port file key tmp read -r host ip port < <(timeout -s 9 2 ssh -oBatchMode=yes -oControlMaster=no -oControlPath=/ -v $1 |& sed -rn "s/debug1: Connecting to ([^ ]+) \[([^\]*)] port ([0-9]+).*/\1 \2 \3/p" ||: ) file=$(readlink -f ~/.ssh/known_hosts) if [[ ! $ip ]]; then echo "khfix: ssh failed" return 1 fi if [[ $port != 22 ]]; then ip_entry="[$ip]:$port" host_entry="[$host]:$port" else ip_entry=$ip host_entry=$host fi if [[ $host != "$ip" ]]; then tmp=$(mktemp) ssh-keygen -F "$host_entry" -f $file >$tmp || [[ $? == 1 ]] # 1 when it doesnt exist in the file if [[ -s $tmp ]]; then key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp) fi rm $tmp if [[ $key ]]; then grep -Fv "$key" "$file" | sponge "$file" fi key= fi tmp=$(mktemp) ssh-keygen -F "$ip_entry" -f $file >$tmp || [[ $? == 1 ]] if [[ -s $tmp ]]; then key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp) fi rm $tmp if [[ $key ]]; then grep -Fv "$key" "$file" | sponge "$file" fi ll ~/.ssh/known_hosts } khfix-r() { # known hosts fix + root _khfix-common "$@" || return 1 ssh $1 : rootsshsync } khfix() { _khfix-common "$@" || return 1 ssh $1 : } # copy path into clipboard a() { local x x=$(readlink -nf "${1:-$PWD}") # yes, its kinda dumb that xclip/xsel cant do this in one invocation echo -n "$x" | xclip -selection clipboard echo -n "$x" | xclip } # a1 = awk {print $1} for field in {1..20}; do eval a$field"() { awk '{print \$$field}'; }" done # h1 = head -n1 for num in {1..9}; do eval h$num"() { head -n$num || [[ \$? == 141 ]]; }" done hexipv4() { # shellcheck disable=SC2046 disable=SC2001 disable=SC2183 # hacks, expected printf '%d.%d.%d.%d\n' $(echo $1 | sed 's/../0x& /g') } vp9() { local f out outdir in fname origdir skip1 origdir="$PWD" outdir=vp9 skip1=false while [[ $1 == -* ]]; do case $1 in # if we got interrupted after 1st phase -2) skip1=true shift ;; --out) outdir=$2 shift 2 ;; esac done m mkdir -p $outdir # first pass only uses about 1 cpu, so run in parallel for f; do { fname="${f##*/f}" if [[ $f == /* ]]; then in="$f" else in=$origdir/$f fi out="$origdir/$outdir/$fname" mkdir -p /tmp/vp9/$fname cd /tmp/vp9/$fname if ! $skip1 && [[ ! -s ffmpeg2pass-0.log ]]; then # -nostdin or else wait causes ffmpeg to go into stopped state. dunno why, random stackoverflow answer. m ffmpeg -nostdin -hide_banner -loglevel error -i $in -g 192 -vcodec libvpx-vp9 -vf scale=-1:720 -max_muxing_queue_size 9999 -b:v 750K -pass 1 -an -f null /dev/null fi if [[ -e $out ]]; then rm -f $out; fi m ffmpeg -nostdin -hide_banner -loglevel error -y -i $in -g 192 -vcodec libvpx-vp9 -tile-rows 2 -vf scale=-1:720 -max_muxing_queue_size 9999 -b:v 750K -pass 2 -c:a libvorbis -qscale:a 5 $out } & done wait -f cd "$origdir" } utcl() { # utc 24 hour time to local hour 24 hour time echo "print( ($1 $(date +%z | sed -r 's/..$//;s/^(-?)0*/\1/')) % 24)"|python3 } bwm() { s bwm-ng -T avg -d } # for running in a fai rescue. iank specific. kdrescue() { d=vgata-Samsung_SSD_850_EVO_2TB_S2RLNX0J502123D for f in $d vgata-Samsung_SSD_870_QVO_8TB_S5VUNG0N900656V; do cryptsetup luksOpen --key-file /p /dev/$f/root crypt-$f-root cryptsetup luksOpen --key-file /p /dev/$f/o crypt-$f-o done mount -o subvol=root_trisquelaramo /dev/mapper/crypt-$d-root /mnt mount -o subvol=a /dev/mapper/crypt-$d-root /mnt/a mount -o subvol=o /dev/mapper/crypt-$d-o /mnt/o mount -o subvol=boot_trisquelaramo /dev/sda2 /mnt/boot cd /mnt chrbind } c4() { c /var/log/exim4; } caa() { git commit --amend --no-edit -a; } cf() { for f; do hr echo "$f" hr cat "$f" done } caf() { local file find -L "$@" -type f -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) -printf '%h\0%d\0%p\n' | sort -t '\0' -n \ | awk -F '\0' '{print $3}' 2>/dev/null | while read -r file; do hr printf "%s\n" "$file" hr cat "$file" done } ccomp cat cf caf calc() { echo "scale=3; $*" | bc -l; } # no having to type quotes, but also no command history: clc() { local x read -r x echo "scale=3; $x" | bc -l } cx() { chmod +X "$@" } cam() { git commit -am "$*" } ccat () { # config cat. see a config without extra lines. sed -r '/^[[:space:]]*([;#]|--|\/\/|$)/d' "$@" } ccomp grep ccat chrbind() { local d # dev/pts needed for pacman signature check for d in dev proc sys dev/pts; do [[ -d $d ]] if ! mountpoint $d &>/dev/null; then m s mount -o bind /$d $d fi done } chumount() { local d # dev/pts needed for pacman signature check for d in dev/pts dev proc sys; do [[ -d $d ]] if mountpoint $d &>/dev/null; then m s umount $d fi done } _cdiff-prep() { # join options which are continued to multiples lines onto one line local first=true while IFS= read -r line; do # remove leading spaces/tabs. assumes extglob if [[ $line == "[ ]*" ]]; then line="${line##+( )}" fi if $first; then pastline="$line" first=false elif [[ $line == *=* ]]; then echo "$pastline" >> "$2" pastline="$line" else pastline="$pastline $line" fi done < <(grep -vE '^([ \t]*#|^[ \t]*$)' "$1") echo "$pastline" >> "$2" } cdiff() { # diff config files, # setup for format of postfix, eg: # option = stuff[,] # [more stuff] local pastline unified f1 f2 unified="$(mktemp)" f1="$(mktemp)" f2="$(mktemp)" _cdiff-prep "$1" "$f1" _cdiff-prep "$2" "$f2" cat "$f1" "$f2" | grep -Po '^[^=]+=' | sort | uniq > "$unified" while IFS= read -r line; do # the default bright red / blue doesnt work in emacs shell dwdiff -cblue,red -A best -d " ," <(grep "^$line" "$f1" || echo ) <(grep "^$line" "$f2" || echo ) | colordiff done < "$unified" } cat-new-files() { local start=$SECONDS local dir="$1" # shellcheck disable=SC2030 inotifywait -m "$dir" -e create -e moved_to | \ while read -r filedir _ file; do cat "$filedir$file" hr calc $((SECONDS - start)) / 60 sleep 5 done } chownme() { s chown -R $USER:$USER "$@" } # shellcheck disable=SC2032 chown() { # makes it so chown -R symlink affects the symlink and its target. if [[ $1 == -R ]]; then shift command chown -h "$@" command chown -R "$@" else command chown "$@" fi } cim() { git commit -m "$*" } d() { builtin bg "$@"; } ccomp bg d # f would be more natural, but i already am using it for something z() { builtin fg "$@"; } ccomp fg z x() { builtin kill %%; } dc() { diff --strip-trailing-cr -w "$@" # diff content } ccomp diff dc despace() { local x y for x in "$@"; do y="${x// /_}" safe_rename "$x" "$y" done } # df progress # usage: dfp MOUNTPOINT [SECOND_INTERVAL] # SECOND_INTERVAL defaults to 90 dfp() { # mp = mountpoint local a b mp interval mp=$1 interval=${2:-90} if [[ ! $mp ]]; then echo "dfp: error, missing 1st arg" >&2 return 1 fi while true; do a=$(df --output=used $mp | tail -n1) sleep $interval b=$(df --output=used $mp | tail -n1) printf "used mib: %'d mib/min: %s\n" $(( b /1000 )) $(( (b-a) / (interval * 1000 / 60 ) )) done } # get ipv4 ip from HOST. or if it is already a number, return that hostip() { local host="$1" case $host in [0-9:]) echo "$host" ;; *) getent ahostsv4 "$host" | awk '{ print $1 }' | head -n1 ;; esac } dig() { command dig +nostats +nocmd "$@" } # Output with sections sorted, and removal of query id, so 2 dig outputs can be diffed. digsort() { local sec sec= dig +nordflag "$@" | sed -r 's/^(;; ->>HEADER<<-.*), id: .*/\1/' | while read -r l; do if [[ $l == [^\;]* ]]; then sec+="$l"$'\n' else if [[ $sec ]]; then printf "%s" "$sec" | sort sec= fi printf "%s\n" "$l" fi done } ccomp dig digsort # compare digs to the 2 servers # usage: digdiff @server1 @server2 DIG_ARGS # note: only the soa master nameserver will respond with # ra "recursive answer" flag. That difference is meaningless afaik. digdiff() { local s1 s2 s1=$1 shift s2=$1 shift digsort $s1 "$@" | tee /tmp/digdiff diff -u /tmp/digdiff <(digsort $s2 "$@") } # date in a format i like reading dt() { date "+%A, %B %d, %r" "$@" } dtr() { date -R "$@" } # date with all digits in a format i like dtd() { date +%F_%T% "$@" } ccomp date dt dtr dtd dus() { # du, sorted, default arg of du -sh ${@:-*} | sort -h } ccomp du dus e() { printf "%s\n" "$*"; } # echo args ea() { if (( ! $# )); then echo no args fi for arg; do printf "%qEOL\n" "${arg}" printf "%s" "${arg}" |& hexdump -C done } # echo variables. print var including escapes, etc, like xxd for variable ev() { if (( ! $# )); then echo no args fi for arg; do if [[ -v $arg ]]; then printf "%qEOL\n" "${!arg}" printf "%s" "${!arg}" |& hexdump -C else echo arg $arg is unset fi done } ediff() { [[ ${#@} == 2 ]] || { echo "error: ediff requires 2 arguments"; return 1; } emacs --eval "(ediff-files \"$1\" \"$2\")" } # mail related # shellcheck disable=SC2120 # we expect to pass arguments in use outside this file etail() { ngset tail -F /var/log/exim4/mainlog /var/log/exim4/*main /var/log/exim4/paniclog /var/log/exim4/*panic -n 200 "$@" ngreset } etailm() { tail -F /var/log/exim4/mainlog -n 200 "$@" } etail2() { tail -F /var/log/exim4/mymain -n 200 "$@" } ccomp tail etail etail2 # ran into this online, trying it out detach() { ( "$@" &>/dev/null & disown ) } showkeys() { ssh "$@" cat .ssh/authorized_keys{,2} } # print exim old pids eoldpids() { local configtime pid piduptime now daemonpid printf -v now '%(%s)T' -1 configtime=$(stat -c%Y /var/lib/exim4/config.autogenerated) if [[ -s /run/exim4/exim.pid ]]; then daemonpid=$(cat /run/exim4/exim.pid) fi for pid in $(pgrep -f '^/usr/sbin/exim4( |$)'); do # the daemonpid gets reexeced on HUP (service reloads), keeping its same old timestamp if [[ $pid == "$daemonpid" ]]; then continue fi piduptime=$(awk -v ticks="$(getconf CLK_TCK)" 'NR==1 { now=$1; next } END { printf "%9.0f\n", now - ($20/ticks) }' /proc/uptime RS=')' /proc/$pid/stat) ||: # sometimes pids disappear pretty fast if (( configtime > now - piduptime )); then echo $pid fi done } # exim tail but only watch lines from new pids etailnew() { local pid oldpids for pid in $(eoldpids); do oldpids+="$pid|" done if [[ $oldpids ]]; then etail | awk '$3 !~ /^\[('"${oldpids%|}"')\]$/' else etail fi } # exim watch as old pids go away ewatchold() { local configtime pid piduptime now tmpstr local -i count local -a oldpids count=0 while true; do tmpstr=$(eoldpids) mapfile -t oldpids <<<"$tmpstr" if (( ! ${#oldpids[@]} )); then return fi # print the date every 20 iterations if (( ! count % 20 )); then date fi count+=1 ps -f -p "${oldpids[*]}" sleep 1 done } eless() { less /var/log/exim4/mainlog } ccomp less eless eqcat() { exiqgrep -ir.\* -o 60 | while read -r i; do hlm exim -Mvc $i echo hlm exigrep $i /var/log/exim4/mainlog | cat ||: done } eqrmf() { # other ways to get the list of message ids: # exim -bp | awk 'NF == 4 {print $3}' # # this is slower 160ms, vs 60. # exipick -i exiqgrep -ir.\* | xargs exim -Mrm } econfdevnew() { rm -rf /tmp/edev mkdir -p /tmp/edev/etc cp -ra /etc/exim4 /tmp/edev/etc cp -ra /etc/alias* /tmp/edev/etc find /tmp/edev/etc/exim4 -type f -execdir sed -i "s,/etc/,/tmp/edev/etc/,g" '{}' + econfdev } econfdev() { update-exim4.conf -d /tmp/edev/etc/exim4 -o /tmp/edev/e.conf } # exim grep in # show important information about incoming mail in the exim log egrin() { sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p' <${1:-/var/log/exim4/mainlog} } # 2nd line is message-id: egrinid() { sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).* id=([^ ]+) T="(.*)" from (<[^ ]+> .*$)/\1 \5\n \3\n \4/p' <${1:-/var/log/exim4/mainlog} } etailin() { tail -F /var/log/exim4/mainlog | sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p' } fa() { # find array. make an array of file names found by find into $x # argument: find arguments # return: find results in an array $x while read -rd ''; do x+=("$REPLY"); done < <(find "$@" -print0); } faf() { # find all files. use -L to follow symlinks find "$@" -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) -type f 2>/dev/null } # usage ffconcat FILES_TO_CONCAT OUTPUT_FILE ffconcat() { local tmpf tmpf=$(mktemp) printf "file '%s'\n" "$1" >$tmpf while (( $# > 1 )); do shift printf "file '%s'\n" "$1" >>$tmpf done # https://trac.ffmpeg.org/wiki/Concatenate ffmpeg -f concat -safe 0 -i $tmpf -c copy "$1" rm $tmpf } ffremux() { local tmpf tmpd if (( $# == 0 )); then echo ffremux error expected args >&2 return 1 fi tmpd="$(mktemp -d)" for f; do tmpf=$tmpd/"${f##*/}" ffmpeg -i "$f" -c:v copy -c:a copy $tmpf cat $tmpf >"$f" done rm -r $tmpd } # absolute path of file/dir without resolving symlinks. # # Most of the time, I want this where I would normally use readlink. # This is what realpath -s does in most cases, but sometimes it # actually resolves symlinks, at least when they are in /. # # Note, if run on a dir, if the final component is relative, it won't # resolve that. Use the below fpd for that. # # note: we could make a variation of this which # assigns to a variable name using eval, so that we don't have to do # x=$(fp somepath), which might save subshell overhead and look nice, # but I'm not going to bother. fp() { local initial_oldpwd initial_pwd dir base initial_oldpwd="$OLDPWD" initial_pwd="$PWD" if [[ $1 == */* ]]; then dir="${1%/*}" base="/${1##*/}" # CDPATH because having it set will cause cd to possibly print output CDPATH='' cd "$dir" printf "%s%s\n" "$PWD" "$base" CDPATH='' cd "$initial_pwd" OLDPWD="$initial_oldpwd" else printf "%s/%s\n" "$PWD" "$1" fi } # full path of directory without resolving symlinks fpd() { local initial_oldpwd initial_pwd dir initial_oldpwd="$OLDPWD" initial_pwd="$PWD" dir="$1" CDPATH='' cd "$dir" printf "%s%s\n" "$PWD" "$base" cd "$initial_pwd" OLDPWD="$initial_oldpwd" } # mail related frozen() { rm -rf /tmp/frozen sudo mailq |gr frozen|awk '{print $3}' | while read -r id; do sudo exim -Mvl $id echo sudo exim -Mvh $id echo sudo exim -Mvb $id echo -e '\n\n##############################\n' done | tee -a /tmp/frozen } frozenrm() { local ids=() while read -r line; do printf '%s\n' "$line" ids+=("$(printf '%s\n' "$line" |gr frozen|awk '{print $3}')") done < <(s mailq) echo "sleeping for 2 in case you change your mind" sleep 2 sudo exim -Mrm "${ids[@]}" } funce() { # like -e for functions. returns on error. # at the end of the function, disable with: # trap ERR trap 'echo "${BASH_COMMAND:+BASH_COMMAND=\"$BASH_COMMAND\" } ${FUNCNAME:+FUNCNAME=\"$FUNCNAME\" }${LINENO:+LINENO=\"$LINENO\" }\$?=$?" trap ERR return' ERR } getdir () { local help="Usage: getdir [--help] PATH Output the directory of PATH, or just PATH if it is a directory." if [[ $1 == --help ]]; then echo "$help" return 0 fi if [[ $# -ne 1 ]]; then echo "getdir error: expected 1 argument, got $#" return 1 fi if [[ -d $1 ]]; then echo "$1" else local dir dir="$(dirname "$1")" if [[ -d $dir ]]; then echo "$dir" else echo "getdir error: directory does not exist" return 1 fi fi } git_empty_branch() { # start an empty git branch. carefull, it deletes untracked files. [[ $# == 1 ]] || { echo 'need a branch name!'; return 1;} local root root=$(gitroot) || return 1 # function to set gitroot builtin cd "$root" git symbolic-ref HEAD refs/heads/$1 rm .git/index git clean -fdx } # shellcheck disable=SC2120 gitroot() { local help="Usage: gitroot [--help] Print the full path to the root of the current git repo Handles being within a .git directory, unlike git rev-parse --show-toplevel, and works in older versions of git which did not have that." if [[ $1 == --help ]]; then echo "$help" return fi local p p=$(git rev-parse --git-dir) || { echo "error: not in a git repo" ; return 1; } [[ $p != /* ]] && p=$PWD echo "${p%%/.git}" } g() { local args gdb=false if [[ $EMACSDIR ]]; then path-add "$EMACSDIR/lib-src" "$EMACSDIR/src" fi if [[ $DISPLAY ]]; then args=-n fi if (( $# == 0 )); then args+=" -c" fi # duplicate -c, but oh well if ! pgrep -u $EUID emacsclient; then if (( $# == 0 )) && type -p gdb &>/dev/null; then gdb=true else args+=" -c" fi fi if [[ $EMACSDIR ]]; then # todo: we don't have to alter HOME since emacs 29+, we can set # user-emacs-directory with the flag --init-directory # Alter the path here, otherwise the nfs mount gets triggered on the # first path lookup when emacs is not being used. # shellcheck disable=SC2098 disable=SC2097 # false positive PATH="$EMACSDIR/lib-src:$EMACSDIR/src:$PATH" EHOME=$HOME HOME=$EMACSDIR m emacsclient -a "" $args "$@" else if $gdb; then # due to a bug, we cant debug from the start unless we get a new gdb # https://sourceware.org/bugzilla/show_bug.cgi?id=24454 # m gdb -ex="set follow-fork-mode child" -ex=r -ex=quit --args emacs --daemon m emacsclient -a "" $args "$@" sleep 1 cd "/a/opt/emacs-$(distro-name)$(distro-num)" s gdb -p "$(pgrep -f 'emacs --daemon')" -ex c cd - else m emacsclient -a "" $args "$@" fi fi } # g pipe. like: cmd | emacs. save cmd output to tmp file, then edit. gp() { cat &>/a/tmp/gtmp g "$@" /a/tmp/gtmp } # g log #like cmd &> tempfile; emacs tempfile # # note: a useful workflow for doing mass replace on my files: # gc rem REGEX ## remove any false positives, or manually edit them. rename files if needed. # sedi 's/REGEX/REPLACEMENT/' $(gr '^/' /a/tmp/gtmp) gl() { "$@" &> /a/tmp/gtmp g /a/tmp/gtmp } # g command substitution gc() { g $("$@") } # force terminal version gn() { g -n "$@" } gmacs() { # quit will prompt if the program crashes. gdb -ex=r -ex=quit --args emacs "$@"; r; } gdkill() { # kill the emacs daemon pk1 emacs --daemon } gr() { grep -iIP --color=auto "$@" || return $? } grr() { # grep recursive # Don't return 1 on nonmatch because this is meant to be # interactive, not in a conditional. if [[ ${#@} == 1 ]]; then grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" . || [[ $? == 1 ]] else grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" || [[ $? == 1 ]] fi } ccomp grep gr grr rg() { grr "$@"; } ccomp grep rg # recursive everything. search for files/dirs and lines. rs = easy chars to press re() { local query query="$1" find "$@" -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto "$query" grr -m 5 "$@" } # horizontal row. used to break up output hr() { local blocks # 180 is long enough. blocks=██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ printf "%s\n" "$(tput setaf 5 2>/dev/null ||:)${blocks:0:${COLUMNS:-180}}$(tput sgr0 2>/dev/null||:)" } # highlight hl() { local col input_len=0 for arg; do input_len=$((input_len + 1 + ${#arg})) done col=$((60 - input_len)) printf "\e[1;97;41m%s" "$*" if (( col > 0 )); then # shellcheck disable=SC2046 # needed to work as intended. a better way would be like hr above. printf "\e[1;97;41m \e[0m%.0s" $(eval echo "{1..${col}}") fi echo } hlm() { hl "$*"; "$@"; } hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; done } # example usage: # github-release-dl restic/restic restic_ _linux_amd64.bz2 # gets a url like: # https://github.com/restic/restic/releases/download/v0.16.3/restic_0.16.3_linux_amd64.bz2 github-release-dl() { local github_path file_prefix file_suffix latest_prefix version redir_path github_path=$1 file_prefix=$2 file_suffix=$3 if (( $# != 3 )); then echo "$0: error, expected 3 arguments" >&2 return 1 fi redir_path="https://github.com/$github_path/releases/latest/download/" latest_prefix=$(curl -s -I "$redir_path" | awk 'tolower($1) == "location:" {print $2}') # it has a trailing /r at the end. just kill any whitespace. latest_prefix="${latest_prefix//[$'\t\r\n ']}" if [[ ! $latest_prefix ]]; then echo "failed to find latest path. Tried to find case insensitive 'location:' in the curl output:" m curl -s -I "$redir_path" return 1 fi version="${latest_prefix##*/}" version="${version#v}" m wget -- "$latest_prefix/$file_prefix$version$file_suffix" } # examples. # go-github-install restic/restic restic_ _linux_amd64.bz2 # go-github-install restic/rest-server rest-server_ _linux_amd64.tar.gz # common pattern among go binaries on github go-github-install() { local tmpd targetf tmp files src tmpd=$(mktemp -d) cd $tmpd file_prefix=$2 file_suffix=$3 tmp="${file_prefix##*[[:alnum:]]}" targetf="${file_prefix%$tmp}" echo targetf: $targetf github-release-dl "$@" files=(./*) case $file_suffix in *.bz2) bunzip2 -- ./* ;; *.tar.gz|*.tgz) tar -vxzf ./* ;; esac rm -f -- "${files[@]}" files=(./*) # Here we detect and handle 2 cases: either we extracted a single # binary which we have to rename or a folder with a binary named # $targetf in it which is all we care about. if (( ${#files[@]} == 1 )) && [[ -f ${files[0]} ]]; then chmod +x ./* mv -- ./* /usr/local/bin/$targetf else files=(./*/$targetf) if [[ -f $targetf ]]; then src=$targetf elif [[ -f ${files[0]} ]]; then src="${files[0]}" fi chmod +x "$src" mv -- "$src" /usr/local/bin fi cd - >/dev/null rm -rf $tmpd } ## 2024: I'm using gh instead of hub, but leaving this just in case. ## I tried the github cli tool (gh) and it seems easier than ## I remember hub. ## ## hub predated github's 2020 official cli tool gh. ## more info at ## https://raw.githubusercontent.com/cli/cli/trunk/docs/gh-vs-hub.md # get latest hub and run it # main command to use: # hub pull-request --no-edit # --no-edit means to use the first commit\'s message as the pull request message. # If that fails, try doing # hub pull-request --no-edit -b UPSTREAM_OWNER:branch # where branch is usually master. it does the pr against your current branch. # # On first use, you input username/pass and it gets an oath token so you dont have to repeat # it\'s at ~/.config/hub hub() { local up uptar updir p re # example https://github.com/github/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz up=$(wget -q -O- https://api.github.com/repos/github/hub/releases/latest | jq -r .assets[].browser_download_url | grep linux-amd64) re='[[:space:]]' if [[ ! $up || $up =~ $re ]]; then echo "failed to get good update url. got: $up" fi uptar=${up##*/} updir=${uptar%.tgz} if [[ ! -e /a/opt/$updir ]]; then rm -rf /a/opt/hub-linux-amd64* wget -P /a/opt $up tar -C /a/opt -zxf /a/opt/$uptar rm -f /a/opt/$uptar fi if ! which hub &>/dev/null; then sudo /a/opt/$updir/install fi # save token across computers if [[ ! -L ~/.config/hub ]]; then if [[ -e ~/.config/hub ]]; then mv ~/.config/hub /p/c/subdir_files/.config/ fi if [[ -e /p/c/subdir_files/.config/hub ]]; then conflink fi fi command hub "$@" } i() { git "$@"; } ccomp git i # git status: # cvs -qn update # git checkout FILE # cvs update -C FILE # git pull # cvs up[date] # potentially useful command translation # https://fling.seas.upenn.edu/~giesen/dynamic/wordpress/equivalent-commands-for-git-svn-and-cvs/ # importing cvs repo into git using git-cvs package: # /f/www $ /usr/lib/git-core/git-cvsimport -C /f/www-git ic() { # fast commit all git commit -am "$*" } ipp() { git pull git push } ifn() { local glob glob="$1" shift find -L "$@" -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) -iname "*$glob*" 2>/dev/null } ifh() { # insensitive find here. args are combined into the search string. # -L = follow symlinks find -L . -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null } ifd() { # insensitive find directory find -L . -type d -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null } ipdrop() { sudo iptables -A INPUT -s $1 -j DROP } istext() { grep -Il "" "$@" &>/dev/null } pst() { pstree -apnA } # journalctl with times in the format the --since= and --until= options accept jrt() { journalctl -e -n100000 -o short-full "$@"; } jr() { journalctl -e -n100000 "$@" ; } jrf() { journalctl -n1000 -f "$@" ; } jru() { # the invocation id is "assigned each time the unit changes from an inactive # state into an activating or active state" man systemd.exec journalctl -e --no-tail -u exim4 _SYSTEMD_INVOCATION_ID="$(systemctl show -p InvocationID --value $1)" } ccomp journalctl jr jrf jru l() { if [[ $PWD == /[iap] ]]; then command ls -A --color=auto -I lost+found "$@" else command ls -A --color=auto "$@" fi } lcn() { locate -i "*$**"; } lg() { LC_COLLATE=C.UTF-8 ll --group-directories-first "$@"; } lt() { ll -tr "$@"; } lld() { ll -d "$@"; } ccomp ls l lg lt lld ll # low recursively lowr() { local f dirs i a local -a all for dirs in false true; do for f; do if [[ -d $f ]]; then all=("$f"/**) # reverse the order to rename the nested dirs first. # note: 0 element is the dir itself for ((i=${#all[@]}-1; i>=1; i--)); do a="${all[i]}" if $dirs && [[ -d $a ]]; then # e dirs low "$a" # debug low "$a" elif ! $dirs && [[ ! -d $a && -e $a ]]; then # debug # e not dirs low "$a" # debug low "$a" fi done fi # just rename all the top level args on the second pass if $dirs; then # e final dirs low "$f" # debug low "$f" fi done done } low() { # make filenames lowercase, remove bad chars local arg new dir f for arg; do arg="${arg%%+(/)}" # remove trailing slashes. assumes we have extglob on. dir="${arg%/*}" if (( ${#dir} == ${#arg} )); then dir=. fi f="${arg##*/}" new="${f,,}" # downcase # shellcheck disable=SC2031 # seems like a shellcheck bug new="${new//[^a-zA-Z0-9._-]/_}" # sub bad chars new="${new#"${new%%[[:alnum:]]*}"}" # remove leading/trailing non-alnum new="${new%"${new##*[[:alnum:]]}"}" # remove bad underscores, like __ and _._ new=$(echo $new | sed -r 's/__+/_/g;s/_+([.-])|([.-])_+/\1/g') safe_rename "$dir/$f" "$dir/$new" || return 1 done return 0 } lower() { # make first letter of filenames lowercase. local x for x in "$@"; do if [[ ${x::1} == [A-Z] ]]; then y=$(tr '[:upper:]' '[:lower:]' <<<"${x::1}")"${x:1}" safe_rename "$x" "$y" || return 1 fi done } k() { # history search grep -iP --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | tail -n 80 || [[ $? == 1 ]]; } ks() { # history search with context # args are an extended regex used by sed history | sed -nr "h;s/^\s*(\S+\s+){4}//;/$*/{g;p}" | tail -n 80 || [[ $? == 1 ]]; } ksu() { # history search unique grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | uniq || [[ $? == 1 ]]; } # todo: id like to do maybe a daily or hourly cronjob to # check that my history file size is increasing. Ive had it # inexplicably truncated in the past. histrm() { history -n HISTTIMEFORMAT='' history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/' read -r -p "press anything but contrl-c to delete" for entry in $(HISTTIMEFORMAT='' history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/ { print a }' | tac); do history -d $entry done history -w } # history without the date histplain() { history "$@" | cut -d' ' -f 7- } ccomp grep k ks ksu histrm make-targets() { # show make targets, via http://stackoverflow.com/questions/3063507/list-goals-targets-in-gnu-make make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}' } mkc() { mkdir "$1" c "$1" } ccomp mkdir mkc mkct() { mkc "$(mktemp -d)" } # mkdir the last arg, cp the rest into it mkcp() { mkdir -p "${@: -1}" cp "${@:1:$#-1}" "${@: -1}" } mkmv() { mkdir -p "${@: -1}" mv "${@:1:$#-1}" "${@: -1}" } mkt() { # mkdir and touch file local path="$1" mkdir -p "$(dirname "$path")" touch "$path" } # shellcheck disable=SC2032 mkdir() { command mkdir -p "$@"; } nags() { # https://github.com/HenriWahl/Nagstamon/issues/357 if ! pgrep -f /usr/bin/dunst >/dev/null; then /usr/bin/dunst & fi /usr/bin/nagstamon & } # profanity screen profsrc() { screen -RD -S profanity } # i dont want to wait for konsole to exit... prof() { command prof &>/dev/null & } # self chat sc() { while read -r l; do printf '\033[1A\033[K'; printf "%s\n" "$l"| ts "%F %T" | tee -a /p/self-chat.log done } nmt() { # cant use s because sudo -i doesnt work for passwordless sudo command case $EUID in 0) sudo nmtui-connect "$@" ;; *) nmtui-connect "$@" ;; esac } ngset() { if shopt nullglob >/dev/null; then ngreset=false else shopt -s nullglob ngreset=true fi } ngreset() { if $ngreset; then shopt -u nullglob fi } nopanic() { # shellcheck disable=SC2024 ngset for f in /var/log/exim4/paniclog /var/log/exim4/*panic; do base=${f##*/} if [[ -s $f ]]; then echo ================== $f ============= s tee -a /var/log/exim4/$base-archive <$f s truncate -s0 $f fi done ngreset } ping() { command ping -O "$@"; } p8() { ping "$@" 8.8.8.8; } p6() { ping6 "$@" 2001:4860:4860::8888; } pkx() { # package extract local pkg cached tmp f c "$(mktemp -d)" pkg=$1 # shellcheck disable=SC2012 cached=$(ls -t /var/cache/apt/archives/${pkg}_* | tail -n1 2>/dev/null) ||: if [[ $cached ]]; then m cp $cached . else m aptitude download $pkg || return 1 fi tmp=(*); f=${tmp[0]} # only 1 expected m ex $f m rm -f $f } # pgrep and kill pk1() { local tmpf local -a pids tmpf=$(pgrep -f "$*") mapfile -t pids <<<"$tmpf" case ${#pids[@]} in 1) # shellcheck disable=SC2128 { ps -F ${pids[0]} m kill ${pids[0]} } ;; 0) echo "no pid found" ;; *) ps -F ${pids[@]} ;; esac } psg () { local x y help help="Usage: psg [--help] GREP_ARGS grep ps and output in a nice format" if [[ $1 == --help ]]; then echo "$help" return fi x=$(ps -eF) # final grep is because some commands tend to have a lot of trailing spaces y=$(echo "$x" | grep -iP "$@" | grep -o '.*[^ ]') ||: if [[ $y ]]; then echo "$x" | head -n 1 || [[ $? == 141 ]] echo "$y" fi } pubip() { curl -4s https://icanhazip.com; } pubip6() { curl -6s https://icanhazip.com; } whatismyip() { pubip; } q() { # start / launch a program in the backround and redir output to null "$@" &> /dev/null & } # shellcheck disable=SC2120 r() { if [[ $HISTFILE ]]; then history -a # save history fi trap ERR # this avoids a segfault exit ${1:0} # i had this redir, not sure why # exit "$@" 2>/dev/null } # scp is insecure and deprecated. scp() { rsync -Pt --inplace "$@" } ccomp rsync scp randport() { # available high ports are 1024-65535, # but lets skip things that are more likely to be in use python3 <<'EOF' import secrets print(secrets.SystemRandom().randrange(10002,65500)) EOF } # reapply bashrc reb() { # shellcheck disable=SC1090 # expected to not follow source ~/.bashrc } rl() { readlink -f "$@" } ccomp readlink rl rsd() { # rsync, root is required to keep permissions right. # rsync --archive --human-readable --verbose --itemize-changes --checksum \(-ahvic\) \ # --no-times --delete # basically, make an exact copy, use checksums instead of file times to be more accurate rsync -ahvic --delete "$@" } rsa() { # like rlu, but dont delete files on the target end which # do not exist on the original end. rsync -ahvic "$@" } rst() { # rl without preserving modification time. rsync -ahvic --delete --no-t "$@" } # [RSYNC_OPTS] HOST PATH rsu() { # eg. rsu -opts frodo /testpath # relative paths will expanded with readlink -f. opts=("${@:1:$#-2}") # 1 to last -2 path="${*:$#}" # last host="${*:$#-1:1}" # last -1 if [[ $path == .* ]]; then path=$(readlink -f $path) fi m rsync -ahvi --relative --no-implied-dirs "${opts[@]}" "$path" "root@$host:/"; } ccomp rsync rsd rsa rst rsu # find programs listening on a port ssp() { local port=$1 # to figure out these args, i had to look at the man page from git version, as of 2022-04. s ss -lpn state listening sport = $port } resolvcat() { local f if [[ $(systemctl is-active nscd ||:) != inactive ]]; then m s nscd -i hosts fi f=/etc/resolv.conf echo $f:; ccat $f hr; s ss -lpn sport = 53 if systemctl is-enabled dnsmasq &>/dev/null || [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then # this will fail is dnsmasq is failed hr; m ser status dnsmasq | cat || : f=/etc/dnsmasq.conf hr; echo $f:; ccat $f hr; m grr '^ *(servers-file|server) *=|^ *no-resolv *$' /etc/dnsmasq.conf /etc/dnsmasq.d f=/etc/dnsmasq-servers.conf hr; echo $f:; ccat $f fi hr echo /etc/nsswitch.conf: grep '^ *hosts:' /etc/nsswitch.conf if systemctl is-enabled systemd-resolved &>/dev/null || [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then hr; m ser status systemd-resolved | cat || : hr; m resolvectl status | cat fi } rcat() { resolvcat | less } reresolv() { if [[ $(systemctl is-active nscd ||:) != inactive ]]; then m ser stop nscd sleep .5 m ser start nscd m sudo nscd -i hosts fi if [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then m sudo systemctl restart dnsmasq fi if [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then m sudo systemctl restart systemd-resolved fi if type -P resolvectl &>/dev/null; then resolvectl flush-caches fi } # add annoyingly long argument which should be the default sedi() { sed -i --follow-symlinks "$@" } # todo: test variable assignment with newlines here. # https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash # beware that it only works on the assumption that any special # characters in the input string are intended to be escaped, not to work # as special chacters. shellescape() { LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/' } rmstrips() { ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less } urun () { umask $1 shift "$@" } sudo () { command sudo "$@" || return $? DID_SUDO=true } s() { # background # I use a function because otherwise we cant use in a script, # cant assign to variable. # # note: gksudo is recommended for X apps because it does not set the # home directory to the same, and thus apps writing to ~ fuck things up # with root owned files. # if [[ $EUID != 0 || $1 == -* ]]; then # shellcheck disable=SC2034 SUDOD="$PWD" command sudo -i "$@" DID_SUDO=true else "$@" fi } sb() { # sudo bash -c # use sb instead of s is for sudo redirections, # eg. sb 'echo "ok fine" > /etc/file' # shellcheck disable=SC2034 local SUDOD="$PWD" sudo -i bash -c "$@" } # secret sudo se() { s urun 0077 "$@"; } ccomp sudo s sb se safe_rename() { # warn and dont rename if file exists. # mv -n exists, but it\'s silent if [[ $# != 2 ]]; then echo safe_rename error: $# args, need 2 >&2 return 1 fi if [[ $1 != "$2" ]]; then # yes, we want to silently ignore this if [[ -e $2 || -L $2 ]]; then echo "Cannot rename $1 to $2 as it already exists." else mv -vi "$1" "$2" fi fi } sd() { sudo dd status=none of="$1" } ser() { if type -p systemctl &>/dev/null; then s systemctl "$@" else if (( $# >= 3 )); then echo iank: ser expected 2 or less arguments return 1 fi s service $2 $1 fi } serstat() { systemctl -n 40 status "$@" } seru() { systemctl --user "$@"; } # like restart, but do nothing if its not already started srestart() { local service=$1 if [[ $(s systemctl --no-pager show -p ActiveState $service ) == ActiveState=active ]]; then systemctl restart $service fi } setini() { # set a value in a .ini style file key="$1" value="$2" section="$3" file="$4" if [[ -s $file ]]; then sed -ri -f - "$file" <"$file" </dev/null; then ser enable $service fi } soff () { for service; do # ignore services that dont exist if systemctl cat $service &>/dev/null; then ser stop $service; ser disable $service fi done } sgu() { systemctl list-unit-files | rg "$@" } sk() { # disable a warning with: # shellcheck disable=SC2206 # reasoning # see bash-template/style-guide.md for justifications local quotes others quotes=2048,2068,2086,2206,2254 others=2029,2032,2033,2054,2164, shellcheck -W 999 -x -e $quotes,$others "$@" || return $? } # sk with quotes. For checking scripts that we expect to take untrusted # input in order to verify we quoted vars. skq() { local others others=2029,2033,2054,2164 shellcheck -W 999 -x -e $others "$@" || return $? } skgit() { local f for f in $(i s | awk '$1 == "modified:" {print $2}'); do if istext "$f" && [[ $(head -n1 "$f" 2>/dev/null) == '#!/bin/bash'* ]]; then sk $f ||: fi done } # sl: ssh, but firsh rsync our bashrc and related files to a special # directory on the remote host if needed. # Some environment variables and files need to be setup for this to work # (mine are set at the beginning of this file) # SL_FILES_DIR: Environment variable. Path to folder which should at # least have a .bashrc file or symlink. This dir will be rsynced to ~ on # remote hosts (top level symlinks are resolved) unless the host already # has a $SL_FILES_DIR/.bashrc. In that case, we assume it is a host you # control and sync files to separately and already has the ~/.bashrc you # want. The remote bash will also take its .inputrc config from this # folder (default of not existing is fine). Mine looks like this: # https://iankelling.org/git/?p=distro-setup;a=tree;f=sl/.iank # SL_INFO_DIR: Environment variable. This folder stores info about what # we detected on the remote system and when we last synced. It will be created # if it does not exist. Sometimes you may want to forget about a # remote system, you can use sl --rsync, or the function for that slr # below. # SL_TEST_CMD: Env var. Meant to be used to vary the files synced # depending on the remote host. Run this string on the remote host the # first time sl is run (or if we run slr). The result is passed to # SL_TEST_HOOK. For example, # export SL_TEST_CMD=". /etc/os-release ; echo \${VERSION//[^a-zA-Z0-9]/}" # SL_TEST_HOOK: Env var. It is run as $SL_TEST_HOOK. This can set # $SL_FILES_DIR to vary the files synced. # SL_RSYNC_ARGS: Env var. String of arguments passed to rsync. For # example to exclude files within a directory. Note, excluded # files wont be deleted on rsync, you can add --delete-excluded # to the rsync command if that is desired. # SL_SSH_ARGS: Env var. Default arguments passed to ssh. # For when ~/.bashrc is already customized on the remote server, you # might find it problematic that ~/.bashrc is sourced for ALL ssh # commands, even in scripts. This paragraph is all about that. bash # scripts dont source ~/.bashrc, but call ssh in scripts and you get # ~/.bashrc. You dont want this. .bashrc is meant for interactive shells # and if you customize it, probably has bugs from time to time. This is # bad. Here's how I fix it. I have a special condition to "return" in my # .bashrc for noninteractive ssh shells (copy that code). Then use this # function or similar that passes LC_USEBASHRC=t when sshing and I want # my bashrc. Also, I don't keep most of my bashrc in .bashrc, i source a # separate file because even if I return early on, the whole file gets # parsed which can fail if there is a syntax error. sl() { # Background on LC_USEBASHRC var (no need to read if you just want to # use this function): env variables sent across ssh are strictly # limited, but we get LC_* at least in debian based machines, so we # just make that * be something no normal program would use. Note, on # hosts that dont allow LC_* I start an inner shell with LC_USEBASHRC # set, and the inner shell also allows running a nondefault # .bashrc. This means the outer shell still ran the default .bashrc, # but that is the best we can do. local now args remote dorsync haveinfo tmpa sshinfo tmp tmp2 type info_sec force_rsync \ sync_dirname testcmd extra_info testbool files_sec sl_test_cmd sl_test_hook declare -a args tmpa args=($SL_SSH_ARGS) # ssh [-1246Antivivisectionist] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] # [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L address] # [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option] # [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] [user@]hostname # [command] # ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] # [-D [bind_address:]port] [-E log_file] [-e escape_char] # [-F configfile] [-I pkcs11] [-i identity_file] # [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec] # [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address] # [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] force_rsync=false if [[ $1 == --rsync ]]; then force_rsync=true shift fi # shellcheck disable=SC2153 # intentional sl_test_cmd=$SL_TEST_CMD # shellcheck disable=SC2153 # intentional sl_test_hook=$SL_TEST_HOOK # shellcheck disable=SC2153 # intentional sl_rsync_args=$SL_RSYNC_ARGS while [[ $1 ]]; do case "$1" in --rsync) force_rsync=true ;; --sl-test-cmd) sl_test_cmd="$2" shift ;; --sl-test-hook) sl_test_hook="$2" shift ;; --sl-rsync-args) sl_rsync_args="$2" shift ;; *) break ;; esac shift done while [[ $1 ]]; do case "$1" in # note we dont support things like -4oOption -[46AaCfGgKkMNnqsTtVvXxYy]*) args+=("$1"); shift ;; -[bcDEeFIiJLlmOopQRSWw]*) # -oOption etc is valid if (( ${#1} >= 3 )); then args+=("$1"); shift else args+=("$1" "$2"); shift 2 fi ;; *) break ;; esac done remote="$1" if [[ ! $remote ]]; then echo $0: error hostname required >&2 return 1 fi shift if [[ ! $SL_INFO_DIR ]]; then echo 'error: missing SL_INFO_DIR env var' >&2 return 1 fi dorsync=false haveinfo=false tmpa=($SL_INFO_DIR/???????????"$remote") sshinfo=${tmpa[0]} if [[ -e $sshinfo ]]; then if $force_rsync; then rm -f $sshinfo else haveinfo=true fi fi if $haveinfo; then tmp=${sshinfo[0]##*/} tmp2=${tmp::11} type=${tmp2: -1} extra_info=$(cat $sshinfo) else # we test for string to know ssh succeeded testbool="test -e $SL_FILES_DIR/.bashrc -a -L .bashrc -a -v LC_USEBASHRC" testcmd="if $testbool; then printf y; else printf n; fi" if ! tmp=$(LC_USEBASHRC=y command ssh "${args[@]}" "$remote" "$testcmd; $sl_test_cmd"); then echo failed sl test. doing plain ssh -v command ssh -v "${args[@]}" "$remote" fi if [[ $tmp == y* ]]; then type=a else dorsync=true type=b fi extra_info="${tmp:1}" fi if [[ $sl_test_hook ]]; then RSYNC_RSH="ssh ${args[*]}" $sl_test_hook "$extra_info" "$remote" fi if $haveinfo && [[ $type == b ]]; then info_sec=${tmp::10} read -r files_sec _ < <(find -L $SL_FILES_DIR -printf "%T@ %p\n" | sort -nr || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]] ) files_sec=${files_sec%%.*} if (( files_sec > info_sec )); then dorsync=true rm -f $sshinfo fi fi sync_dirname=${SL_FILES_DIR##*/} if [[ ! $SL_FILES_DIR ]]; then echo 'error: missing SL_FILES_DIR env var' >&2 return 1 fi if $dorsync; then RSYNC_RSH="ssh ${args[*]}" m rsync -rptL --delete $sl_rsync_args $SL_FILES_DIR "$remote": fi if $dorsync || ! $haveinfo; then sshinfo=$SL_INFO_DIR/$EPOCHSECONDS$type"$remote" [[ -e $SL_INFO_DIR ]] || mkdir -p $SL_INFO_DIR printf "%s\n" "$extra_info" >$sshinfo chmod 666 $sshinfo fi if [[ $type == b ]]; then if (( ${#@} )); then # Theres a couple ways to pass arguments, im not sure whats best, # but relying on bash 4.4+ escape quoting seems most reliable. command ssh "${args[@]}" "$remote" \ LC_USEBASHRC=t bash -c '.\ '$sync_dirname'/.bashrc\;"\"\$@\""' bash ${@@Q} elif [[ ! -t 0 ]]; then # This case is when commands are being piped to ssh. # Normally, no bashrc gets sourced. # But, since we are doing all this, lets source it because we can. cat <(echo . $sync_dirname/.bashrc) - | command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash else command ssh -t "${args[@]}" "$remote" LC_USEBASHRC=t INPUTRC=$sync_dirname/.inputrc bash --rcfile $sync_dirname/.bashrc fi else if [[ -t 0 ]]; then LC_USEBASHRC=t command ssh "${args[@]}" "$remote" ${@@Q} else command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash fi fi # this function inspired from https://github.com/Russell91/sshrc } slr() { sl --rsync "$@" } sss() { # ssh solo sl -oControlMaster=no -oControlPath=/ "$@" } # kill off old shared socket then ssh ssk() { m ssh -O exit "$@" || [[ $? == 255 ]] m sl "$@" } ccomp ssh sl slr sss ssk # plain ssh ssh() { if [[ $TERM == alacritty || $TERM == xterm-kitty ]]; then TERM=xterm-256color LC_USEBASHRC=t command ssh "$@" else LC_USEBASHRC=t command ssh "$@" fi } slog() { # log with script. timing is $1.t and script is $1.s # -l to save to ~/typescripts/ # -t to add a timestamp to the filenames local logdir do_stamp arg_base (( $# >= 1 )) || { echo "arguments wrong"; return 1; } logdir="/a/dt/" do_stamp=false while getopts "lt" option do case $option in l) arg_base=$logdir ;; t) do_stamp=true ;; *) echo error: bad option return 1 ;; esac done shift $((OPTIND - 1)) arg_base+=$1 [[ -e $logdir ]] || mkdir -p $logdir $do_stamp && arg_base+=$(date +%F.%T%z) script -t $arg_base.s 2> $arg_base.t } splay() { # script replay #logRoot="$HOME/typescripts/" #scriptreplay "$logRoot$1.t" "$logRoot$1.s" scriptreplay "$1.t" "$1.s" } sr() { # sudo redo. be aware, this command may not work right on strange distros or earlier software if [[ $# == 0 ]]; then sudo -E bash -c -l "$(history -p '!!')" else echo this command redos last history item. no argument is accepted fi } srm () { # with -ll, less secure but faster. command srm -ll "$@" } srun() { scp $2 $1:/tmp ssh $1 "/tmp/${2##*/}" "$(printf "%q\n" "${@:2}")" } swap() { local tmp tmp=$(mktemp) mv $1 $tmp mv $2 $1 mv $tmp $2 } tclock() { # terminal clock local x clear date +%l:%_M len=60 # this goes to full width #len=${1:-$((COLUMNS -7))} x=1 while true; do if (( x == len )); then end=true d="$(date +%l:%_M) " else end=false d=$(date +%l:%M:%_S) fi echo -en "\r" echo -n "$d" for ((i=0; i/dev/null; then s ip netns add nonet fi sudo -E env /sbin/ip netns exec nonet sudo -E -u iank /bin/bash } m() { printf "%s\n" "$*"; "$@"; } # update file. note: duplicated in mail-setup. # updates $ur u result to true or false # updates $reload to true if file updated is in /etc/systemd/system u() { local tmp tmpdir dest="$1" local base="${dest##*/}" local dir="${dest%/*}" if [[ $dir != "$base" ]]; then # dest has a directory component mkdir -p "$dir" fi # shellcheck disable=SC2034 # see comment at top of function ur=false # u result tmpdir="$(mktemp -d)" cat >$tmpdir/"$base" tmp=$(rsync -ic $tmpdir/"$base" "$dest") if [[ $tmp ]]; then printf "%s\n" "$tmp" # shellcheck disable=SC2034 # see comment at top of function ur=true if [[ $dest == /etc/systemd/system/* ]]; then # shellcheck disable=SC2034 # see comment at top of function reload=true fi fi rm -rf $tmpdir } uptime() { if type -p uprecords &>/dev/null; then uprecords -B else command uptime fi } virshrm() { for x in "$@"; do virsh destroy "$x"; virsh undefine "$x"; done } vm-set-listen(){ local t t=$(mktemp) local vm=$1 local ip=$2 sudo virsh dumpxml $vm | sed -r "s/( ▁▁▃▂▇ # The MIT License # Copyright (c) Zach Holman, https://zachholman.com # https://github.com/holman/spark # As of 2022-10-28, I reviewed github forks that had several newer # commits, none had anything interesting. I did a little refactoring # mostly to fix emacs indent bug. # Generates sparklines. _spark_echo() { if [ "X$1" = "X-n" ]; then shift printf "%s" "$*" else printf "%s\n" "$*" fi } spark() { local f tc local n numbers= # find min/max values local min=0xffffffff max=0 for n in ${@//,/ } do # on Linux (or with bash4) we could use `printf %.0f $n` here to # round the number but that doesn't work on OS X (bash3) nor does # `awk '{printf "%.0f",$1}' <<< $n` work, so just cut it off n=${n%.*} (( n < min )) && min=$n (( n > max )) && max=$n numbers=$numbers${numbers:+ }$n done # print ticks local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █) # use a high tick if data is constant (( min == max )) && ticks=(▅ ▆) tc=${#ticks[@]} f=$(( ( (max-min) <<8)/( tc - 1) )) (( f < 1 )) && f=1 for n in $numbers do _spark_echo -n ${ticks[$(( (((n-min)<<8)/f) ))]} done _spark_echo } pdfwc() { local f; for f; do echo "$f" "$(pdfinfo "$f" | awk '/^Pages:/ {print $2}')"; done } # nvm install script appended this to my .bashrc. I dont want to run it all the time, # so put it in a function. nvm-init() { export NVM_DIR="$HOME/.nvm" # shellcheck disable=SC1091 # may not exist, & third party [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" # This loads nvm # shellcheck disable=SC1091 # may not exist, & third party [ -s "$NVM_DIR/bash_completion" ] && source "$NVM_DIR/bash_completion" # This loads nvm bash_completion } leap-year() { if date -d 'february 29' &>/dev/null; then year_days=366 else year_days=365 fi echo $year_days } # on-battery on-bat() { if [[ -e /sys/class/power_supply/AC/online && $(/dev/null; then echo "$0: error: missing dependency: sudo apt install moreutils" >&2 return 1 fi for f; do echo "adding header to $f" if [[ -s $f ]]; then f_maybe=("$f") else f_maybe=() fi cat - "${f_maybe[@]}" < Copyright (C) $(date +%Y) Free Software Foundation Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. Contributions are welcome. See . EOF done } tsr() { # ts run "$@" |& ts || return $? } # * misc stuff if $use_color && type -p tput &>/dev/null; then # this is nice for a dark background terminal: # https://github.com/trapd00r/LS_COLORS # I would like if there was something similar for light. # the default bold green is too light. # this explains the codes: https://gist.github.com/thomd/7667642 export LS_COLORS=ex=1 term_bold="$(tput bold)" term_red="$(tput setaf 1)" term_green="$(tput setaf 2)" # shellcheck disable=SC2034 # expected term_yellow="$(tput setaf 3)" term_purple="$(tput setaf 5)" term_nocolor="$(tput sgr0)" # no font attributes # unused so far. commented for shellcheck # term_underl="$(tput smul)" # term_blue="$(tput setaf 4)" # term_cyan="$(tput setaf 6)" fi # Try to keep environment pollution down, EPA loves us. unset safe_term match_lhs use_color # * prompt if [[ $- == *i* ]]; then case $HOSTNAME in bk|je|li) if [[ $EUID == 1000 ]]; then system-status _ ||: fi ;; esac # this needs to come before next ps1 stuff # this stuff needs bash 4, feb 2009, # old enough to no longer condition on $BASH_VERSION anymore shopt -s autocd shopt -s dirspell PS1='\w' if [[ $- == *i* ]] && [[ ! $LC_INSIDE_EMACS ]]; then PROMPT_DIRTRIM=2 bind -m vi-command B:shell-backward-word bind -m vi-command W:shell-forward-word fi if [[ $SSH_CLIENT || $SUDO_USER ]]; then unset PROMPT_DIRTRIM PS1="\h:$PS1" fi # emacs terminal has problems if this runs slowly, # so I've thrown a bunch of things at the wall to speed it up. prompt-command() { local return=$? # this MUST COME FIRST local ps_char ps_color unset IFS if [[ $HISTFILE ]]; then history -a # save history fi case $return in 0) ps_color="$term_purple" ps_char='\$' ;; *) ps_color="$term_green" ps_char="$return \\$" ;; esac if [[ ! -O . ]]; then # not owner if [[ -w . ]]; then # writable ps_color="$term_bold$term_red" else ps_color="$term_bold$term_green" fi fi # faster than sourceing the file im guessing if [[ -e /dev/shm/iank-status && ! -e /tmp/quiet-status ]]; then eval "$(< /dev/shm/iank-status)" fi if [[ $MAIL_HOST && $MAIL_HOST != "$HOSTNAME" ]]; then ps_char="@ $ps_char" fi jobs_char= if [[ $(jobs -p) ]]; then jobs_char='j\j ' fi # allow a function to specify a command to run after we run the next # command. Use case: a function makes a persistent notification. If # we happen to be using that terminal, we can just keep working by # entering our next command, even a noop in order to dismiss the # notification, instead of having to explicitly dismiss it. if [[ ${_psrun[*]} ]]; then if (( _psrun_count >= 1 )); then "${_psrun[@]}" ||: _psrun_count=0 unset _psrun else _psrun_count=$(( _psrun_count + 1 )) fi else _psrun_count=0 fi # We could test if sudo is active with sudo -nv # but then we get an email and log of lots of failed sudo commands. # We could turn those off, but seems better not to. if [[ $EUID != 0 ]] && [[ $DID_SUDO ]]; then psudo="\[$term_bold$term_red\]s\[$term_nocolor\] " fi if [[ ! $HISTFILE ]]; then ps_char="NOHIST $ps_char" fi PS1="${PS1%"${PS1#*[wW]}"} $jobs_char$psudo\[$ps_color\]$ps_char\[$term_nocolor\] " # copy of what is automatically added by guix. # adds [env] to PS1 if GUIX_ENVIRONMENT is set and PS1 contains '$'; if [ -n "$GUIX_ENVIRONMENT" ]; then if [[ $PS1 =~ (.*)"\\$" ]]; then PS1="${BASH_REMATCH[1]} [env]\\\$ " fi fi # set titlebar. instead, using more advanced # titelbar below #echo -ne "$_title_escape $HOSTNAME ${PWD/#$HOME/~} \007" } PROMPT_COMMAND=(prompt-command) if [[ $TERM == screen* ]]; then _title_escape="\033]..2;" else # somme sites recommend this, i dunno what the diff is. #_title_escape="\033]30;" _title_escape="\033]0;" fi # make the titlebar be the last command and the current directory. settitle () { # These are some checks to help ensure we dont set the title at # times that the debug trap is running other than the case we # want. Some of them might not be needed. if (( ${#FUNCNAME[@]} != 1 || ${#BASH_ARGC[@]} != 2 || BASH_SUBSHELL != 0 )); then return 0 fi if [[ $1 == prompt-command ]]; then return 0 fi echo -ne "$_title_escape ${PWD/#$HOME/~} " printf "%s" "$*" echo -ne "\007" } # note, this wont work: # x=$(mktemp); cp a $x # I havnt figured out why, bigger fish to fry. # # for titlebar. # condition from the screen man page i think. # note: duplicated in tx() if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then trap 'settitle "$BASH_COMMAND"' DEBUG else trap DEBUG fi fi # * stuff that makes sense to be at the end # best practice unset IFS if [[ -s "$HOME/.rvm/scripts/rvm" ]]; then # shellcheck disable=SC1091 source "$HOME/.rvm/scripts/rvm" fi # I had this idea to start a bash shell which would run an initial # command passed through this env variable, then continue on # interactively. But the use case I had in mind went away. # # if [[ $MY_INIT_CMD ]]; then # "${MY_INIT_CMD[@]}" # unset MY_INIT_CMD # fi # ensure no bad programs appending to this file will have an affect return 0