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