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