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