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