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