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