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