mostly fixes, a few improvements
[distro-setup] / brc
1 #!/bin/bash
2 # Copyright (C) 2019 Ian Kelling
3 # SPDX-License-Identifier: AGPL-3.0-or-later
4 # this gets sourced. shebang is just for file mode detection
5
6 # Use source ~/.bashrc instead of doing bash -l when running a script
7 # so this can set extdebug and avoid the bash debugger.
8
9
10 if [[ -s /a/bin/bash-bear-trap/bash-bear ]]; then
11 # shellcheck source=/a/bin/bash-bear-trap/bash-bear
12 source /a/bin/bash-bear-trap/bash-bear
13 # wtf, shellcheck doesn't allow disabling warnings in elifs
14 else
15 # bleh shellcheck can't handle disabling in an elif, so nesting this if.
16 # shellcheck disable=SC2154 # set in .bashrc
17 if [[ -s $bashrc_dir/bash-bear ]]; then
18 # shellcheck source=/a/bin/bash-bear-trap/bash-bear
19 source $bashrc_dir/bash-bear
20 fi
21 fi
22
23 # In t8, it runs clear_console for login shells by default. I don't want
24 # my console cleared. And linux ttys get cleared without this.
25 if shopt login_shell >/dev/null && [[ -e ~/.bash_logout ]]; then
26 rm ~/.bash_logout
27 fi
28
29 # if [[ -s /usr/share/bash-completion/completions/git ]]; then
30 # source /usr/share/bash-completion/completions/git
31 # fi
32 # if [[ -s /usr/share/bash-completion/completions/gitk ]]; then
33 # source /usr/share/bash-completion/completions/gitk
34 # fi
35
36 # for testing error catching:
37 # t2() {
38 # echo t2
39 # grep sdf sdfd
40 # echo wtf
41 # }
42 # t1() {
43 # echo t1
44 # t2 a b c
45 # }
46
47 # * settings
48
49 CDPATH=.
50
51
52 # remove all aliases. aliases provided by the system tend to get in the way,
53 # for example, error happens if I try to define a function the same name as an alias
54 unalias -a
55
56 # remove gnome keyring warning messages
57 # there is probably a more proper way, but I didnt find any easily on google
58 # now using xfce+xmonad instead of vanilla xmonad, so disabling this
59 #unset GNOME_KEYRING_CONTROL
60
61 # use extra globing features.
62 shopt -s extglob
63 # include .files when globbing, but ignore files name . and ..
64 # setting this also sets dotglob.
65 export GLOBIGNORE="*/.:*/.."
66
67 # Useful info. see man bash.
68 PS4='$LINENO+ '
69
70
71 # broken with bash_completion package. Saw a bug for this once. dont anymore.
72 # still broken in wheezy
73 # still buggered in latest stable from the web, version 2.1
74 # perhaps its fixed in newer git version, which fails to make for me
75 # this note is from 6-2014.
76 # still broken in flidas.
77 #shopt -s nullglob
78
79 # make tab on an empty line do nothing
80 shopt -s no_empty_cmd_completion
81
82 # fix spelling errors for cd, only in interactive shell
83 shopt -s cdspell
84 # append history instead of overwritting it
85 shopt -s histappend
86 # for compatibility, per gentoo/debian bashrc
87 shopt -s checkwinsize
88 # attempt to save multiline single commands as single history entries.
89 shopt -s cmdhist
90 # enable **
91 shopt -s globstar
92
93
94 # inside emacs fixes
95 if [[ $LC_INSIDE_EMACS ]]; then
96 # EMACS is used by bash on startup, but we dont need it anymore.
97 # plus I hit a bug in a makefile which inherited it
98 unset EMACS
99 export LC_INSIDE_EMACS
100 export PAGER=cat
101 export MANPAGER=cat
102 # scp completion does not work, but this doesnt fix it. todo, figure this out
103 #complete -r scp &> /dev/null
104 # todo, remote file completion fails, figure out how to turn it off
105 export NODE_DISABLE_COLORS=1
106 # This gets rid of ugly terminal escape chars in node repl
107 # sometime, Id like to have completion working in emacs shell for node
108 # the offending chars can be found in lib/readline.js,
109 # things that do like:
110 # stream.write('\x1b[' + (x + 1) + 'G');
111 # We can remove them and keep readline, for example by doing this
112 # to start a repl:
113 #!/usr/bin/env nodejs
114 # var readline = require('readline');
115 # readline.cursorTo = function(a,b,c) {};
116 # readline.clearScreenDown = function(a) {};
117 # const repl = require('repl');
118 # var replServer = repl.start('');
119 #
120 # no prompt, or else readline complete seems to be confused, based
121 # on our column being different? node probably needs to send
122 # different kind of escape sequence that is not ugly. Anyways,
123 # completion doesnt work yet even with the ugly prompt, so whatever
124 #
125 export NODE_NO_READLINE=1
126
127 fi
128
129 export SSH_CONFIG_FILE_OVERRIDE=/root/.ssh/confighome
130
131
132
133 # emacs has a different default search path than the info command. This
134 # adds the info defaults to emacs. This is commented because after
135 # various upgrades this is no longer a problem: for the directories that
136 # exist on my system, emacs already includes the ones that info
137 # searches.
138 #
139 # but not the reverse, because I dun
140 # care much about the cli. The search path is only on the cli if you run
141 # "info xxx", or in emacs if you run '(info xxx)', so not that
142 # important and i don't bother fixing it.
143
144 # # info info says this path is what was compiled, and its not documented
145 # # anywhere. Through source grepping, i found it in files.h of the info
146 # # source in trisquel flidas.
147 # #
148 # # Trailing : means for emacs to add its own stuff on to the end.
149 # #
150 # # A problem with this is that directories which are not readable breaks info. And of course, this hard coding is not nice.
151 # # 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.
152 # #
153 # # https://raw.githubusercontent.com/debian-tex/texinfo/master/info/filesys.h
154 # #
155
156 # # note: to split up the var like this, do:
157 # # IFS=:; printf '%s\n' $INFOPATH
158
159 # dirs=(
160 # /usr/local/info
161 # /usr/info
162 # /usr/local/lib/info
163 # /usr/lib/info
164 # /usr/local/gnu/info
165 # /usr/local/gnu/lib/info
166 # /usr/gnu/info
167 # /usr/gnu/lib/info
168 # /opt/gnu/info
169 # /usr/share/info
170 # /usr/share/lib/info
171 # /usr/local/share/info
172 # /usr/local/share/lib/info
173 # /usr/gnu/lib/emacs/info
174 # /usr/local/gnu/lib/emacs/info
175 # /usr/local/lib/emacs/info
176 # /usr/local/emacs/info
177 # )
178
179 # for d in ${dirs[@]}; do
180 # if [[ -r $d ]]; then
181 # INFOPATH="$d:$INFOPATH"
182 # fi
183 # done
184 # unset d dirs
185
186
187 # note: guix bash config does this automatically.
188 if [[ $INFOPATH != *: ]]; then
189 INFOPATH="$INFOPATH:"
190 fi
191
192 # info parameter expansion
193 #
194 # info cheat sheet:
195 # H: see keybinds
196 # / search, {, }: next/prev match
197 # ctrl/alt-v scroll forward/backward within this node
198 # l: go to previous node
199 #
200 info-pe() {
201 info bash 'Basic Shell Features' 'Shell Expansions' 'Shell Parameter Expansion'
202 }
203
204
205 # for openwrt system that has no stty, this is easier than
206 # guarding every time i use it.
207 if ! type -p stty >/dev/null; then
208 stty() { :; }
209 fi
210
211
212 use_color=false
213 if [[ $- == *i* ]]; then
214 # for readline-complete.el
215 if [[ $LC_INSIDE_EMACS ]]; then
216 # all for readline-complete.el
217 stty echo
218 bind 'set horizontal-scroll-mode on'
219 bind 'set print-completions-horizontally on'
220 bind '"\C-i": self-insert'
221 else
222
223
224 if [[ $TERM != dumb ]] && test -t 1; then
225 use_color=true
226 fi
227
228 if [[ $KONSOLE_PROFILE_NAME ]]; then
229 TERM=xterm-256color
230 fi
231
232 if [[ $TERM == alacritty && ! -e /usr/share/terminfo/a/alacritty ]]; then
233 # todo: we should try installing the alacritty terminfo if it is not found
234 # https://github.com/alacritty/alacritty/issues/2838
235 TERM=xterm-256color
236 fi
237
238 # copying from the alacritty example above,
239 if [[ $TERM == xterm-kitty ]]; then
240 if [[ ! -e /usr/share/terminfo/x/xterm-kitty ]]; then
241 TERM=xterm-256color
242 else
243 if [[ -e /a/opt/kitty/shell-integration/bash/kitty.bash ]]; then
244 KITTY_SHELL_INTEGRATION=t
245 source /a/opt/kitty/shell-integration/bash/kitty.bash
246 fi
247 fi
248 fi
249
250 # todo: not sure this works in sakura
251 #stty werase undef
252 #bind "\C-w": kill-region
253 # sakura == xterm-256color
254 # konsole == xterm
255 if [[ $TERM != xterm-kitty && $TERM == xterm* ]]; then
256 # control + arrow keys. for other terminals, see http://unix.stackexchange.com/questions/10806/how-to-change-previous-next-word-shortcut-in-bash
257 bind '"\e[1;5C": shell-forward-word' 2>/dev/null
258 bind '"\e[1;5D": shell-backward-word' 2>/dev/null
259 else
260 # make ctrl-backspace work. for konsole, i fixed it through
261 # /home/iank/.local/share/konsole/default.keytab
262 stty werase ^h
263 bind '"\eOc": shell-forward-word'
264 bind '"\eOd": shell-backward-word'
265 fi
266 # i cant remember why i did this, probably to free up some keys to bind
267 # to other things in bash.
268 # other than C-c and C-z, the rest defined by stty -a are, at least in
269 # gnome-terminal, overridden by bash, or disabled by the system
270 stty lnext undef stop undef start undef
271 fi
272
273 fi
274
275 case $TERM in
276 # fixup broken backspace in chroots
277 xterm-kitty|alacritty)
278 chroot() {
279 TERM=xterm-256color command chroot "$@"
280 }
281 ;;
282 esac
283
284 export BC_LINE_LENGTH=0
285
286 # ansible option
287 export PROFILE_TASKS_TASK_OUTPUT_LIMIT=100
288
289 # note, if I use a machine I dont want files readable by all users, set
290 # umask 077 # If fewer than 4 digits are entered, leading zeros are assumed
291
292 # i for insensitive. the rest from
293 # X means dont remove the current screenworth of output upon exit
294 # R means to show colors n things
295 # a useful flag is -F aka --quit-if-one-screen
296 export LESS=RXij12
297 export SYSTEMD_LESS=$LESS
298
299 export NNN_COLORS=2136
300
301 export SL_FILES_DIR=/b/ds/sl/.iank
302 export SL_INFO_DIR=/p/sshinfo
303
304
305 ### begin pyenv ###
306
307 # this is adapted from things printed to term after install
308 # pyenv. commented for now since I'm not actually using pyenv.
309
310 # export PYENV_ROOT="$HOME/.pyenv"
311 # command -v pyenv &>/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
312 # command -v pyenv &>/dev/null && eval "$(pyenv init -)"
313
314
315 # output showed this example for pyenv-virtualenv, which i have no idea
316 # what it is, but leaving it as a comment in case I end up doing python
317 # dev.
318
319 #eval "$(pyenv virtualenv-init -)"
320 ### end begin pyenv ###
321
322
323
324 # * include files
325
326 if [[ -s $bashrc_dir/path-add-function ]]; then
327 source $bashrc_dir/path-add-function
328 if [[ $SSH_CLIENT ]]; then
329 if grep -qF /home/iank/.iank/e/e /etc/exports &>/dev/null; then
330 export EMACSDIR=/home/iank/.iank/e/e
331 fi
332 path-add $bashrc_dir
333 fi
334 fi
335
336 # if someone exported $SOE (stop on error), catch errors.
337 #
338 # Note, on debian this results in the following warning when in ssh,
339 # hich I haven't figured out how to fix. It doesn't happen if we source
340 # after the shell has started
341 #
342 # bash: /usr/share/bashdb/bashdb-main.inc: No such file or directory
343 # bash: warning: cannot start debugger; debugging mode disabled
344 if [[ $SOE ]]; then
345 if [[ -e /a/bin/bash-bear-trap/bash-bear ]]; then
346 source /a/bin/bash-bear-trap/bash-bear
347 fi
348 fi
349
350
351 mysrc() {
352 local path dir file
353 path=$1
354 dir=${path%/*}
355 file=${path##*/}
356 if [[ -s $path ]]; then
357 # shellcheck disable=SC1090 # this is dynamic, shellcheck can't follow it.
358 source $path
359 elif [[ -s $bashrc_dir/$file ]]; then
360 # shellcheck disable=SC1090 # this is dynamic, shellcheck can't follow it.
361 source $bashrc_dir/$file
362 fi
363 }
364
365
366 mysrc /a/bin/small-misc-bash/ll-function
367 mysrc /a/bin/distro-functions/src/package-manager-abstractions
368
369 # things to remember:
370 # ALT-C - cd into the selected directory
371 # CTRL-T - Paste the selected file path into the command line
372 #
373 # good guide to some of its basic features is the readme file
374 # https://github.com/junegunn/fzf
375
376 # if [[ -s /usr/share/doc/fzf/examples/key-bindings.bash ]]; then
377 # source /usr/share/doc/fzf/examples/key-bindings.bash
378 # fi
379
380 # * functions
381
382
383 # temporary functions
384 y() {
385 m "${@//spring/fall}"
386 }
387 h() {
388 e "${@//spring/fall}"
389 }
390
391
392 ### begin FSF section ###
393
394 # Comments before functions are meant to be good useful
395 # documentation. If they fail at that, please improve them or send Ian a
396 # note.
397
398 ## copy bash completion
399 #
400 # It copies how the bash completion works from one command to other
401 # commands. Generally just use within a .bashrc.
402 #
403 # Usage: ORIGINAL_COMMAND TARGET_COMMAND...
404 #
405 ccomp() {
406 local c src
407 src=$1
408 shift
409 if ! c=$(complete -p $src 2>/dev/null); then
410 _completion_loader $src &>/dev/null ||:
411 c=$(complete -p $src 2>/dev/null) || return 0
412 fi
413 # remove $src( .*|$)
414 c=${c% "$src"}
415 c=${c%% "$src" *}
416 eval $c $*
417 }
418
419 ## BEGIN functions to change directory better than cd ##
420 #
421 # The functions:
422 #
423 # c: acts like cd, but stores directory history: you could alias to cd if you wanted.
424 # b: go back
425 # f: go forward
426 # cl: list recent directories and optionally choose one.
427 #
428 # Finer details you may want to skip:
429 #
430 # bl: print the list of back and forward directories.
431 #
432 # We keep 2 stacks of directories, forward and back. Unlike with a web
433 # browser, the forward stack is not erased when going somewhere new.
434 #
435 # Recent directories are stored in ~/.cdirs.
436 #
437 declare -a _dir_forward _dir_back
438 c() {
439 # normally, the top of _dir_back is our current dir. if it isn't,
440 # put it on there, except we don't want to do that when we
441 # just launched a shell
442 if [[ $OLDPWD ]]; then
443 if (( ${#_dir_back[@]} == 0 )) || [[ ${_dir_back[-1]} != "$PWD" ]]; then
444 _dir_back+=("$PWD")
445 fi
446 fi
447 command cd "$@"
448 if (( ${#_dir_back[@]} == 0 )) || [[ ${_dir_back[-1]} != "$PWD" ]]; then
449 _dir_back+=("$PWD")
450 fi
451 echo "$PWD" >> ~/.cdirs
452 }
453 ccomp cd c
454
455 # back
456 b() {
457 local top_back
458 if (( ${#_dir_back[@]} == 0 )); then
459 echo "nothing left to go back to" >&2
460 return 0
461 fi
462 top_back="${_dir_back[-1]}"
463
464 if [[ $top_back == "$PWD" ]] && (( ${#_dir_back[@]} == 1 )); then
465 echo "already on last back entry" >&2
466 return 0
467 fi
468
469
470 if [[ $top_back == "$PWD" ]]; then
471 # add to dirf if not already there
472 if (( ${#_dir_forward[@]} == 0 )) || [[ ${_dir_forward[-1]} != "$top_back" ]]; then
473 _dir_forward+=("$top_back")
474 fi
475 unset "_dir_back[-1]"
476 command cd "${_dir_back[-1]}"
477 else
478 if (( ${#_dir_forward[@]} == 0 )) || [[ ${_dir_forward[-1]} != "$PWD" ]]; then
479 _dir_forward+=("$PWD")
480 fi
481 command cd "$top_back"
482 fi
483
484 # Interesting feature, not sure I want it.
485 # give us a peek at what is next in the list
486 # if (( ${#_dir_back[@]} >= 2 )); then
487 # printf "%s\n" "${_dir_back[-2]}"
488 # fi
489 #
490
491 # c/b/f Implementation notes:
492 #
493 # The top of the back is $PWD
494 # as long as the last directory change was due to c,b,or cl.
495 #
496 # Example of stack changes:
497 #
498 # a b c (d)
499 ## back
500 # a b (c)
501 # d
502 #back
503 #a (b)
504 #d c
505 #back
506 #(a)
507 #d c b
508 #forward
509 #a (b)
510 #d c
511 #
512 # a b c
513 ## back
514 # a b
515 # (c)
516 ## forward
517
518 }
519 # forward
520 f() {
521 local top_forward
522 if (( ${#_dir_forward[@]} == 0 )); then
523 echo "no forward dir left" >&2
524 return 0
525 fi
526 top_forward="${_dir_forward[-1]}"
527 unset "_dir_forward[-1]"
528 c "$top_forward"
529
530 # give us a peek at what is next in the list
531 # if (( ${#_dir_forward[@]} )); then
532 # printf "%s\n" "${_dir_forward[-1]}"
533 # fi
534 }
535 # cl = cd list
536 cl() {
537 local i line input start
538 local -A buttondirs alines
539 local -a buttons dirs lines
540 buttons=( {a..z} {2..9} )
541 if [[ ! -s ~/.cdirs ]]; then
542 echo nothing in ~/.cdirs
543 return 0
544 fi
545
546 i=0
547
548 mapfile -t lines <~/.cdirs
549 start=$(( ${#lines[@]} - 1 ))
550
551 # we have ~33 buttons as of this writing, so lets
552 # prune down the history every once in a while.
553 if (( start > 500 )); then
554 tac ~/.cdirs | awk '!seen[$0]++' | head -n 200 | tac | sponge ~/.cdirs || [[ $? == 141 ]]
555 fi
556
557 for (( j=start; j >= 0; j-- )); do
558 line="${lines[$j]}"
559 if [[ ! $line || ${alines[$line]} || ! -d "$line" || $line == "$PWD" || line == "$HOME" ]]; then
560 continue
561 fi
562 alines[$line]=t
563 buttondirs[${buttons[i]}]="$line"
564 printf "%s %s\n" ${buttons[i]} "$line"
565 # the LINES bit is for when we have a short terminal, just dont print all
566 # the directories. alternative would be to do something like less the list.
567 if (( i == ${#buttons[@]} - 1 )) || { [[ $LINES ]] && (( i == LINES - 3 )); }; then
568 break
569 fi
570 i=$(( i + 1 ))
571 done
572
573 if (( i == 0 )); then
574 echo "no dirs in ~/.cdirs"
575 return 0
576 fi
577 read -r -N 1 input
578 if [[ $input != $'\n' ]]; then
579 c "${buttondirs[$input]}"
580 fi
581 }
582 # bl = back list. lists the back and forward directories. i tend to
583 # forget this exists and use cl instead.
584 bl() {
585 local start i j max
586 max=10
587 start=$(( ${#_dir_back[@]} - 1 ))
588
589 # cleanup possible repeating of pwd
590 if (( start >= 0 )) && [[ ${_dir_back[$start]} == "$PWD" ]]; then
591 start=$(( start - 1 ))
592 fi
593 j=1
594 if (( start >= 0 )); then
595 for (( i=start; i >= 0 ; i-- )); do
596 printf "%s %s\n" $j ${_dir_back[i]}
597 j=$(( j + 1 ))
598 if (( j >= max )); then
599 break
600 fi
601 done
602 fi
603
604 max=10
605 start=$(( ${#_dir_forward[@]} - 1 ))
606
607 # cleanup possible repeating of pwd
608 if (( start >= 0 )) && [[ ${_dir_forward[$start]} == "$PWD" ]]; then
609 start=$(( start - 1 ))
610 fi
611 if (( start < 0 )); then
612 return 0
613 fi
614 echo --
615 j=1
616 for (( i=start; i >= 0 ; i-- )); do
617 printf "%s %s\n" $j ${_dir_forward[i]}
618 j=$(( j + 1 ))
619 if (( j >= max )); then
620 break
621 fi
622 done
623 }
624 # like running cl <enter> a <enter>
625 cla() {
626 local line
627 mapfile -t lines <~/.cdirs
628 start=$(( ${#lines[@]} - 1 ))
629 for (( j=start; j >= 0; j-- )); do
630 line="${lines[$j]}"
631 if [[ ! $line || ! -d "$line" || $line == "$PWD" || line == "$HOME" ]]; then
632 continue
633 fi
634 e "$line"
635 c "$line"
636 break
637 done
638 }
639 ## END functions to change directory better than cd ##
640
641 # pee do. run args as a command with output copied to syslog.
642 #
643 # Usage: pd [-t TAG] COMMAND...
644 #
645 # -t TAG Override the tag in the syslog. The default is COMMAND with
646 # any path part is removed, eg. for /bin/cat the tag is cat.
647 #
648 # You can view the log via "journalctl -t TAG"
649 pd() {
650 local tag ret
651 ret=0
652 tag=${1##*/}
653 case $1 in
654 -t) tag="$2"; shift 2 ;;
655 esac
656 echo "PWD=$PWD command: $*" | logger -t $tag
657 "$@" |& pee cat "logger -t $tag" || ret=$?
658 echo "exited with status=$ret" | pee cat "logger -t $tag"
659 # this avoids any err-catch
660 (( ret == 0 )) || return $ret
661 }
662 ccomp time pd
663
664 # jdo = journal do. Run command as transient systemd service, tailing
665 # its output in the journal until it completes.
666 #
667 # Usage: jdo COMMAND...
668 #
669 # Compared to pd: commands recognize this is a non-interactive shell.
670 # The service is unaffected if our ssh connection dies, no need to run
671 # in screen or tmux.
672 #
673 # Note: The last few lines of any existing entries for a unit by that
674 # name will be output first, and there will be a few second delay at the
675 # start of the command, and a second or so at the end.
676 #
677 # Note: Functions and aliases obviously won't work, we resolve the
678 # command to a file.
679 #
680 # Note: requires running as root.
681 jdo() {
682 local cmd cmd_name jr_pid ret
683 ret=0
684 cmd="$1"
685 shift
686 if [[ $EUID != 0 ]]; then
687 echo "jdo: error: rerun as root"
688 return 1
689 fi
690 cmd_name=${cmd##*/}
691 if [[ $cmd != /* ]]; then
692 cmd=$(type -P "$cmd")
693 fi
694 # -q = quiet
695 journalctl -qn2 -f -u "$cmd_name" &
696 # Trial and error of time needed to avoid missing initial lines.
697 # .5 was not reliable. 1 was not reliable. 2 was not reliable
698 sleep 4
699 jr_pid=$!
700 systemd-run --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
701 # The sleep lets the journal output its last line
702 # before the prompt comes up.
703 sleep .5
704 kill $jr_pid &>/dev/null ||:
705 unset jr_pid
706 fg &>/dev/null ||:
707 # this avoids any err-catch
708 (( ret == 0 )) || return $ret
709 }
710 ccomp time jdo
711
712 # standard date as used in logs
713 datelog() {
714 date +%Y-%m-%d "$@"
715 }
716
717 # date in log appropriate format
718 dtl() {
719 date "+%F %T" "$@"
720 }
721
722 # ts formatted
723 tsf() {
724 command ts "%F %T" "$@"
725 }
726
727 # ts log. log command to log file.
728 # usage: tsl LOG_PATH_PREFIX COMMAND...
729 # example: tsl /root/command
730 # log file will be like /root/command-2024-02-10.log
731 tsl() {
732 local log_prefix log_path appending ret
733 if (( $# < 2 )); then
734 echo "tsl: error: expected >= 2 arguments, got $#" >&2
735 return 1
736 fi
737 log_prefix="$1"
738 if [[ $log_prefix == */* && ! -d ${log_prefix%*/} ]]; then
739 echo "tsl: error: expected directory at ${log_prefix%*/}" >&2
740 return 1
741 fi
742 log_path=$log_prefix-$(date +%Y-%m-%d).log
743 appending=false
744 if [[ -s $log_path ]]; then
745 appending=true
746 fi
747 shift
748 printf "%s\n" "CWD: $PWD, log: $log_path, running $*" | ts "%F %T" | tee -a "$log_path"
749 ret=0
750 "$@" |& ts "%F %T" | tee -a "$log_path" || ret=$?
751 printf "%s\n" "exit code $ret from command: $*" | ts "%F %T" | tee -a "$log_path"
752 if $appending; then
753 printf "%s\n" "note: this log file contains logs before those of previous command" | ts "%F %T" | tee -a "$log_path"
754 fi
755 }
756
757 disk-info() {
758 local cmds cmd
759 mapfile -t cmds <<'EOF'
760 tail -n +1 /proc/mdstat /etc/mdadm/mdadm.conf /etc/fstab /etc/crypttab
761 lsblk
762 blkid
763 ls -la /dev/disk/by-id
764 EOF
765
766 for cmd in "${cmds[@]}"; do
767 cat <<EOF
768 ### $cmd
769
770 \`\`\`
771 EOF
772 $cmd
773 cat <<'EOF'
774
775 ```
776
777 EOF
778 done
779 }
780
781 #### end fsf section
782
783
784 ..() { c ..; }
785 ...() { c ../..; }
786 ....() { c ../../..; }
787 .....() { c ../../../..; }
788 ......() { c ../../../../..; }
789
790 chere() {
791 local f path
792 for f; do
793 path=$(readlink -e "$f")
794 echo "cat >$path <<'EOF'"
795 cat "$f"
796 echo EOF
797 done
798 }
799
800
801 # file cut copy and paste, like the text buffers :)
802 # I havnt tested these.
803 _fbufferinit() { # internal use
804 ! [[ $my_f_tempdir ]] && my_f_tempdir="$(mktemp -d)"
805 rm -rf "${my_f_tempdir:?}"/*
806 }
807 fcp() { # file cp
808 _fbufferinit
809 cp "$@" "$my_f_tempdir"/
810 }
811 fct() { # file cut
812 _fbufferinit
813 mv "$@" "$my_f_tempdir"/
814 }
815 fpst() { # file paste
816 [[ $2 ]] && { echo too many arguments; return 1; }
817 target=${1:-.}
818 cp "$my_f_tempdir"/* "$target"
819 }
820
821 _khfix-common() {
822 local host ip port file key tmp
823 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" ||: )
824 file=$(readlink -f ~/.ssh/known_hosts)
825 if [[ ! $ip ]]; then
826 echo "khfix: ssh failed"
827 return 1
828 fi
829 if [[ $port != 22 ]]; then
830 ip_entry="[$ip]:$port"
831 host_entry="[$host]:$port"
832 else
833 ip_entry=$ip
834 host_entry=$host
835 fi
836 if [[ $host != "$ip" ]]; then
837 tmp=$(mktemp)
838 ssh-keygen -F "$host_entry" -f $file >$tmp || [[ $? == 1 ]] # 1 when it doesnt exist in the file
839 if [[ -s $tmp ]]; then
840 key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp)
841 fi
842 rm $tmp
843 if [[ $key ]]; then
844 grep -Fv "$key" "$file" | sponge "$file"
845 fi
846 key=
847 fi
848 tmp=$(mktemp)
849 ssh-keygen -F "$ip_entry" -f $file >$tmp || [[ $? == 1 ]]
850 if [[ -s $tmp ]]; then
851 key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp)
852 fi
853 rm $tmp
854 if [[ $key ]]; then
855 grep -Fv "$key" "$file" | sponge "$file"
856 fi
857 ll ~/.ssh/known_hosts
858 }
859 khfix-r() { # known hosts fix + root
860 _khfix-common "$@" || return 1
861 ssh $1 :
862 rootsshsync
863 }
864 khfix() {
865 _khfix-common "$@" || return 1
866 ssh $1 :
867 }
868
869 # copy path into clipboard
870 a() {
871 local x
872 x=$(readlink -nf "${1:-$PWD}")
873 # yes, its kinda dumb that xclip/xsel cant do this in one invocation
874 echo -n "$x" | xclip -selection clipboard
875 echo -n "$x" | xclip
876 }
877
878 # a1 = awk {print $1}
879 for field in {1..20}; do
880 eval a$field"() { awk '{print \$$field}'; }"
881 done
882 # h1 = head -n1
883 for num in {1..9}; do
884 eval h$num"() { head -n$num || [[ \$? == 141 ]]; }"
885 done
886
887
888 hexipv4() {
889 # shellcheck disable=SC2046 disable=SC2001 disable=SC2183 # hacks, expected
890 printf '%d.%d.%d.%d\n' $(echo $1 | sed 's/../0x& /g')
891 }
892
893 vp9() {
894 local f out outdir in fname origdir skip1
895 origdir="$PWD"
896 outdir=vp9
897 skip1=false
898 while [[ $1 == -* ]]; do
899 case $1 in
900 # if we got interrupted after 1st phase
901 -2)
902 skip1=true
903 shift
904 ;;
905 --out)
906 outdir=$2
907 shift 2
908 ;;
909 esac
910 done
911 m mkdir -p $outdir
912 # first pass only uses about 1 cpu, so run in parallel
913 for f; do
914 {
915 fname="${f##*/f}"
916 if [[ $f == /* ]]; then
917 in="$f"
918 else
919 in=$origdir/$f
920 fi
921 out="$origdir/$outdir/$fname"
922 mkdir -p /tmp/vp9/$fname
923 cd /tmp/vp9/$fname
924 if ! $skip1 && [[ ! -s ffmpeg2pass-0.log ]]; then
925 # -nostdin or else wait causes ffmpeg to go into stopped state. dunno why, random stackoverflow answer.
926 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
927 fi
928 if [[ -e $out ]]; then rm -f $out; fi
929 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
930 } &
931 done
932 wait -f
933 cd "$origdir"
934 }
935
936 utcl() { # utc 24 hour time to local hour 24 hour time
937 echo "print( ($1 $(date +%z | sed -r 's/..$//;s/^(-?)0*/\1/')) % 24)"|python3
938 }
939
940 bwm() {
941 s bwm-ng -T avg -d
942 }
943
944
945 # for running in a fai rescue. iank specific.
946 kdrescue() {
947 d=vgata-Samsung_SSD_850_EVO_2TB_S2RLNX0J502123D
948 for f in $d vgata-Samsung_SSD_870_QVO_8TB_S5VUNG0N900656V; do
949 cryptsetup luksOpen --key-file /p /dev/$f/root crypt-$f-root
950 cryptsetup luksOpen --key-file /p /dev/$f/o crypt-$f-o
951 done
952 mount -o subvol=root_trisquelaramo /dev/mapper/crypt-$d-root /mnt
953 mount -o subvol=a /dev/mapper/crypt-$d-root /mnt/a
954 mount -o subvol=o /dev/mapper/crypt-$d-o /mnt/o
955 mount -o subvol=boot_trisquelaramo /dev/sda2 /mnt/boot
956 cd /mnt
957 chrbind
958 }
959
960
961
962
963 c4() { c /var/log/exim4; }
964
965 caa() { git commit --amend --no-edit -a; }
966
967 cf() {
968 for f; do
969 hr
970 echo "$f"
971 hr
972 cat "$f"
973 done
974 }
975 caf() {
976
977 local file
978 find -L "$@" -type f -not \( -name .svn -prune -o -name .git -prune \
979 -o -name .hg -prune -o -name .editor-backups -prune \
980 -o -name .undo-tree-history -prune \) -printf '%h\0%d\0%p\n' | sort -t '\0' -n \
981 | awk -F '\0' '{print $3}' 2>/dev/null | while read -r file; do
982 hr
983 printf "%s\n" "$file"
984 hr
985 cat "$file"
986 done
987 }
988 ccomp cat cf caf
989
990 calc() { echo "scale=3; $*" | bc -l; }
991 # no having to type quotes, but also no command history:
992 clc() {
993 local x
994 read -r x
995 echo "scale=3; $x" | bc -l
996 }
997
998 cx() {
999 chmod +X "$@"
1000 }
1001
1002 cam() {
1003 git commit -am "$*"
1004 }
1005
1006 ccat () { # config cat. see a config without extra lines.
1007 sed -r '/^[[:space:]]*([;#]|--|\/\/|$)/d' "$@"
1008 }
1009 ccomp grep ccat
1010
1011 chrbind() {
1012 local d
1013 # dev/pts needed for pacman signature check
1014 for d in dev proc sys dev/pts; do
1015 [[ -d $d ]]
1016 if ! mountpoint $d &>/dev/null; then
1017 m s mount -o bind /$d $d
1018 fi
1019 done
1020 }
1021 chumount() {
1022 local d
1023 # dev/pts needed for pacman signature check
1024 for d in dev/pts dev proc sys; do
1025 [[ -d $d ]]
1026 if mountpoint $d &>/dev/null; then
1027 m s umount $d
1028 fi
1029 done
1030 }
1031
1032
1033 _cdiff-prep() {
1034 # join options which are continued to multiples lines onto one line
1035 local first=true
1036 while IFS= read -r line; do
1037 # remove leading spaces/tabs. assumes extglob
1038 if [[ $line == "[ ]*" ]]; then
1039 line="${line##+( )}"
1040 fi
1041 if $first; then
1042 pastline="$line"
1043 first=false
1044 elif [[ $line == *=* ]]; then
1045 echo "$pastline" >> "$2"
1046 pastline="$line"
1047 else
1048 pastline="$pastline $line"
1049 fi
1050 done < <(grep -vE '^([ \t]*#|^[ \t]*$)' "$1")
1051 echo "$pastline" >> "$2"
1052 }
1053
1054 cdiff() {
1055 # diff config files,
1056 # setup for format of postfix, eg:
1057 # option = stuff[,]
1058 # [more stuff]
1059 local pastline unified f1 f2
1060 unified="$(mktemp)"
1061 f1="$(mktemp)"
1062 f2="$(mktemp)"
1063 _cdiff-prep "$1" "$f1"
1064 _cdiff-prep "$2" "$f2"
1065 cat "$f1" "$f2" | grep -Po '^[^=]+=' | sort | uniq > "$unified"
1066 while IFS= read -r line; do
1067 # the default bright red / blue doesnt work in emacs shell
1068 dwdiff -cblue,red -A best -d " ," <(grep "^$line" "$f1" || echo ) <(grep "^$line" "$f2" || echo ) | colordiff
1069 done < "$unified"
1070 }
1071
1072
1073 cat-new-files() {
1074 local start=$SECONDS
1075 local dir="$1"
1076 # shellcheck disable=SC2030
1077 inotifywait -m "$dir" -e create -e moved_to | \
1078 while read -r filedir _ file; do
1079 cat "$filedir$file"
1080 hr
1081 calc $((SECONDS - start)) / 60
1082 sleep 5
1083 done
1084
1085 }
1086
1087 chownme() {
1088 s chown -R $USER:$USER "$@"
1089 }
1090
1091 # shellcheck disable=SC2032
1092 chown() {
1093 # makes it so chown -R symlink affects the symlink and its target.
1094 if [[ $1 == -R ]]; then
1095 shift
1096 command chown -h "$@"
1097 command chown -R "$@"
1098 else
1099 command chown "$@"
1100 fi
1101 }
1102
1103 cim() {
1104 git commit -m "$*"
1105 }
1106
1107
1108 d() { builtin bg "$@"; }
1109 ccomp bg d
1110
1111 # f would be more natural, but i already am using it for something
1112 z() { builtin fg "$@"; }
1113 ccomp fg z
1114
1115 x() { builtin kill %%; }
1116
1117 dc() {
1118 diff --strip-trailing-cr -w "$@" # diff content
1119 }
1120 ccomp diff dc
1121
1122 despace() {
1123 local x y
1124 for x in "$@"; do
1125 y="${x// /_}"
1126 safe_rename "$x" "$y"
1127 done
1128 }
1129
1130 # df progress
1131 # usage: dfp MOUNTPOINT [SECOND_INTERVAL]
1132 # SECOND_INTERVAL defaults to 90
1133 dfp() {
1134 # mp = mountpoint
1135 local a b mp interval
1136 mp=$1
1137 interval=${2:-90}
1138 if [[ ! $mp ]]; then
1139 echo "dfp: error, missing 1st arg" >&2
1140 return 1
1141 fi
1142 while true; do
1143 a=$(df --output=used $mp | tail -n1)
1144 sleep $interval
1145 b=$(df --output=used $mp | tail -n1)
1146 printf "used mib: %'d mib/min: %s\n" $(( b /1000 )) $(( (b-a) / (interval * 1000 / 60 ) ))
1147 done
1148 }
1149
1150 # get ipv4 ip from HOST. or if it is already a number, return that
1151 hostip() {
1152 local host="$1"
1153 case $host in
1154 [0-9:])
1155 echo "$host"
1156 ;;
1157 *)
1158 getent ahostsv4 "$host" | awk '{ print $1 }' | head -n1
1159 ;;
1160 esac
1161 }
1162
1163 dig() {
1164 command dig +nostats +nocmd "$@"
1165 }
1166 # Output with sections sorted, and removal of query id, so 2 dig outputs can be diffed.
1167 digsort() {
1168 local sec
1169 sec=
1170 dig +nordflag "$@" | sed -r 's/^(;; ->>HEADER<<-.*), id: .*/\1/' | while read -r l; do
1171 if [[ $l == [^\;]* ]]; then
1172 sec+="$l"$'\n'
1173 else
1174 if [[ $sec ]]; then
1175 printf "%s" "$sec" | sort
1176 sec=
1177 fi
1178 printf "%s\n" "$l"
1179 fi
1180 done
1181 }
1182 ccomp dig digsort
1183 # compare digs to the 2 servers
1184 # usage: digdiff @server1 @server2 DIG_ARGS
1185 # note: only the soa master nameserver will respond with
1186 # ra "recursive answer" flag. That difference is meaningless afaik.
1187 digdiff() {
1188 local s1 s2
1189 s1=$1
1190 shift
1191 s2=$1
1192 shift
1193 digsort $s1 "$@" | tee /tmp/digdiff
1194 diff -u /tmp/digdiff <(digsort $s2 "$@")
1195 }
1196
1197 # date in a format i like reading
1198 dt() {
1199 date "+%A, %B %d, %r" "$@"
1200 }
1201 dtr() {
1202 date -R "$@"
1203 }
1204 # date with all digits in a format i like
1205 dtd() {
1206 date +%F_%T% "$@"
1207 }
1208 ccomp date dt dtr dtd
1209
1210 dus() { # du, sorted, default arg of
1211 du -sh ${@:-*} | sort -h
1212 }
1213 ccomp du dus
1214
1215
1216 e() { printf "%s\n" "$*"; }
1217
1218 # echo args
1219 ea() {
1220 if (( ! $# )); then
1221 echo no args
1222 fi
1223 for arg; do
1224 printf "%qEOL\n" "${arg}"
1225 printf "%s" "${arg}" |& hexdump -C
1226 done
1227 }
1228
1229 # echo variables. print var including escapes, etc, like xxd for variable
1230 ev() {
1231 if (( ! $# )); then
1232 echo no args
1233 fi
1234 for arg; do
1235 if [[ -v $arg ]]; then
1236 printf "%qEOL\n" "${!arg}"
1237 printf "%s" "${!arg}" |& hexdump -C
1238 else
1239 echo arg $arg is unset
1240 fi
1241 done
1242 }
1243
1244 ediff() {
1245 [[ ${#@} == 2 ]] || { echo "error: ediff requires 2 arguments"; return 1; }
1246 emacs --eval "(ediff-files \"$1\" \"$2\")"
1247 }
1248
1249 # mail related
1250 # shellcheck disable=SC2120 # we expect to pass arguments in use outside this file
1251 etail() {
1252 ngset
1253 tail -F /var/log/exim4/mainlog /var/log/exim4/*main /var/log/exim4/paniclog /var/log/exim4/*panic -n 200 "$@"
1254 ngreset
1255 }
1256 etailm() {
1257 tail -F /var/log/exim4/mainlog -n 200 "$@"
1258 }
1259 etail2() {
1260 tail -F /var/log/exim4/mymain -n 200 "$@"
1261 }
1262 ccomp tail etail etail2
1263
1264 # ran into this online, trying it out
1265 detach() {
1266 ( "$@" &>/dev/null & disown )
1267 }
1268
1269 showkeys() {
1270 ssh "$@" cat .ssh/authorized_keys{,2}
1271 }
1272
1273
1274 # print exim old pids
1275 eoldpids() {
1276 local configtime pid piduptime now daemonpid
1277 printf -v now '%(%s)T' -1
1278 configtime=$(stat -c%Y /var/lib/exim4/config.autogenerated)
1279 if [[ -s /run/exim4/exim.pid ]]; then
1280 daemonpid=$(cat /run/exim4/exim.pid)
1281 fi
1282 for pid in $(pgrep -f '^/usr/sbin/exim4( |$)'); do
1283 # the daemonpid gets reexeced on HUP (service reloads), keeping its same old timestamp
1284 if [[ $pid == "$daemonpid" ]]; then
1285 continue
1286 fi
1287 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
1288 if (( configtime > now - piduptime )); then
1289 echo $pid
1290 fi
1291 done
1292 }
1293
1294 # exim tail but only watch lines from new pids
1295 etailnew() {
1296 local pid oldpids
1297 for pid in $(eoldpids); do
1298 oldpids+="$pid|"
1299 done
1300 if [[ $oldpids ]]; then
1301 etail | awk '$3 !~ /^\[('"${oldpids%|}"')\]$/'
1302 else
1303 etail
1304 fi
1305 }
1306 # exim watch as old pids go away
1307 ewatchold() {
1308 local configtime pid piduptime now tmpstr
1309 local -i count
1310 local -a oldpids
1311 count=0
1312 while true; do
1313 tmpstr=$(eoldpids)
1314 mapfile -t oldpids <<<"$tmpstr"
1315 if (( ! ${#oldpids[@]} )); then
1316 return
1317 fi
1318 # print the date every 20 iterations
1319 if (( ! count % 20 )); then
1320 date
1321 fi
1322 count+=1
1323 ps -f -p "${oldpids[*]}"
1324 sleep 1
1325 done
1326 }
1327
1328 eless() {
1329 less /var/log/exim4/mainlog
1330 }
1331 ccomp less eless
1332 eqcat() {
1333 exiqgrep -ir.\* -o 60 | while read -r i; do
1334 hlm exim -Mvc $i
1335 echo
1336 hlm exigrep $i /var/log/exim4/mainlog | cat ||:
1337 done
1338 }
1339 eqrmf() {
1340 # other ways to get the list of message ids:
1341 # exim -bp | awk 'NF == 4 {print $3}'
1342 # # this is slower 160ms, vs 60.
1343 # exipick -i
1344 exiqgrep -ir.\* | xargs exim -Mrm
1345 }
1346
1347 econfdevnew() {
1348 rm -rf /tmp/edev
1349 mkdir -p /tmp/edev/etc
1350 cp -ra /etc/exim4 /tmp/edev/etc
1351 cp -ra /etc/alias* /tmp/edev/etc
1352 find /tmp/edev/etc/exim4 -type f -execdir sed -i "s,/etc/,/tmp/edev/etc/,g" '{}' +
1353 econfdev
1354 }
1355 econfdev() {
1356 update-exim4.conf -d /tmp/edev/etc/exim4 -o /tmp/edev/e.conf
1357 }
1358
1359 # exim grep in
1360 # show important information about incoming mail in the exim log
1361 egrin() {
1362 sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p' <${1:-/var/log/exim4/mainlog}
1363 }
1364
1365 # 2nd line is message-id:
1366 egrinid() {
1367 sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).* id=([^ ]+) T="(.*)" from (<[^ ]+> .*$)/\1 \5\n \3\n \4/p' <${1:-/var/log/exim4/mainlog}
1368 }
1369 etailin() {
1370 tail -F /var/log/exim4/mainlog | sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p'
1371 }
1372
1373
1374
1375
1376 fa() {
1377 # find array. make an array of file names found by find into $x
1378 # argument: find arguments
1379 # return: find results in an array $x
1380 while read -rd ''; do
1381 x+=("$REPLY");
1382 done < <(find "$@" -print0);
1383 }
1384
1385 faf() { # find all files. use -L to follow symlinks
1386 find "$@" -not \( -name .svn -prune -o -name .git -prune \
1387 -o -name .hg -prune -o -name .editor-backups -prune \
1388 -o -name .undo-tree-history -prune \) -type f 2>/dev/null
1389 }
1390
1391 # usage ffconcat FILES_TO_CONCAT OUTPUT_FILE
1392 ffconcat() {
1393 local tmpf
1394 tmpf=$(mktemp)
1395 printf "file '%s'\n" "$1" >$tmpf
1396 while (( $# > 1 )); do
1397 shift
1398 printf "file '%s'\n" "$1" >>$tmpf
1399 done
1400 # https://trac.ffmpeg.org/wiki/Concatenate
1401 ffmpeg -f concat -safe 0 -i $tmpf -c copy "$1"
1402 rm $tmpf
1403 }
1404 ffremux() {
1405 local tmpf tmpd
1406 if (( $# == 0 )); then
1407 echo ffremux error expected args >&2
1408 return 1
1409 fi
1410 tmpd="$(mktemp -d)"
1411 for f; do
1412 tmpf=$tmpd/"${f##*/}"
1413 ffmpeg -i "$f" -c:v copy -c:a copy $tmpf
1414 cat $tmpf >"$f"
1415 done
1416 rm -r $tmpd
1417 }
1418
1419
1420
1421 # absolute path of file/dir without resolving symlinks.
1422 #
1423 # Most of the time, I want this where I would normally use readlink.
1424 # This is what realpath -s does in most cases, but sometimes it
1425 # actually resolves symlinks, at least when they are in /.
1426 #
1427 # Note, if run on a dir, if the final component is relative, it won't
1428 # resolve that. Use the below fpd for that.
1429 #
1430 # note: we could make a variation of this which
1431 # assigns to a variable name using eval, so that we don't have to do
1432 # x=$(fp somepath), which might save subshell overhead and look nice,
1433 # but I'm not going to bother.
1434 fp() {
1435 local initial_oldpwd initial_pwd dir base
1436 initial_oldpwd="$OLDPWD"
1437 initial_pwd="$PWD"
1438 if [[ $1 == */* ]]; then
1439 dir="${1%/*}"
1440 base="/${1##*/}"
1441 # CDPATH because having it set will cause cd to possibly print output
1442 CDPATH='' cd "$dir"
1443 printf "%s%s\n" "$PWD" "$base"
1444 CDPATH='' cd "$initial_pwd"
1445 OLDPWD="$initial_oldpwd"
1446 else
1447 printf "%s/%s\n" "$PWD" "$1"
1448 fi
1449 }
1450 # full path of directory without resolving symlinks
1451 fpd() {
1452 local initial_oldpwd initial_pwd dir
1453 initial_oldpwd="$OLDPWD"
1454 initial_pwd="$PWD"
1455 dir="$1"
1456 CDPATH='' cd "$dir"
1457 printf "%s%s\n" "$PWD" "$base"
1458 cd "$initial_pwd"
1459 OLDPWD="$initial_oldpwd"
1460 }
1461
1462
1463 # mail related
1464 frozen() {
1465 rm -rf /tmp/frozen
1466 sudo mailq |gr frozen|awk '{print $3}' | while read -r id; do
1467 sudo exim -Mvl $id
1468 echo
1469 sudo exim -Mvh $id
1470 echo
1471 sudo exim -Mvb $id
1472 echo -e '\n\n##############################\n'
1473 done | tee -a /tmp/frozen
1474 }
1475 frozenrm() {
1476 local ids=()
1477 while read -r line; do
1478 printf '%s\n' "$line"
1479 ids+=("$(printf '%s\n' "$line" |gr frozen|awk '{print $3}')")
1480 done < <(s mailq)
1481 echo "sleeping for 2 in case you change your mind"
1482 sleep 2
1483 sudo exim -Mrm "${ids[@]}"
1484 }
1485
1486 funce() {
1487 # like -e for functions. returns on error.
1488 # at the end of the function, disable with:
1489 # trap ERR
1490 trap 'echo "${BASH_COMMAND:+BASH_COMMAND=\"$BASH_COMMAND\" }
1491 ${FUNCNAME:+FUNCNAME=\"$FUNCNAME\" }${LINENO:+LINENO=\"$LINENO\" }\$?=$?"
1492 trap ERR
1493 return' ERR
1494 }
1495
1496 getdir () {
1497 local help="Usage: getdir [--help] PATH
1498 Output the directory of PATH, or just PATH if it is a directory."
1499 if [[ $1 == --help ]]; then
1500 echo "$help"
1501 return 0
1502 fi
1503 if [[ $# -ne 1 ]]; then
1504 echo "getdir error: expected 1 argument, got $#"
1505 return 1
1506 fi
1507 if [[ -d $1 ]]; then
1508 echo "$1"
1509 else
1510 local dir
1511 dir="$(dirname "$1")"
1512 if [[ -d $dir ]]; then
1513 echo "$dir"
1514 else
1515 echo "getdir error: directory does not exist"
1516 return 1
1517 fi
1518 fi
1519 }
1520
1521 git_empty_branch() { # start an empty git branch. carefull, it deletes untracked files.
1522 [[ $# == 1 ]] || { echo 'need a branch name!'; return 1;}
1523 local root
1524 root=$(gitroot) || return 1 # function to set gitroot
1525 builtin cd "$root"
1526 git symbolic-ref HEAD refs/heads/$1
1527 rm .git/index
1528 git clean -fdx
1529 }
1530
1531 # shellcheck disable=SC2120
1532 gitroot() {
1533 local help="Usage: gitroot [--help]
1534 Print the full path to the root of the current git repo
1535
1536 Handles being within a .git directory, unlike git rev-parse --show-toplevel,
1537 and works in older versions of git which did not have that."
1538 if [[ $1 == --help ]]; then
1539 echo "$help"
1540 return
1541 fi
1542 local p
1543 p=$(git rev-parse --git-dir) || { echo "error: not in a git repo" ; return 1; }
1544 [[ $p != /* ]] && p=$PWD
1545 echo "${p%%/.git}"
1546 }
1547
1548 g() {
1549
1550 local args gdb=false
1551
1552 if [[ $EMACSDIR ]]; then
1553 path-add "$EMACSDIR/lib-src" "$EMACSDIR/src"
1554 fi
1555
1556 if [[ $DISPLAY ]]; then
1557 args=-n
1558 fi
1559
1560 if (( $# == 0 )); then
1561 args+=" -c"
1562 fi
1563 # duplicate -c, but oh well
1564 if ! pgrep -u $EUID emacsclient; then
1565 if (( $# == 0 )) && type -p gdb &>/dev/null; then
1566 gdb=true
1567 else
1568 args+=" -c"
1569 fi
1570 fi
1571 if [[ $EMACSDIR ]]; then
1572
1573 # todo: we don't have to alter HOME since emacs 29+, we can set
1574 # user-emacs-directory with the flag --init-directory
1575
1576 # Alter the path here, otherwise the nfs mount gets triggered on the
1577 # first path lookup when emacs is not being used.
1578 # shellcheck disable=SC2098 disable=SC2097 # false positive
1579 PATH="$EMACSDIR/lib-src:$EMACSDIR/src:$PATH" EHOME=$HOME HOME=$EMACSDIR m emacsclient -a "" $args "$@"
1580 else
1581 if $gdb; then
1582 # due to a bug, we cant debug from the start unless we get a new gdb
1583 # https://sourceware.org/bugzilla/show_bug.cgi?id=24454
1584 # m gdb -ex="set follow-fork-mode child" -ex=r -ex=quit --args emacs --daemon
1585 m emacsclient -a "" $args "$@"
1586 sleep 1
1587 cd "/a/opt/emacs-$(distro-name)$(distro-num)"
1588 s gdb -p "$(pgrep -f 'emacs --daemon')" -ex c
1589 cd -
1590 else
1591 m emacsclient -a "" $args "$@"
1592 fi
1593 fi
1594 }
1595
1596 # g pipe. like: cmd | emacs. save cmd output to tmp file, then edit.
1597 gp() {
1598 cat &>/a/tmp/gtmp
1599 g "$@" /a/tmp/gtmp
1600 }
1601 # g log
1602 #like cmd &> tempfile; emacs tempfile
1603 #
1604 # note: a useful workflow for doing mass replace on my files:
1605 # gc rem REGEX
1606 ## remove any false positives, or manually edit them. rename files if needed.
1607 # sedi 's/REGEX/REPLACEMENT/' $(gr '^/' /a/tmp/gtmp)
1608 gl() {
1609 "$@" &> /a/tmp/gtmp
1610 g /a/tmp/gtmp
1611 }
1612 # g command substitution
1613 gc() {
1614 g $("$@")
1615 }
1616
1617 # force terminal version
1618 gn() {
1619 g -n "$@"
1620 }
1621
1622 gmacs() {
1623 # quit will prompt if the program crashes.
1624 gdb -ex=r -ex=quit --args emacs "$@"; r;
1625 }
1626
1627 gdkill() {
1628 # kill the emacs daemon
1629 pk1 emacs --daemon
1630 }
1631
1632 gr() {
1633 grep -iIP --color=auto "$@" || return $?
1634 }
1635 grr() { # grep recursive
1636 # Don't return 1 on nonmatch because this is meant to be
1637 # interactive, not in a conditional.
1638 if [[ ${#@} == 1 ]]; then
1639 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" . || [[ $? == 1 ]]
1640 else
1641 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" || [[ $? == 1 ]]
1642 fi
1643 }
1644 ccomp grep gr grr
1645
1646 rg() { grr "$@"; }
1647 ccomp grep rg
1648
1649 # recursive everything. search for files/dirs and lines. rs = easy chars to press
1650 re() {
1651 local query
1652 query="$1"
1653 find "$@" -not \( -name .svn -prune -o -name .git -prune \
1654 -o -name .hg -prune -o -name .editor-backups -prune \
1655 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto "$query"
1656 grr -m 5 "$@"
1657 }
1658
1659 # horizontal row. used to break up output
1660 hr() {
1661 local blocks
1662 # 180 is long enough.
1663 blocks=██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
1664 printf "%s\n" "$(tput setaf 5 2>/dev/null ||:)${blocks:0:${COLUMNS:-180}}$(tput sgr0 2>/dev/null||:)"
1665 }
1666 # highlight
1667 hl() {
1668 local col input_len=0
1669 for arg; do
1670 input_len=$((input_len + 1 + ${#arg}))
1671 done
1672 col=$((60 - input_len))
1673 printf "\e[1;97;41m%s" "$*"
1674 if (( col > 0 )); then
1675 # shellcheck disable=SC2046 # needed to work as intended. a better way would be like hr above.
1676 printf "\e[1;97;41m \e[0m%.0s" $(eval echo "{1..${col}}")
1677 fi
1678 echo
1679 }
1680 hlm() { hl "$*"; "$@"; }
1681
1682 hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; done }
1683
1684 # example usage:
1685 # github-release-dl restic/restic restic_ _linux_amd64.bz2
1686 # gets a url like:
1687 # https://github.com/restic/restic/releases/download/v0.16.3/restic_0.16.3_linux_amd64.bz2
1688 github-release-dl() {
1689 local github_path file_prefix file_suffix latest_prefix version redir_path
1690 github_path=$1
1691 file_prefix=$2
1692 file_suffix=$3
1693 if (( $# != 3 )); then
1694 echo "$0: error, expected 3 arguments" >&2
1695 return 1
1696 fi
1697 redir_path="https://github.com/$github_path/releases/latest/download/"
1698 latest_prefix=$(curl -s -I "$redir_path" | awk 'tolower($1) == "location:" {print $2}')
1699 # it has a trailing /r at the end. just kill any whitespace.
1700 latest_prefix="${latest_prefix//[$'\t\r\n ']}"
1701 if [[ ! $latest_prefix ]]; then
1702 echo "failed to find latest path. Tried to find case insensitive 'location:' in the curl output:"
1703 m curl -s -I "$redir_path"
1704 return 1
1705 fi
1706 version="${latest_prefix##*/}"
1707 version="${version#v}"
1708 m wget -- "$latest_prefix/$file_prefix$version$file_suffix"
1709 }
1710
1711 # examples.
1712 # go-github-install restic/restic restic_ _linux_amd64.bz2
1713 # go-github-install restic/rest-server rest-server_ _linux_amd64.tar.gz
1714
1715 # common pattern among go binaries on github
1716 go-github-install() {
1717 local tmpd targetf tmp files src
1718 tmpd=$(mktemp -d)
1719 cd $tmpd
1720 file_prefix=$2
1721 file_suffix=$3
1722 tmp="${file_prefix##*[[:alnum:]]}"
1723 targetf="${file_prefix%$tmp}"
1724 echo targetf: $targetf
1725 github-release-dl "$@"
1726 files=(./*)
1727 case $file_suffix in
1728 *.bz2)
1729 bunzip2 -- ./*
1730 ;;
1731 *.tar.gz|*.tgz)
1732 tar -vxzf ./*
1733 ;;
1734 esac
1735 rm -f -- "${files[@]}"
1736 files=(./*)
1737 # Here we detect and handle 2 cases: either we extracted a single
1738 # binary which we have to rename or a folder with a binary named
1739 # $targetf in it which is all we care about.
1740 if (( ${#files[@]} == 1 )) && [[ -f ${files[0]} ]]; then
1741 chmod +x ./*
1742 mv -- ./* /usr/local/bin/$targetf
1743 else
1744 files=(./*/$targetf)
1745 if [[ -f $targetf ]]; then
1746 src=$targetf
1747 elif [[ -f ${files[0]} ]]; then
1748 src="${files[0]}"
1749 fi
1750 chmod +x "$src"
1751 mv -- "$src" /usr/local/bin
1752 fi
1753 cd - >/dev/null
1754 rm -rf $tmpd
1755 }
1756
1757 ## 2024: I'm using gh instead of hub, but leaving this just in case.
1758 ## I tried the github cli tool (gh) and it seems easier than
1759 ## I remember hub.
1760 ##
1761 ## hub predated github's 2020 official cli tool gh.
1762 ## more info at
1763 ## https://raw.githubusercontent.com/cli/cli/trunk/docs/gh-vs-hub.md
1764 # get latest hub and run it
1765 # main command to use:
1766 # hub pull-request --no-edit
1767 # --no-edit means to use the first commit\'s message as the pull request message.
1768 # If that fails, try doing
1769 # hub pull-request --no-edit -b UPSTREAM_OWNER:branch
1770 # where branch is usually master. it does the pr against your current branch.
1771 #
1772 # On first use, you input username/pass and it gets an oath token so you dont have to repeat
1773 # it\'s at ~/.config/hub
1774 hub() {
1775 local up uptar updir p re
1776 # example https://github.com/github/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz
1777 up=$(wget -q -O- https://api.github.com/repos/github/hub/releases/latest | jq -r .assets[].browser_download_url | grep linux-amd64)
1778 re='[[:space:]]'
1779 if [[ ! $up || $up =~ $re ]]; then
1780 echo "failed to get good update url. got: $up"
1781 fi
1782 uptar=${up##*/}
1783 updir=${uptar%.tgz}
1784 if [[ ! -e /a/opt/$updir ]]; then
1785 rm -rf /a/opt/hub-linux-amd64*
1786 wget -P /a/opt $up
1787 tar -C /a/opt -zxf /a/opt/$uptar
1788 rm -f /a/opt/$uptar
1789 fi
1790 if ! which hub &>/dev/null; then
1791 sudo /a/opt/$updir/install
1792 fi
1793
1794 # save token across computers
1795 if [[ ! -L ~/.config/hub ]]; then
1796 if [[ -e ~/.config/hub ]]; then
1797 mv ~/.config/hub /p/c/subdir_files/.config/
1798 fi
1799 if [[ -e /p/c/subdir_files/.config/hub ]]; then
1800 conflink
1801 fi
1802 fi
1803 command hub "$@"
1804 }
1805
1806 i() { git "$@"; }
1807 ccomp git i
1808
1809 # git status:
1810 # cvs -qn update
1811
1812 # git checkout FILE
1813 # cvs update -C FILE
1814
1815 # git pull
1816 # cvs up[date]
1817
1818 # potentially useful command translation
1819 # https://fling.seas.upenn.edu/~giesen/dynamic/wordpress/equivalent-commands-for-git-svn-and-cvs/
1820
1821 # importing cvs repo into git using git-cvs package:
1822 # /f/www $ /usr/lib/git-core/git-cvsimport -C /f/www-git
1823
1824 ic() {
1825 # fast commit all
1826 git commit -am "$*"
1827 }
1828
1829 ipp() {
1830 git pull
1831 git push
1832 }
1833
1834 ifn() {
1835 local glob
1836 glob="$1"
1837 shift
1838 find -L "$@" -not \( -name .svn -prune -o -name .git -prune \
1839 -o -name .hg -prune -o -name .editor-backups -prune \
1840 -o -name .undo-tree-history -prune \) -iname "*$glob*" 2>/dev/null
1841 }
1842
1843 ifh() {
1844 # insensitive find here. args are combined into the search string.
1845 # -L = follow symlinks
1846 find -L . -not \( -name .svn -prune -o -name .git -prune \
1847 -o -name .hg -prune -o -name .editor-backups -prune \
1848 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
1849 }
1850
1851 ifd() {
1852 # insensitive find directory
1853 find -L . -type d -not \( -name .svn -prune -o -name .git -prune \
1854 -o -name .hg -prune -o -name .editor-backups -prune \
1855 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
1856 }
1857
1858
1859 ipdrop() {
1860 sudo iptables -A INPUT -s $1 -j DROP
1861 }
1862
1863
1864 istext() {
1865 grep -Il "" "$@" &>/dev/null
1866 }
1867
1868 pst() {
1869 pstree -apnA
1870 }
1871
1872 # journalctl with times in the format the --since= and --until= options accept
1873 jrt() { journalctl -e -n100000 -o short-full "$@"; }
1874 jr() { journalctl -e -n100000 "$@" ; }
1875 jrf() { journalctl -n1000 -f "$@" ; }
1876 jru() {
1877 # the invocation id is "assigned each time the unit changes from an inactive
1878 # state into an activating or active state" man systemd.exec
1879 journalctl -e --no-tail -u exim4 _SYSTEMD_INVOCATION_ID="$(systemctl show -p InvocationID --value $1)"
1880 }
1881 ccomp journalctl jr jrf jru
1882
1883
1884
1885 l() {
1886 if [[ $PWD == /[iap] ]]; then
1887 command ls -A --color=auto -I lost+found "$@"
1888 else
1889 command ls -A --color=auto "$@"
1890 fi
1891 }
1892
1893 lcn() { locate -i "*$**"; }
1894
1895 lg() { LC_COLLATE=C.UTF-8 ll --group-directories-first "$@"; }
1896
1897 lt() { ll -tr "$@"; }
1898
1899 lld() { ll -d "$@"; }
1900
1901 ccomp ls l lg lt lld ll
1902
1903 # low recursively
1904 lowr() {
1905 local f dirs i a
1906 local -a all
1907 for dirs in false true; do
1908 for f; do
1909 if [[ -d $f ]]; then
1910 all=("$f"/**)
1911 # reverse the order to rename the nested dirs first.
1912 # note: 0 element is the dir itself
1913 for ((i=${#all[@]}-1; i>=1; i--)); do
1914 a="${all[i]}"
1915 if $dirs && [[ -d $a ]]; then
1916 # e dirs low "$a" # debug
1917 low "$a"
1918 elif ! $dirs && [[ ! -d $a && -e $a ]]; then
1919 # debug
1920 # e not dirs low "$a" # debug
1921 low "$a"
1922 fi
1923 done
1924 fi
1925 # just rename all the top level args on the second pass
1926 if $dirs; then
1927 # e final dirs low "$f" # debug
1928 low "$f"
1929 fi
1930 done
1931 done
1932 }
1933
1934 low() { # make filenames lowercase, remove bad chars
1935 local arg new dir f
1936 for arg; do
1937 arg="${arg%%+(/)}" # remove trailing slashes. assumes we have extglob on.
1938 dir="${arg%/*}"
1939 if (( ${#dir} == ${#arg} )); then
1940 dir=.
1941 fi
1942 f="${arg##*/}"
1943 new="${f,,}" # downcase
1944 # shellcheck disable=SC2031 # seems like a shellcheck bug
1945 new="${new//[^a-zA-Z0-9._-]/_}" # sub bad chars
1946 new="${new#"${new%%[[:alnum:]]*}"}" # remove leading/trailing non-alnum
1947 new="${new%"${new##*[[:alnum:]]}"}"
1948 # remove bad underscores, like __ and _._
1949 new=$(echo $new | sed -r 's/__+/_/g;s/_+([.-])|([.-])_+/\1/g')
1950 safe_rename "$dir/$f" "$dir/$new" || return 1
1951 done
1952 return 0
1953 }
1954
1955 lower() { # make first letter of filenames lowercase.
1956 local x
1957 for x in "$@"; do
1958 if [[ ${x::1} == [A-Z] ]]; then
1959 y=$(tr '[:upper:]' '[:lower:]' <<<"${x::1}")"${x:1}"
1960 safe_rename "$x" "$y" || return 1
1961 fi
1962 done
1963 }
1964
1965
1966 k() { # history search
1967 grep -iP --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | tail -n 80 || [[ $? == 1 ]];
1968 }
1969 ks() { # history search with context
1970 # args are an extended regex used by sed
1971 history | sed -nr "h;s/^\s*(\S+\s+){4}//;/$*/{g;p}" | tail -n 80 || [[ $? == 1 ]];
1972 }
1973 ksu() { # history search unique
1974 grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | uniq || [[ $? == 1 ]];
1975 }
1976
1977 # todo: id like to do maybe a daily or hourly cronjob to
1978 # check that my history file size is increasing. Ive had it
1979 # inexplicably truncated in the past.
1980 histrm() {
1981 history -n
1982 HISTTIMEFORMAT='' history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/'
1983 read -r -p "press anything but contrl-c to delete"
1984 for entry in $(HISTTIMEFORMAT='' history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/ { print a }' | tac); do
1985 history -d $entry
1986 done
1987 history -w
1988 }
1989
1990 # history without the date
1991 histplain() {
1992 history "$@" | cut -d' ' -f 7-
1993 }
1994
1995 ccomp grep k ks ksu histrm
1996
1997
1998 make-targets() {
1999 # show make targets, via http://stackoverflow.com/questions/3063507/list-goals-targets-in-gnu-make
2000 make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'
2001 }
2002
2003 mkc() {
2004 mkdir "$1"
2005 c "$1"
2006 }
2007 ccomp mkdir mkc
2008
2009 mkct() {
2010 mkc "$(mktemp -d)"
2011 }
2012 # mkdir the last arg, cp the rest into it
2013 mkcp() {
2014 mkdir -p "${@: -1}"
2015 cp "${@:1:$#-1}" "${@: -1}"
2016 }
2017 mkmv() {
2018 mkdir -p "${@: -1}"
2019 mv "${@:1:$#-1}" "${@: -1}"
2020 }
2021
2022 mkt() { # mkdir and touch file
2023 local path="$1"
2024 mkdir -p "$(dirname "$path")"
2025 touch "$path"
2026 }
2027
2028 # shellcheck disable=SC2032
2029 mkdir() { command mkdir -p "$@"; }
2030
2031 nags() {
2032 # https://github.com/HenriWahl/Nagstamon/issues/357
2033 if ! pgrep -f /usr/bin/dunst >/dev/null; then
2034 /usr/bin/dunst &
2035 fi
2036 /usr/bin/nagstamon &
2037 }
2038
2039 # profanity screen
2040 profsrc() {
2041 screen -RD -S profanity
2042 }
2043
2044 # i dont want to wait for konsole to exit...
2045 prof() {
2046 command prof &>/dev/null &
2047 }
2048 # self chat
2049 sc() {
2050 while read -r l; do
2051 printf '\033[1A\033[K'; printf "%s\n" "$l"| ts "%F %T" | tee -a /p/self-chat.log
2052 done
2053 }
2054
2055 nmt() {
2056 # cant use s because sudo -i doesnt work for passwordless sudo command
2057 case $EUID in
2058 0)
2059 sudo nmtui-connect "$@"
2060 ;;
2061 *)
2062 nmtui-connect "$@"
2063 ;;
2064 esac
2065 }
2066
2067
2068 ngset() {
2069 if shopt nullglob >/dev/null; then
2070 ngreset=false
2071 else
2072 shopt -s nullglob
2073 ngreset=true
2074 fi
2075 }
2076 ngreset() {
2077 if $ngreset; then
2078 shopt -u nullglob
2079 fi
2080 }
2081
2082 nopanic() {
2083 # shellcheck disable=SC2024
2084 ngset
2085 for f in /var/log/exim4/paniclog /var/log/exim4/*panic; do
2086 base=${f##*/}
2087 if [[ -s $f ]]; then
2088 echo ================== $f =============
2089 s tee -a /var/log/exim4/$base-archive <$f
2090 s truncate -s0 $f
2091 fi
2092 done
2093 ngreset
2094 }
2095
2096
2097 ping() { command ping -O "$@"; }
2098 p8() { ping "$@" 8.8.8.8; }
2099 p6() { ping6 "$@" 2001:4860:4860::8888; }
2100
2101 pkx() { # package extract
2102 local pkg cached tmp f
2103 c "$(mktemp -d)"
2104 pkg=$1
2105 # shellcheck disable=SC2012
2106 cached=$(ls -t /var/cache/apt/archives/${pkg}_* | tail -n1 2>/dev/null) ||:
2107 if [[ $cached ]]; then
2108 m cp $cached .
2109 else
2110 m aptitude download $pkg || return 1
2111 fi
2112 tmp=(*); f=${tmp[0]} # only 1 expected
2113 m ex $f
2114 m rm -f $f
2115 }
2116
2117 # pgrep and kill
2118 pk1() {
2119 local tmpf
2120 local -a pids
2121 tmpf=$(pgrep -f "$*")
2122 mapfile -t pids <<<"$tmpf"
2123 case ${#pids[@]} in
2124 1)
2125 # shellcheck disable=SC2128
2126 {
2127 ps -F ${pids[0]}
2128 m kill ${pids[0]}
2129 }
2130 ;;
2131 0) echo "no pid found" ;;
2132 *)
2133 ps -F ${pids[@]}
2134 ;;
2135 esac
2136 }
2137
2138 psg () {
2139 local x y help
2140 help="Usage: psg [--help] GREP_ARGS
2141 grep ps and output in a nice format"
2142 if [[ $1 == --help ]]; then
2143 echo "$help"
2144 return
2145 fi
2146 x=$(ps -eF)
2147 # final grep is because some commands tend to have a lot of trailing spaces
2148 y=$(echo "$x" | grep -iP "$@" | grep -o '.*[^ ]') ||:
2149 if [[ $y ]]; then
2150 echo "$x" | head -n 1 || [[ $? == 141 ]]
2151 echo "$y"
2152 fi
2153 }
2154
2155 pubip() { curl -4s https://icanhazip.com; }
2156 pubip6() { curl -6s https://icanhazip.com; }
2157 whatismyip() { pubip; }
2158
2159
2160 q() { # start / launch a program in the backround and redir output to null
2161 "$@" &> /dev/null &
2162 }
2163
2164 # shellcheck disable=SC2120
2165 r() {
2166 if [[ $HISTFILE ]]; then
2167 history -a # save history
2168 fi
2169 trap ERR # this avoids a segfault
2170 exit ${1:0}
2171 # i had this redir, not sure why
2172 # exit "$@" 2>/dev/null
2173 }
2174
2175 # scp is insecure and deprecated.
2176 scp() {
2177 rsync -Pt --inplace "$@"
2178 }
2179 ccomp rsync scp
2180
2181 randport() {
2182 # available high ports are 1024-65535,
2183 # but lets skip things that are more likely to be in use
2184 python3 <<'EOF'
2185 import secrets
2186 print(secrets.SystemRandom().randrange(10002,65500))
2187 EOF
2188 }
2189
2190 # reapply bashrc
2191 reb() {
2192 # shellcheck disable=SC1090 # expected to not follow
2193 source ~/.bashrc
2194 }
2195
2196 rl() {
2197 readlink -f "$@"
2198 }
2199 ccomp readlink rl
2200
2201 rsd() {
2202 # rsync, root is required to keep permissions right.
2203 # rsync --archive --human-readable --verbose --itemize-changes --checksum \(-ahvic\) \
2204 # --no-times --delete
2205 # basically, make an exact copy, use checksums instead of file times to be more accurate
2206 rsync -ahvic --delete "$@"
2207 }
2208 rsa() {
2209 # like rlu, but dont delete files on the target end which
2210 # do not exist on the original end.
2211 rsync -ahvic "$@"
2212 }
2213 rst() {
2214 # rl without preserving modification time.
2215 rsync -ahvic --delete --no-t "$@"
2216 }
2217 # [RSYNC_OPTS] HOST PATH
2218 rsu() {
2219 # eg. rsu -opts frodo /testpath
2220 # relative paths will expanded with readlink -f.
2221 opts=("${@:1:$#-2}") # 1 to last -2
2222 path="${*:$#}" # last
2223 host="${*:$#-1:1}" # last -1
2224 if [[ $path == .* ]]; then
2225 path=$(readlink -f $path)
2226 fi
2227 m rsync -ahvi --relative --no-implied-dirs "${opts[@]}" "$path" "root@$host:/";
2228 }
2229 ccomp rsync rsd rsa rst rsu
2230
2231 # find programs listening on a port
2232 ssp() {
2233 local port=$1
2234 # to figure out these args, i had to look at the man page from git version, as of 2022-04.
2235 s ss -lpn state listening sport = $port
2236 }
2237
2238 resolvcat() {
2239 local f
2240 if [[ $(systemctl is-active nscd ||:) != inactive ]]; then
2241 m s nscd -i hosts
2242 fi
2243 f=/etc/resolv.conf
2244 echo $f:; ccat $f
2245 hr; s ss -lpn sport = 53
2246 if systemctl is-enabled dnsmasq &>/dev/null || [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
2247 # this will fail is dnsmasq is failed
2248 hr; m ser status dnsmasq | cat || :
2249 f=/etc/dnsmasq.conf
2250 hr; echo $f:; ccat $f
2251 hr; m grr '^ *(servers-file|server) *=|^ *no-resolv *$' /etc/dnsmasq.conf /etc/dnsmasq.d
2252 f=/etc/dnsmasq-servers.conf
2253 hr; echo $f:; ccat $f
2254 fi
2255 hr
2256 echo /etc/nsswitch.conf:
2257 grep '^ *hosts:' /etc/nsswitch.conf
2258 if systemctl is-enabled systemd-resolved &>/dev/null || [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then
2259 hr; m ser status systemd-resolved | cat || :
2260 hr; m resolvectl status | cat
2261 fi
2262
2263 }
2264 rcat() {
2265 resolvcat | less
2266 }
2267 reresolv() {
2268 if [[ $(systemctl is-active nscd ||:) != inactive ]]; then
2269 m ser stop nscd
2270 sleep .5
2271 m ser start nscd
2272 m sudo nscd -i hosts
2273 fi
2274 if [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
2275 m sudo systemctl restart dnsmasq
2276 fi
2277 if [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then
2278 m sudo systemctl restart systemd-resolved
2279 fi
2280 if type -P resolvectl &>/dev/null; then
2281 resolvectl flush-caches
2282 fi
2283 }
2284
2285 # add annoyingly long argument which should be the default
2286 sedi() {
2287 sed -i --follow-symlinks "$@"
2288 }
2289
2290
2291
2292 # todo: test variable assignment with newlines here.
2293 # https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash
2294
2295 # beware that it only works on the assumption that any special
2296 # characters in the input string are intended to be escaped, not to work
2297 # as special chacters.
2298 shellescape() {
2299 LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'
2300 }
2301
2302 rmstrips() {
2303 ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less
2304 }
2305
2306 urun () {
2307 umask $1
2308 shift
2309 "$@"
2310 }
2311 sudo () {
2312 command sudo "$@" || return $?
2313 DID_SUDO=true
2314 }
2315 s() {
2316 # background
2317 # I use a function because otherwise we cant use in a script,
2318 # cant assign to variable.
2319 #
2320 # note: gksudo is recommended for X apps because it does not set the
2321 # home directory to the same, and thus apps writing to ~ fuck things up
2322 # with root owned files.
2323 #
2324 if [[ $EUID != 0 || $1 == -* ]]; then
2325 # shellcheck disable=SC2034
2326 SUDOD="$PWD" command sudo -i "$@"
2327 DID_SUDO=true
2328 else
2329 "$@"
2330 fi
2331 }
2332 sb() { # sudo bash -c
2333 # use sb instead of s is for sudo redirections,
2334 # eg. sb 'echo "ok fine" > /etc/file'
2335 # shellcheck disable=SC2034
2336 local SUDOD="$PWD"
2337 sudo -i bash -c "$@"
2338 }
2339 # secret sudo
2340 se() { s urun 0077 "$@"; }
2341 ccomp sudo s sb se
2342
2343 safe_rename() { # warn and dont rename if file exists.
2344 # mv -n exists, but it\'s silent
2345 if [[ $# != 2 ]]; then
2346 echo safe_rename error: $# args, need 2 >&2
2347 return 1
2348 fi
2349 if [[ $1 != "$2" ]]; then # yes, we want to silently ignore this
2350 if [[ -e $2 || -L $2 ]]; then
2351 echo "Cannot rename $1 to $2 as it already exists."
2352 else
2353 mv -vi "$1" "$2"
2354 fi
2355 fi
2356 }
2357
2358
2359 sd() {
2360 sudo dd status=none of="$1"
2361 }
2362
2363 ser() {
2364 if type -p systemctl &>/dev/null; then
2365 s systemctl "$@"
2366 else
2367 if (( $# >= 3 )); then
2368 echo iank: ser expected 2 or less arguments
2369 return 1
2370 fi
2371 s service $2 $1
2372 fi
2373 }
2374 serstat() {
2375 systemctl -n 40 status "$@"
2376 }
2377
2378 seru() { systemctl --user "$@"; }
2379 # like restart, but do nothing if its not already started
2380 srestart() {
2381 local service=$1
2382 if [[ $(s systemctl --no-pager show -p ActiveState $service ) == ActiveState=active ]]; then
2383 systemctl restart $service
2384 fi
2385 }
2386
2387 setini() { # set a value in a .ini style file
2388 key="$1" value="$2" section="$3" file="$4"
2389 if [[ -s $file ]]; then
2390 sed -ri -f - "$file" <<EOF
2391 # remove existing keys
2392 / *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d}
2393 # add key
2394 /^\s*\[$section\]/a $key=$value
2395 # from section to eof, do nothing
2396 /^\s*\[$section\]/,\$b
2397 # on the last line, if we haven't found section yet, add section and key
2398 \$a [$section]\\
2399 $key=$value
2400 EOF
2401 else
2402 cat >"$file" <<EOF
2403 [$section]
2404 $key=$value
2405 EOF
2406 fi
2407 }
2408
2409 sgo() { # service go
2410 service=$1
2411 ser restart $service || return 1
2412 if type -p systemctl &>/dev/null; then
2413 ser enable $service
2414 fi
2415 }
2416 soff () {
2417 for service; do
2418 # ignore services that dont exist
2419 if systemctl cat $service &>/dev/null; then
2420 ser stop $service;
2421 ser disable $service
2422 fi
2423 done
2424 }
2425
2426 sgu() {
2427 systemctl list-unit-files | rg "$@"
2428 }
2429
2430
2431 sk() {
2432 # disable a warning with:
2433 # shellcheck disable=SC2206 # reasoning
2434
2435 # see bash-template/style-guide.md for justifications
2436
2437 local quotes others
2438 quotes=2048,2068,2086,2206,2254
2439 others=2029,2032,2033,2054,2164,
2440 shellcheck -W 999 -x -e $quotes,$others "$@" || return $?
2441 }
2442 # sk with quotes. For checking scripts that we expect to take untrusted
2443 # input in order to verify we quoted vars.
2444 skq() {
2445 local others
2446 others=2029,2033,2054,2164
2447 shellcheck -W 999 -x -e $others "$@" || return $?
2448 }
2449
2450 skgit() {
2451 local f
2452 for f in $(i s | awk '$1 == "modified:" {print $2}'); do
2453 if istext "$f" && [[ $(head -n1 "$f" 2>/dev/null) == '#!/bin/bash'* ]]; then
2454 sk $f ||:
2455 fi
2456 done
2457 }
2458
2459 # sl: ssh, but firsh rsync our bashrc and related files to a special
2460 # directory on the remote host if needed.
2461
2462 # Some environment variables and files need to be setup for this to work
2463 # (mine are set at the beginning of this file)
2464
2465 # SL_FILES_DIR: Environment variable. Path to folder which should at
2466 # least have a .bashrc file or symlink. This dir will be rsynced to ~ on
2467 # remote hosts (top level symlinks are resolved) unless the host already
2468 # has a $SL_FILES_DIR/.bashrc. In that case, we assume it is a host you
2469 # control and sync files to separately and already has the ~/.bashrc you
2470 # want. The remote bash will also take its .inputrc config from this
2471 # folder (default of not existing is fine). Mine looks like this:
2472 # https://iankelling.org/git/?p=distro-setup;a=tree;f=sl/.iank
2473
2474 # SL_INFO_DIR: Environment variable. This folder stores info about what
2475 # we detected on the remote system and when we last synced. It will be created
2476 # if it does not exist. Sometimes you may want to forget about a
2477 # remote system, you can use sl --rsync, or the function for that slr
2478 # below.
2479
2480 # SL_TEST_CMD: Env var. Meant to be used to vary the files synced
2481 # depending on the remote host. Run this string on the remote host the
2482 # first time sl is run (or if we run slr). The result is passed to
2483 # SL_TEST_HOOK. For example,
2484 # export SL_TEST_CMD=". /etc/os-release ; echo \${VERSION//[^a-zA-Z0-9]/}"
2485
2486 # SL_TEST_HOOK: Env var. It is run as $SL_TEST_HOOK. This can set
2487 # $SL_FILES_DIR to vary the files synced.
2488
2489 # SL_RSYNC_ARGS: Env var. String of arguments passed to rsync. For
2490 # example to exclude files within a directory. Note, excluded
2491 # files wont be deleted on rsync, you can add --delete-excluded
2492 # to the rsync command if that is desired.
2493
2494 # SL_SSH_ARGS: Env var. Default arguments passed to ssh.
2495
2496 # For when ~/.bashrc is already customized on the remote server, you
2497 # might find it problematic that ~/.bashrc is sourced for ALL ssh
2498 # commands, even in scripts. This paragraph is all about that. bash
2499 # scripts dont source ~/.bashrc, but call ssh in scripts and you get
2500 # ~/.bashrc. You dont want this. .bashrc is meant for interactive shells
2501 # and if you customize it, probably has bugs from time to time. This is
2502 # bad. Here's how I fix it. I have a special condition to "return" in my
2503 # .bashrc for noninteractive ssh shells (copy that code). Then use this
2504 # function or similar that passes LC_USEBASHRC=t when sshing and I want
2505 # my bashrc. Also, I don't keep most of my bashrc in .bashrc, i source a
2506 # separate file because even if I return early on, the whole file gets
2507 # parsed which can fail if there is a syntax error.
2508 sl() {
2509 # Background on LC_USEBASHRC var (no need to read if you just want to
2510 # use this function): env variables sent across ssh are strictly
2511 # limited, but we get LC_* at least in debian based machines, so we
2512 # just make that * be something no normal program would use. Note, on
2513 # hosts that dont allow LC_* I start an inner shell with LC_USEBASHRC
2514 # set, and the inner shell also allows running a nondefault
2515 # .bashrc. This means the outer shell still ran the default .bashrc,
2516 # but that is the best we can do.
2517
2518 local now args remote dorsync haveinfo tmpa sshinfo tmp tmp2 type info_sec force_rsync \
2519 sync_dirname testcmd extra_info testbool files_sec sl_test_cmd sl_test_hook
2520 declare -a args tmpa
2521
2522 args=($SL_SSH_ARGS)
2523
2524 # ssh [-1246Antivivisectionist] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
2525 # [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L address]
2526 # [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option]
2527 # [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] [user@]hostname
2528 # [command]
2529
2530 # ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
2531 # [-D [bind_address:]port] [-E log_file] [-e escape_char]
2532 # [-F configfile] [-I pkcs11] [-i identity_file]
2533 # [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]
2534 # [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]
2535 # [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
2536
2537 force_rsync=false
2538 if [[ $1 == --rsync ]]; then
2539 force_rsync=true
2540 shift
2541 fi
2542 # shellcheck disable=SC2153 # intentional
2543 sl_test_cmd=$SL_TEST_CMD
2544 # shellcheck disable=SC2153 # intentional
2545 sl_test_hook=$SL_TEST_HOOK
2546 # shellcheck disable=SC2153 # intentional
2547 sl_rsync_args=$SL_RSYNC_ARGS
2548 while [[ $1 ]]; do
2549 case "$1" in
2550 --rsync)
2551 force_rsync=true
2552 ;;
2553 --sl-test-cmd)
2554 sl_test_cmd="$2"
2555 shift
2556 ;;
2557 --sl-test-hook)
2558 sl_test_hook="$2"
2559 shift
2560 ;;
2561 --sl-rsync-args)
2562 sl_rsync_args="$2"
2563 shift
2564 ;;
2565 *)
2566 break
2567 ;;
2568 esac
2569 shift
2570 done
2571
2572 while [[ $1 ]]; do
2573 case "$1" in
2574 # note we dont support things like -4oOption
2575 -[46AaCfGgKkMNnqsTtVvXxYy]*)
2576 args+=("$1"); shift
2577 ;;
2578 -[bcDEeFIiJLlmOopQRSWw]*)
2579 # -oOption etc is valid
2580 if (( ${#1} >= 3 )); then
2581 args+=("$1"); shift
2582 else
2583 args+=("$1" "$2"); shift 2
2584 fi
2585 ;;
2586 *)
2587 break
2588 ;;
2589 esac
2590 done
2591 remote="$1"
2592 if [[ ! $remote ]]; then
2593 echo $0: error hostname required >&2
2594 return 1
2595 fi
2596 shift
2597
2598 if [[ ! $SL_INFO_DIR ]]; then
2599 echo 'error: missing SL_INFO_DIR env var' >&2
2600 return 1
2601 fi
2602
2603 dorsync=false
2604 haveinfo=false
2605 tmpa=($SL_INFO_DIR/???????????"$remote")
2606 sshinfo=${tmpa[0]}
2607 if [[ -e $sshinfo ]]; then
2608 if $force_rsync; then
2609 rm -f $sshinfo
2610 else
2611 haveinfo=true
2612 fi
2613 fi
2614 if $haveinfo; then
2615 tmp=${sshinfo[0]##*/}
2616 tmp2=${tmp::11}
2617 type=${tmp2: -1}
2618 extra_info=$(cat $sshinfo)
2619 else
2620 # we test for string to know ssh succeeded
2621 testbool="test -e $SL_FILES_DIR/.bashrc -a -L .bashrc -a -v LC_USEBASHRC"
2622 testcmd="if $testbool; then printf y; else printf n; fi"
2623 if ! tmp=$(LC_USEBASHRC=y command ssh "${args[@]}" "$remote" "$testcmd; $sl_test_cmd"); then
2624 echo failed sl test. doing plain ssh -v
2625 command ssh -v "${args[@]}" "$remote"
2626 fi
2627 if [[ $tmp == y* ]]; then
2628 type=a
2629 else
2630 dorsync=true
2631 type=b
2632 fi
2633 extra_info="${tmp:1}"
2634 fi
2635 if [[ $sl_test_hook ]]; then
2636 RSYNC_RSH="ssh ${args[*]}" $sl_test_hook "$extra_info" "$remote"
2637 fi
2638
2639 if $haveinfo && [[ $type == b ]]; then
2640 info_sec=${tmp::10}
2641 read -r files_sec _ < <(find -L $SL_FILES_DIR -printf "%T@ %p\n" | sort -nr || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]] )
2642 files_sec=${files_sec%%.*}
2643 if (( files_sec > info_sec )); then
2644 dorsync=true
2645 rm -f $sshinfo
2646 fi
2647 fi
2648
2649 sync_dirname=${SL_FILES_DIR##*/}
2650
2651 if [[ ! $SL_FILES_DIR ]]; then
2652 echo 'error: missing SL_FILES_DIR env var' >&2
2653 return 1
2654 fi
2655
2656 if $dorsync; then
2657 RSYNC_RSH="ssh ${args[*]}" m rsync -rptL --delete $sl_rsync_args $SL_FILES_DIR "$remote":
2658 fi
2659 if $dorsync || ! $haveinfo; then
2660 sshinfo=$SL_INFO_DIR/$EPOCHSECONDS$type"$remote"
2661 [[ -e $SL_INFO_DIR ]] || mkdir -p $SL_INFO_DIR
2662 printf "%s\n" "$extra_info" >$sshinfo
2663 chmod 666 $sshinfo
2664 fi
2665 if [[ $type == b ]]; then
2666 if (( ${#@} )); then
2667 # Theres a couple ways to pass arguments, im not sure whats best,
2668 # but relying on bash 4.4+ escape quoting seems most reliable.
2669 command ssh "${args[@]}" "$remote" \
2670 LC_USEBASHRC=t bash -c '.\ '$sync_dirname'/.bashrc\;"\"\$@\""' bash ${@@Q}
2671 elif [[ ! -t 0 ]]; then
2672 # This case is when commands are being piped to ssh.
2673 # Normally, no bashrc gets sourced.
2674 # But, since we are doing all this, lets source it because we can.
2675 cat <(echo . $sync_dirname/.bashrc) - | command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
2676 else
2677 command ssh -t "${args[@]}" "$remote" LC_USEBASHRC=t INPUTRC=$sync_dirname/.inputrc bash --rcfile $sync_dirname/.bashrc
2678 fi
2679 else
2680 if [[ -t 0 ]]; then
2681 LC_USEBASHRC=t command ssh "${args[@]}" "$remote" ${@@Q}
2682 else
2683 command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
2684 fi
2685 fi
2686 # this function inspired from https://github.com/Russell91/sshrc
2687 }
2688
2689 slr() {
2690 sl --rsync "$@"
2691 }
2692 sss() { # ssh solo
2693 sl -oControlMaster=no -oControlPath=/ "$@"
2694 }
2695 # kill off old shared socket then ssh
2696 ssk() {
2697 m ssh -O exit "$@" || [[ $? == 255 ]]
2698 m sl "$@"
2699 }
2700 ccomp ssh sl slr sss ssk
2701 # plain ssh
2702 ssh() {
2703 if [[ $TERM == alacritty || $TERM == xterm-kitty ]]; then
2704 TERM=xterm-256color LC_USEBASHRC=t command ssh "$@"
2705 else
2706 LC_USEBASHRC=t command ssh "$@"
2707 fi
2708 }
2709
2710
2711 slog() {
2712 # log with script. timing is $1.t and script is $1.s
2713 # -l to save to ~/typescripts/
2714 # -t to add a timestamp to the filenames
2715 local logdir do_stamp arg_base
2716 (( $# >= 1 )) || { echo "arguments wrong"; return 1; }
2717 logdir="/a/dt/"
2718 do_stamp=false
2719 while getopts "lt" option
2720 do
2721 case $option in
2722 l) arg_base=$logdir ;;
2723 t) do_stamp=true ;;
2724 *)
2725 echo error: bad option
2726 return 1
2727 ;;
2728 esac
2729 done
2730 shift $((OPTIND - 1))
2731 arg_base+=$1
2732 [[ -e $logdir ]] || mkdir -p $logdir
2733 $do_stamp && arg_base+=$(date +%F.%T%z)
2734 script -t $arg_base.s 2> $arg_base.t
2735 }
2736 splay() { # script replay
2737 #logRoot="$HOME/typescripts/"
2738 #scriptreplay "$logRoot$1.t" "$logRoot$1.s"
2739 scriptreplay "$1.t" "$1.s"
2740 }
2741
2742 sr() {
2743 # sudo redo. be aware, this command may not work right on strange distros or earlier software
2744 if [[ $# == 0 ]]; then
2745 sudo -E bash -c -l "$(history -p '!!')"
2746 else
2747 echo this command redos last history item. no argument is accepted
2748 fi
2749 }
2750
2751 srm () {
2752 # with -ll, less secure but faster.
2753 command srm -ll "$@"
2754 }
2755
2756 srun() {
2757 scp $2 $1:/tmp
2758 ssh $1 "/tmp/${2##*/}" "$(printf "%q\n" "${@:2}")"
2759 }
2760
2761
2762 swap() {
2763 local tmp
2764 tmp=$(mktemp)
2765 mv $1 $tmp
2766 mv $2 $1
2767 mv $tmp $2
2768 }
2769
2770 tclock() { # terminal clock
2771 local x
2772 clear
2773 date +%l:%_M
2774 len=60
2775 # this goes to full width
2776 #len=${1:-$((COLUMNS -7))}
2777 x=1
2778 while true; do
2779 if (( x == len )); then
2780 end=true
2781 d="$(date +%l:%_M) "
2782 else
2783 end=false
2784 d=$(date +%l:%M:%_S)
2785 fi
2786 echo -en "\r"
2787 echo -n "$d"
2788 for ((i=0; i<x; i++)); do
2789 if (( i % 6 )); then
2790 echo -n _
2791 else
2792 echo -n .
2793 fi
2794 done
2795 if $end; then
2796 echo
2797 x=1
2798 else
2799 x=$((x+1))
2800 fi
2801 sleep 5
2802 done
2803 }
2804
2805
2806 te() {
2807 # test existence / exists
2808 local ret=0
2809 for x in "$@"; do
2810 [[ -e "$x" || -L "$x" ]] || ret=1
2811 done
2812 return $ret
2813 }
2814
2815 psoff() {
2816 # normally, i would just execute these commands in the function.
2817 # however, DEBUG is not inherited, so we need to run it outside a function.
2818 # And we want to run set -x afterwards to avoid spam, so we cram everything
2819 # in here, and then it will run after this function is done.
2820 # # set as array to satisfy shellcheck, but it is equivalent to setting it as non-array
2821 PROMPT_COMMAND=('trap DEBUG; unset PROMPT_COMMAND; PS1="\w \$ "')
2822 }
2823 pson() {
2824 PROMPT_COMMAND=(prompt-command)
2825 if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
2826 trap 'settitle "$BASH_COMMAND"' DEBUG
2827 fi
2828 }
2829
2830 # prometheus node curl
2831 pnodecurl() {
2832 local host
2833 host=${1:-127.0.0.1}
2834 s curl --cert-type PEM --cert /etc/prometheus/ssl/prometheus_cert.pem --key /etc/prometheus/ssl/prometheus_key.pem --cacert /etc/prometheus/ssl/prom_node_cert.pem --resolve prom_node:9100:$host -v https://prom_node:9100/metrics
2835 }
2836
2837 tx() { # toggle set -x, and the prompt so it doesnt spam
2838 if [[ $- == *x* ]]; then
2839 set +x
2840 pson
2841 else
2842 psoff
2843 fi
2844 }
2845
2846 psnetns() {
2847 # show all processes in the network namespace $1.
2848 # blank entries appear to be subprocesses/threads
2849 local x netns
2850 netns=$1
2851 ps -w | head -n 1
2852 sudo find -L /proc/[1-9]*/task/*/ns/net -samefile /run/netns/$netns | cut -d/ -f5 | \
2853 while read -r l; do
2854 x=$(ps -w --no-headers -p $l);
2855 if [[ $x ]]; then echo "$x"; else echo $l; fi;
2856 done
2857 }
2858 nonet() {
2859 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2860 s ip netns add nonet
2861 fi
2862 sudo -E env /sbin/ip netns exec nonet sudo -E -u iank /bin/bash
2863 }
2864
2865 m() { printf "%s\n" "$*"; "$@"; }
2866
2867 # update file. note: duplicated in mail-setup.
2868 # updates $ur u result to true or false
2869 # updates $reload to true if file updated is in /etc/systemd/system
2870 u() {
2871 local tmp tmpdir dest="$1"
2872 local base="${dest##*/}"
2873 local dir="${dest%/*}"
2874 if [[ $dir != "$base" ]]; then
2875 # dest has a directory component
2876 mkdir -p "$dir"
2877 fi
2878 # shellcheck disable=SC2034 # see comment at top of function
2879 ur=false # u result
2880 tmpdir="$(mktemp -d)"
2881 cat >$tmpdir/"$base"
2882 tmp=$(rsync -ic $tmpdir/"$base" "$dest")
2883 if [[ $tmp ]]; then
2884 printf "%s\n" "$tmp"
2885 # shellcheck disable=SC2034 # see comment at top of function
2886 ur=true
2887 if [[ $dest == /etc/systemd/system/* ]]; then
2888 # shellcheck disable=SC2034 # see comment at top of function
2889 reload=true
2890 fi
2891 fi
2892 rm -rf $tmpdir
2893 }
2894
2895
2896 uptime() {
2897 if type -p uprecords &>/dev/null; then
2898 uprecords -B
2899 else
2900 command uptime
2901 fi
2902 }
2903
2904 virshrm() {
2905 for x in "$@"; do virsh destroy "$x"; virsh undefine "$x"; done
2906 }
2907
2908 vm-set-listen(){
2909 local t
2910 t=$(mktemp)
2911 local vm=$1
2912 local ip=$2
2913 sudo virsh dumpxml $vm | sed -r "s/(<listen.*address=')([^']+)/\1$ip/" | \
2914 sed -r "s/listen='[^']+/listen='$ip/"> $t
2915 sudo virsh undefine $vm
2916 sudo virsh define $t
2917 }
2918
2919
2920 vmshare() {
2921 vm-set-listen $1 0.0.0.0
2922 }
2923
2924
2925 vmunshare() {
2926 vm-set-listen $1 127.0.0.1
2927 }
2928
2929 myiwscan() {
2930 local i
2931 interfaces=$(iw dev | awk '$1 == "Interface" {print $2}')
2932 for i in $interfaces; do
2933 echo "myiwscan: considering $i"
2934 # find input, copy to pattern space, when we find the first field, print the copy in different order without newlines.
2935 # instead of using labels, we could just match a line and group, eg: /signal:/,{s/signal:(.*)/\1/h}
2936 sudo iw dev $i scan | sed -rn "
2937 s/^\Wcapability: (.*)/\1/;Ta;h;b
2938 :a;s/^\Wsignal: -([^.]+).*/\1/;Tb;H;b
2939 # padded to min width of 20
2940 :b;s/\WSSID: (.*)/\1 /;T;s/^(.{20}(.*[^ ])?) */\1/;H;g;s/(.*)\n(.*)\n(.*)/\2 \3 \1/gp;b
2941 "|sort -r
2942 done
2943 }
2944
2945 # Run script by copying it to a temporary location first,
2946 # and changing directory, so we don't have any open
2947 # directories or files that could cause problems when
2948 # remounting.
2949 zr() {
2950 local tmp
2951 tmp=$(type -p "$1")
2952 if [[ $tmp ]]; then
2953 cd "$(mktemp -d)"
2954 cp -a "$tmp" .
2955 shift
2956 ./"${tmp##*/}" "$@"
2957 else
2958 "$@"
2959 fi
2960 }
2961
2962
2963 # * spark
2964 # spark 1 5 22 13 53
2965 # # => ▁▁▃▂▇
2966
2967 # The MIT License
2968 # Copyright (c) Zach Holman, https://zachholman.com
2969 # https://github.com/holman/spark
2970
2971 # As of 2022-10-28, I reviewed github forks that had several newer
2972 # commits, none had anything interesting. I did a little refactoring
2973 # mostly to fix emacs indent bug.
2974
2975 # Generates sparklines.
2976 _spark_echo()
2977 {
2978 if [ "X$1" = "X-n" ]; then
2979 shift
2980 printf "%s" "$*"
2981 else
2982 printf "%s\n" "$*"
2983 fi
2984 }
2985
2986
2987 spark()
2988 {
2989 local f tc
2990 local n numbers=
2991
2992 # find min/max values
2993 local min=0xffffffff max=0
2994
2995 for n in ${@//,/ }
2996 do
2997 # on Linux (or with bash4) we could use `printf %.0f $n` here to
2998 # round the number but that doesn't work on OS X (bash3) nor does
2999 # `awk '{printf "%.0f",$1}' <<< $n` work, so just cut it off
3000 n=${n%.*}
3001 (( n < min )) && min=$n
3002 (( n > max )) && max=$n
3003 numbers=$numbers${numbers:+ }$n
3004 done
3005
3006 # print ticks
3007 local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █)
3008
3009 # use a high tick if data is constant
3010 (( min == max )) && ticks=(▅ ▆)
3011
3012 tc=${#ticks[@]}
3013 f=$(( ( (max-min) <<8)/( tc - 1) ))
3014 (( f < 1 )) && f=1
3015
3016 for n in $numbers
3017 do
3018 _spark_echo -n ${ticks[$(( (((n-min)<<8)/f) ))]}
3019 done
3020 _spark_echo
3021 }
3022
3023 pdfwc() { local f; for f; do echo "$f" "$(pdfinfo "$f" | awk '/^Pages:/ {print $2}')"; done }
3024
3025
3026 # nvm install script appended this to my .bashrc. I dont want to run it all the time,
3027 # so put it in a function.
3028 nvm-init() {
3029 export NVM_DIR="$HOME/.nvm"
3030 # shellcheck disable=SC1091 # may not exist, & third party
3031 [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" # This loads nvm
3032 # shellcheck disable=SC1091 # may not exist, & third party
3033 [ -s "$NVM_DIR/bash_completion" ] && source "$NVM_DIR/bash_completion" # This loads nvm bash_completion
3034 }
3035
3036
3037 leap-year() {
3038 if date -d 'february 29' &>/dev/null; then
3039 year_days=366
3040 else
3041 year_days=365
3042 fi
3043 echo $year_days
3044 }
3045
3046 # on-battery
3047 on-bat() {
3048 if [[ -e /sys/class/power_supply/AC/online && $(</sys/class/power_supply/AC/online) == 0 ]]; then
3049 return 0
3050 else
3051 return 1
3052 fi
3053 }
3054
3055 # make vim work with my light colortheme terminal.
3056 vim() {
3057 if [[ -e ~/.vimrc ]]; then
3058 command vim "$@"
3059 else
3060 command vim -c ':colorscheme peachpuff' "$@"
3061 fi
3062 }
3063
3064 # ls count. usage: pass a directory, get the number of files.
3065 # https://unix.stackexchange.com/questions/90106/whats-the-most-resource-efficient-way-to-count-how-many-files-are-in-a-director
3066 lsc() {
3067 # shellcheck disable=SC2790 disable=SC2012 # intentional
3068 ls -Uq "$@"|wc -l
3069 }
3070
3071 # run then notify. close notification after the next prompt.
3072 rn() {
3073 "$@"
3074 dunstify -u critical -h string:x-dunst-stack-tag:profanity "$*"
3075 _psrun=(dunstctl close-all)
3076 }
3077 n() {
3078 dunstify -u critical -h string:x-dunst-stack-tag:profanity n
3079 _psrun=(dunstctl close-all)
3080 }
3081
3082 catnew() {
3083 local dir file
3084 dir="$1"
3085 inotifywait -m "$dir" -e create -e moved_to | while read -r _ _ file; do
3086 hr
3087 cat "$dir/$file"
3088 done
3089 }
3090 # cat mail
3091 cm() {
3092 catnew /m/md/$1/new
3093 }
3094
3095
3096 fsf-sv-header() {
3097 local f
3098 local -a f_maybe
3099 if ! type -p sponge &>/dev/null; then
3100 echo "$0: error: missing dependency: sudo apt install moreutils" >&2
3101 return 1
3102 fi
3103
3104 for f; do
3105 echo "adding header to $f"
3106 if [[ -s $f ]]; then
3107 f_maybe=("$f")
3108 else
3109 f_maybe=()
3110 fi
3111 cat - "${f_maybe[@]}" <<EOF | sponge "$f"
3112 The following is the GNU All-permissive License as recommended in
3113 <https://www.gnu.org/licenses/license-recommendations.en.html>
3114
3115 Copyright (C) $(date +%Y) Free Software Foundation <sysadmin@fsf.org>
3116
3117 Copying and distribution of this file, with or without modification,
3118 are permitted in any medium without royalty provided the copyright
3119 notice and this notice are preserved. This file is offered as-is,
3120 without any warranty.
3121
3122 Contributions are welcome. See <https://savannah.gnu.org/maintenance/fsf/>.
3123
3124 EOF
3125 done
3126 }
3127
3128
3129 tsr() { # ts run
3130 "$@" |& ts || return $?
3131 }
3132
3133
3134 # * misc stuff
3135
3136
3137 if $use_color && type -p tput &>/dev/null; then
3138 # this is nice for a dark background terminal:
3139 # https://github.com/trapd00r/LS_COLORS
3140 # I would like if there was something similar for light.
3141
3142 # the default bold green is too light.
3143 # this explains the codes: https://gist.github.com/thomd/7667642
3144 export LS_COLORS=ex=1
3145
3146 term_bold="$(tput bold)"
3147 term_red="$(tput setaf 1)"
3148 term_green="$(tput setaf 2)"
3149 # shellcheck disable=SC2034 # expected
3150 term_yellow="$(tput setaf 3)"
3151 term_purple="$(tput setaf 5)"
3152 term_nocolor="$(tput sgr0)" # no font attributes
3153
3154 # unused so far. commented for shellcheck
3155 # term_underl="$(tput smul)"
3156 # term_blue="$(tput setaf 4)"
3157 # term_cyan="$(tput setaf 6)"
3158 fi
3159 # Try to keep environment pollution down, EPA loves us.
3160 unset safe_term match_lhs use_color
3161
3162 # * prompt
3163
3164
3165 if [[ $- == *i* ]]; then
3166
3167
3168 case $HOSTNAME in
3169 bk|je|li)
3170 if [[ $EUID == 1000 ]]; then
3171 system-status _ ||:
3172 fi
3173 ;;
3174 esac
3175
3176
3177 # this needs to come before next ps1 stuff
3178 # this stuff needs bash 4, feb 2009,
3179 # old enough to no longer condition on $BASH_VERSION anymore
3180 shopt -s autocd
3181 shopt -s dirspell
3182 PS1='\w'
3183 if [[ $- == *i* ]] && [[ ! $LC_INSIDE_EMACS ]]; then
3184 PROMPT_DIRTRIM=2
3185 bind -m vi-command B:shell-backward-word
3186 bind -m vi-command W:shell-forward-word
3187 fi
3188
3189 if [[ $SSH_CLIENT || $SUDO_USER ]]; then
3190 unset PROMPT_DIRTRIM
3191 PS1="\h:$PS1"
3192 fi
3193
3194 # emacs terminal has problems if this runs slowly,
3195 # so I've thrown a bunch of things at the wall to speed it up.
3196 prompt-command() {
3197 local return=$? # this MUST COME FIRST
3198 local ps_char ps_color
3199 unset IFS
3200
3201 if [[ $HISTFILE ]]; then
3202 history -a # save history
3203 fi
3204
3205 case $return in
3206 0) ps_color="$term_purple"
3207 ps_char='\$'
3208 ;;
3209 *) ps_color="$term_green"
3210 ps_char="$return \\$"
3211 ;;
3212 esac
3213 if [[ ! -O . ]]; then # not owner
3214 if [[ -w . ]]; then # writable
3215 ps_color="$term_bold$term_red"
3216 else
3217 ps_color="$term_bold$term_green"
3218 fi
3219 fi
3220
3221 # faster than sourceing the file im guessing
3222 if [[ -e /dev/shm/iank-status && ! -e /tmp/quiet-status ]]; then
3223 eval "$(< /dev/shm/iank-status)"
3224 fi
3225 if [[ $MAIL_HOST && $MAIL_HOST != "$HOSTNAME" ]]; then
3226 ps_char="@ $ps_char"
3227 fi
3228 jobs_char=
3229 if [[ $(jobs -p) ]]; then
3230 jobs_char='j\j '
3231 fi
3232
3233
3234 # allow a function to specify a command to run after we run the next
3235 # command. Use case: a function makes a persistent notification. If
3236 # we happen to be using that terminal, we can just keep working by
3237 # entering our next command, even a noop in order to dismiss the
3238 # notification, instead of having to explicitly dismiss it.
3239 if [[ ${_psrun[*]} ]]; then
3240 if (( _psrun_count >= 1 )); then
3241
3242 "${_psrun[@]}" ||:
3243 _psrun_count=0
3244 unset _psrun
3245 else
3246 _psrun_count=$(( _psrun_count + 1 ))
3247 fi
3248 else
3249 _psrun_count=0
3250 fi
3251
3252 # We could test if sudo is active with sudo -nv
3253 # but then we get an email and log of lots of failed sudo commands.
3254 # We could turn those off, but seems better not to.
3255 if [[ $EUID != 0 ]] && [[ $DID_SUDO ]]; then
3256 psudo="\[$term_bold$term_red\]s\[$term_nocolor\] "
3257 fi
3258 if [[ ! $HISTFILE ]]; then
3259 ps_char="NOHIST $ps_char"
3260 fi
3261 PS1="${PS1%"${PS1#*[wW]}"} $jobs_char$psudo\[$ps_color\]$ps_char\[$term_nocolor\] "
3262
3263 # copy of what is automatically added by guix.
3264 # adds [env] to PS1 if GUIX_ENVIRONMENT is set and PS1 contains '$';
3265 if [ -n "$GUIX_ENVIRONMENT" ]; then
3266 if [[ $PS1 =~ (.*)"\\$" ]]; then
3267 PS1="${BASH_REMATCH[1]} [env]\\\$ "
3268 fi
3269 fi
3270
3271
3272 # set titlebar. instead, using more advanced
3273 # titelbar below
3274 #echo -ne "$_title_escape $HOSTNAME ${PWD/#$HOME/~} \007"
3275 }
3276 PROMPT_COMMAND=(prompt-command)
3277
3278 if [[ $TERM == screen* ]]; then
3279 _title_escape="\033]..2;"
3280 else
3281 # somme sites recommend this, i dunno what the diff is.
3282 #_title_escape="\033]30;"
3283 _title_escape="\033]0;"
3284 fi
3285
3286 # make the titlebar be the last command and the current directory.
3287 settitle () {
3288
3289
3290 # These are some checks to help ensure we dont set the title at
3291 # times that the debug trap is running other than the case we
3292 # want. Some of them might not be needed.
3293 if (( ${#FUNCNAME[@]} != 1 || ${#BASH_ARGC[@]} != 2 || BASH_SUBSHELL != 0 )); then
3294 return 0
3295 fi
3296 if [[ $1 == prompt-command ]]; then
3297 return 0
3298 fi
3299 echo -ne "$_title_escape ${PWD/#$HOME/~} "
3300 printf "%s" "$*"
3301 echo -ne "\007"
3302 }
3303
3304 # note, this wont work:
3305 # x=$(mktemp); cp a $x
3306 # I havnt figured out why, bigger fish to fry.
3307 #
3308 # for titlebar.
3309 # condition from the screen man page i think.
3310 # note: duplicated in tx()
3311 if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
3312 trap 'settitle "$BASH_COMMAND"' DEBUG
3313 else
3314 trap DEBUG
3315 fi
3316
3317 fi
3318
3319 # * stuff that makes sense to be at the end
3320
3321
3322 # best practice
3323 unset IFS
3324
3325 if [[ -s "$HOME/.rvm/scripts/rvm" ]]; then
3326 # shellcheck disable=SC1091
3327 source "$HOME/.rvm/scripts/rvm"
3328 fi
3329
3330 # I had this idea to start a bash shell which would run an initial
3331 # command passed through this env variable, then continue on
3332 # interactively. But the use case I had in mind went away.
3333 #
3334 # if [[ $MY_INIT_CMD ]]; then
3335 # "${MY_INIT_CMD[@]}"
3336 # unset MY_INIT_CMD
3337 # fi
3338
3339 # ensure no bad programs appending to this file will have an affect
3340 return 0