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