strip out terminals that require gpu accel
[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 faf() { # find all files. use -L to follow symlinks
1394 find "$@" -not \( -name .svn -prune -o -name .git -prune \
1395 -o -name .hg -prune -o -name .editor-backups -prune \
1396 -o -name .undo-tree-history -prune \) -type f 2>/dev/null
1397 }
1398
1399 # usage ffconcat FILES_TO_CONCAT OUTPUT_FILE
1400 ffconcat() {
1401 local tmpf
1402 tmpf=$(mktemp)
1403 printf "file '%s'\n" "$1" >$tmpf
1404 while (( $# > 1 )); do
1405 shift
1406 printf "file '%s'\n" "$1" >>$tmpf
1407 done
1408 # https://trac.ffmpeg.org/wiki/Concatenate
1409 ffmpeg -f concat -safe 0 -i $tmpf -c copy "$1"
1410 rm $tmpf
1411 }
1412 ffremux() {
1413 local tmpf tmpd
1414 if (( $# == 0 )); then
1415 echo ffremux error expected args >&2
1416 return 1
1417 fi
1418 tmpd="$(mktemp -d)"
1419 for f; do
1420 tmpf=$tmpd/"${f##*/}"
1421 ffmpeg -i "$f" -c:v copy -c:a copy $tmpf
1422 cat $tmpf >"$f"
1423 done
1424 rm -r $tmpd
1425 }
1426
1427
1428
1429 # absolute path of file/dir without resolving symlinks.
1430 #
1431 # Most of the time, I want this where I would normally use readlink.
1432 # This is what realpath -s does in most cases, but sometimes it
1433 # actually resolves symlinks, at least when they are in /.
1434 #
1435 # Note, if run on a dir, if the final component is relative, it won't
1436 # resolve that. Use the below fpd for that.
1437 #
1438 # note: we could make a variation of this which
1439 # assigns to a variable name using eval, so that we don't have to do
1440 # x=$(fp somepath), which might save subshell overhead and look nice,
1441 # but I'm not going to bother.
1442 fp() {
1443 local initial_oldpwd initial_pwd dir base
1444 initial_oldpwd="$OLDPWD"
1445 initial_pwd="$PWD"
1446 if [[ $1 == */* ]]; then
1447 dir="${1%/*}"
1448 base="/${1##*/}"
1449 # CDPATH because having it set will cause cd to possibly print output
1450 CDPATH='' cd "$dir"
1451 printf "%s%s\n" "$PWD" "$base"
1452 CDPATH='' cd "$initial_pwd"
1453 OLDPWD="$initial_oldpwd"
1454 else
1455 printf "%s/%s\n" "$PWD" "$1"
1456 fi
1457 }
1458 # full path of directory without resolving symlinks
1459 fpd() {
1460 local initial_oldpwd initial_pwd dir
1461 initial_oldpwd="$OLDPWD"
1462 initial_pwd="$PWD"
1463 dir="$1"
1464 CDPATH='' cd "$dir"
1465 printf "%s%s\n" "$PWD" "$base"
1466 cd "$initial_pwd"
1467 OLDPWD="$initial_oldpwd"
1468 }
1469
1470
1471 # mail related
1472 frozen() {
1473 rm -rf /tmp/frozen
1474 sudo mailq |gr frozen|awk '{print $3}' | while read -r id; do
1475 sudo exim -Mvl $id
1476 echo
1477 sudo exim -Mvh $id
1478 echo
1479 sudo exim -Mvb $id
1480 echo -e '\n\n##############################\n'
1481 done | tee -a /tmp/frozen
1482 }
1483 frozenrm() {
1484 local ids=()
1485 while read -r line; do
1486 printf '%s\n' "$line"
1487 ids+=("$(printf '%s\n' "$line" |gr frozen|awk '{print $3}')")
1488 done < <(s mailq)
1489 echo "sleeping for 2 in case you change your mind"
1490 sleep 2
1491 sudo exim -Mrm "${ids[@]}"
1492 }
1493
1494 funce() {
1495 # like -e for functions. returns on error.
1496 # at the end of the function, disable with:
1497 # trap ERR
1498 trap 'echo "${BASH_COMMAND:+BASH_COMMAND=\"$BASH_COMMAND\" }
1499 ${FUNCNAME:+FUNCNAME=\"$FUNCNAME\" }${LINENO:+LINENO=\"$LINENO\" }\$?=$?"
1500 trap ERR
1501 return' ERR
1502 }
1503
1504 getdir () {
1505 local help="Usage: getdir [--help] PATH
1506 Output the directory of PATH, or just PATH if it is a directory."
1507 if [[ $1 == --help ]]; then
1508 echo "$help"
1509 return 0
1510 fi
1511 if [[ $# -ne 1 ]]; then
1512 echo "getdir error: expected 1 argument, got $#"
1513 return 1
1514 fi
1515 if [[ -d $1 ]]; then
1516 echo "$1"
1517 else
1518 local dir
1519 dir="$(dirname "$1")"
1520 if [[ -d $dir ]]; then
1521 echo "$dir"
1522 else
1523 echo "getdir error: directory does not exist"
1524 return 1
1525 fi
1526 fi
1527 }
1528
1529 git_empty_branch() { # start an empty git branch. carefull, it deletes untracked files.
1530 [[ $# == 1 ]] || { echo 'need a branch name!'; return 1;}
1531 local root
1532 root=$(gitroot) || return 1 # function to set gitroot
1533 builtin cd "$root"
1534 git symbolic-ref HEAD refs/heads/$1
1535 rm .git/index
1536 git clean -fdx
1537 }
1538
1539 # shellcheck disable=SC2120
1540 gitroot() {
1541 local help="Usage: gitroot [--help]
1542 Print the full path to the root of the current git repo
1543
1544 Handles being within a .git directory, unlike git rev-parse --show-toplevel,
1545 and works in older versions of git which did not have that."
1546 if [[ $1 == --help ]]; then
1547 echo "$help"
1548 return
1549 fi
1550 local p
1551 p=$(git rev-parse --git-dir) || { echo "error: not in a git repo" ; return 1; }
1552 [[ $p != /* ]] && p=$PWD
1553 echo "${p%%/.git}"
1554 }
1555
1556 g() {
1557
1558 local args gdb=false
1559
1560 if [[ $EMACSDIR ]]; then
1561 path-add "$EMACSDIR/lib-src" "$EMACSDIR/src"
1562 fi
1563
1564 if [[ $DISPLAY ]]; then
1565 args=-n
1566 fi
1567
1568 if (( $# == 0 )); then
1569 args+=" -c"
1570 fi
1571 # duplicate -c, but oh well
1572 if ! pgrep -u $EUID emacsclient; then
1573 if (( $# == 0 )) && type -p gdb &>/dev/null; then
1574 gdb=true
1575 else
1576 args+=" -c"
1577 fi
1578 fi
1579 if [[ $EMACSDIR ]]; then
1580
1581 # todo: we don't have to alter HOME since emacs 29+, we can set
1582 # user-emacs-directory with the flag --init-directory
1583
1584 # Alter the path here, otherwise the nfs mount gets triggered on the
1585 # first path lookup when emacs is not being used.
1586 # shellcheck disable=SC2098 disable=SC2097 # false positive
1587 PATH="$EMACSDIR/lib-src:$EMACSDIR/src:$PATH" EHOME=$HOME HOME=$EMACSDIR m emacsclient -a "" $args "$@"
1588 else
1589 if $gdb; then
1590 # due to a bug, we cant debug from the start unless we get a new gdb
1591 # https://sourceware.org/bugzilla/show_bug.cgi?id=24454
1592 # m gdb -ex="set follow-fork-mode child" -ex=r -ex=quit --args emacs --daemon
1593 m emacsclient -a "" $args "$@"
1594 sleep 1
1595 cd "/a/opt/emacs-$(distro-name)$(distro-num)"
1596 s gdb -p "$(pgrep -f 'emacs --daemon')" -ex c
1597 cd -
1598 else
1599 m emacsclient -a "" $args "$@"
1600 fi
1601 fi
1602 }
1603
1604 # g pipe. like: cmd | emacs. save cmd output to tmp file, then edit.
1605 gp() {
1606 cat &>/a/tmp/gtmp
1607 g "$@" /a/tmp/gtmp
1608 }
1609 # g log
1610 #like cmd &> tempfile; emacs tempfile
1611 #
1612 # note: a useful workflow for doing mass replace on my files:
1613 # gc rem REGEX
1614 ## remove any false positives, or manually edit them. rename files if needed.
1615 # sedi 's/REGEX/REPLACEMENT/' $(gr '^/' /a/tmp/gtmp)
1616 gl() {
1617 "$@" &> /a/tmp/gtmp
1618 g /a/tmp/gtmp
1619 }
1620 # g command substitution.
1621 gc() {
1622 # shellcheck disable=SC2046 # i want word splitting for this hackery
1623 g $("$@")
1624 }
1625
1626 # force terminal version
1627 gn() {
1628 g -n "$@"
1629 }
1630
1631 gmacs() {
1632 # quit will prompt if the program crashes.
1633 gdb -ex=r -ex=quit --args emacs "$@"; r;
1634 }
1635
1636 gdkill() {
1637 # kill the emacs daemon
1638 pk1 emacs --daemon
1639 }
1640
1641 gr() {
1642 grep -iIP --color=auto "$@" || return $?
1643 }
1644 grr() { # grep recursive
1645 # Don't return 1 on nonmatch because this is meant to be
1646 # interactive, not in a conditional.
1647 if [[ ${#@} == 1 ]]; then
1648 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" . || [[ $? == 1 ]]
1649 else
1650 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" || [[ $? == 1 ]]
1651 fi
1652 }
1653 ccomp grep gr grr
1654
1655 rg() { grr "$@"; }
1656 ccomp grep rg
1657
1658 # recursive everything. search for files/dirs and lines. rs = easy chars to press
1659 re() {
1660 local query
1661 query="$1"
1662 find "$@" -not \( -name .svn -prune -o -name .git -prune \
1663 -o -name .hg -prune -o -name .editor-backups -prune \
1664 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto "$query"
1665 grr -m 5 "$@"
1666 }
1667
1668 # horizontal row. used to break up output
1669 hr() {
1670 local blocks
1671 # 180 is long enough.
1672 blocks=██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
1673 printf "%s\n" "$(tput setaf 5 2>/dev/null ||:)${blocks:0:${COLUMNS:-180}}$(tput sgr0 2>/dev/null||:)"
1674 }
1675 # highlight
1676 hl() {
1677 local col input_len=0
1678 for arg; do
1679 input_len=$((input_len + 1 + ${#arg}))
1680 done
1681 col=$((60 - input_len))
1682 printf "\e[1;97;41m%s" "$*"
1683 if (( col > 0 )); then
1684 # shellcheck disable=SC2046 # needed to work as intended. a better way would be like hr above.
1685 printf "\e[1;97;41m \e[0m%.0s" $(eval echo "{1..${col}}")
1686 fi
1687 echo
1688 }
1689 hlm() { hl "$*"; "$@"; }
1690
1691 hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; done }
1692
1693 # example usage:
1694 # github-release-dl restic/restic restic_ _linux_amd64.bz2
1695 # gets a url like:
1696 # https://github.com/restic/restic/releases/download/v0.16.3/restic_0.16.3_linux_amd64.bz2
1697 github-release-dl() {
1698 local github_path file_prefix file_suffix latest_prefix version redir_path
1699 github_path=$1
1700 file_prefix=$2
1701 file_suffix=$3
1702 if (( $# != 3 )); then
1703 echo "$0: error, expected 3 arguments" >&2
1704 return 1
1705 fi
1706 redir_path="https://github.com/$github_path/releases/latest/download/"
1707 latest_prefix=$(curl -s -I "$redir_path" | awk 'tolower($1) == "location:" {print $2}')
1708 # it has a trailing /r at the end. just kill any whitespace.
1709 latest_prefix="${latest_prefix//[$'\t\r\n ']}"
1710 if [[ ! $latest_prefix ]]; then
1711 echo "failed to find latest path. Tried to find case insensitive 'location:' in the curl output:"
1712 m curl -s -I "$redir_path"
1713 return 1
1714 fi
1715 version="${latest_prefix##*/}"
1716 version="${version#v}"
1717 m wget -- "$latest_prefix/$file_prefix$version$file_suffix"
1718 }
1719
1720 # examples.
1721 # go-github-install restic/restic restic_ _linux_amd64.bz2
1722 # go-github-install restic/rest-server rest-server_ _linux_amd64.tar.gz
1723
1724 # common pattern among go binaries on github
1725 go-github-install() {
1726 local tmpd targetf tmp files src
1727 tmpd=$(mktemp -d)
1728 cd $tmpd
1729 file_prefix=$2
1730 file_suffix=$3
1731 tmp="${file_prefix##*[[:alnum:]]}"
1732 targetf="${file_prefix%"$tmp"}"
1733 echo targetf: $targetf
1734 github-release-dl "$@"
1735 files=(./*)
1736 case $file_suffix in
1737 *.bz2)
1738 bunzip2 -- ./*
1739 ;;
1740 *.tar.gz|*.tgz)
1741 tar -vxzf ./*
1742 ;;
1743 esac
1744 rm -f -- "${files[@]}"
1745 files=(./*)
1746 # Here we detect and handle 2 cases: either we extracted a single
1747 # binary which we have to rename or a folder with a binary named
1748 # $targetf in it which is all we care about.
1749 if (( ${#files[@]} == 1 )) && [[ -f ${files[0]} ]]; then
1750 chmod +x ./*
1751 mv -- ./* /usr/local/bin/$targetf
1752 else
1753 files=(./*/$targetf)
1754 if [[ -f $targetf ]]; then
1755 src=$targetf
1756 elif [[ -f ${files[0]} ]]; then
1757 src="${files[0]}"
1758 fi
1759 chmod +x "$src"
1760 mv -- "$src" /usr/local/bin
1761 fi
1762 cd - >/dev/null
1763 rm -rf $tmpd
1764 }
1765
1766 ## 2024: I'm using gh instead of hub, but leaving this just in case.
1767 ## I tried the github cli tool (gh) and it seems easier than
1768 ## I remember hub.
1769 ##
1770 ## hub predated github's 2020 official cli tool gh.
1771 ## more info at
1772 ## https://raw.githubusercontent.com/cli/cli/trunk/docs/gh-vs-hub.md
1773 # get latest hub and run it
1774 # main command to use:
1775 # hub pull-request --no-edit
1776 # --no-edit means to use the first commit\'s message as the pull request message.
1777 # If that fails, try doing
1778 # hub pull-request --no-edit -b UPSTREAM_OWNER:branch
1779 # where branch is usually master. it does the pr against your current branch.
1780 #
1781 # On first use, you input username/pass and it gets an oath token so you dont have to repeat
1782 # it\'s at ~/.config/hub
1783 hub() {
1784 local up uptar updir p re
1785 # example https://github.com/github/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz
1786 up=$(wget -q -O- https://api.github.com/repos/github/hub/releases/latest | jq -r .assets[].browser_download_url | grep linux-amd64)
1787 re='[[:space:]]'
1788 if [[ ! $up || $up =~ $re ]]; then
1789 echo "failed to get good update url. got: $up"
1790 fi
1791 uptar=${up##*/}
1792 updir=${uptar%.tgz}
1793 if [[ ! -e /a/opt/$updir ]]; then
1794 rm -rf /a/opt/hub-linux-amd64*
1795 wget -P /a/opt $up
1796 tar -C /a/opt -zxf /a/opt/$uptar
1797 rm -f /a/opt/$uptar
1798 fi
1799 if ! which hub &>/dev/null; then
1800 sudo /a/opt/$updir/install
1801 fi
1802
1803 # save token across computers
1804 if [[ ! -L ~/.config/hub ]]; then
1805 if [[ -e ~/.config/hub ]]; then
1806 mv ~/.config/hub /p/c/subdir_files/.config/
1807 fi
1808 if [[ -e /p/c/subdir_files/.config/hub ]]; then
1809 conflink
1810 fi
1811 fi
1812 command hub "$@"
1813 }
1814
1815 i() { git "$@"; }
1816 ccomp git i
1817
1818 # git status:
1819 # cvs -qn update
1820
1821 # git checkout FILE
1822 # cvs update -C FILE
1823
1824 # git pull
1825 # cvs up[date]
1826
1827 # potentially useful command translation
1828 # https://fling.seas.upenn.edu/~giesen/dynamic/wordpress/equivalent-commands-for-git-svn-and-cvs/
1829
1830 # importing cvs repo into git using git-cvs package:
1831 # /f/www $ /usr/lib/git-core/git-cvsimport -C /f/www-git
1832
1833 ic() {
1834 # fast commit all
1835 git commit -am "$*"
1836 }
1837
1838 ipp() {
1839 git pull
1840 git push
1841 }
1842
1843 ifn() {
1844 local glob
1845 glob="$1"
1846 shift
1847 find -L "$@" -not \( -name .svn -prune -o -name .git -prune \
1848 -o -name .hg -prune -o -name .editor-backups -prune \
1849 -o -name .undo-tree-history -prune \) -iname "*$glob*" 2>/dev/null
1850 }
1851
1852 ifh() {
1853 # insensitive find here. args are combined into the search string.
1854 # -L = follow symlinks
1855 find -L . -not \( -name .svn -prune -o -name .git -prune \
1856 -o -name .hg -prune -o -name .editor-backups -prune \
1857 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
1858 }
1859
1860 ifd() {
1861 # insensitive find directory
1862 find -L . -type d -not \( -name .svn -prune -o -name .git -prune \
1863 -o -name .hg -prune -o -name .editor-backups -prune \
1864 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
1865 }
1866
1867
1868 ipdrop() {
1869 sudo iptables -A INPUT -s $1 -j DROP
1870 }
1871
1872
1873 istext() {
1874 grep -Il "" "$@" &>/dev/null
1875 }
1876
1877 pst() {
1878 pstree -apnA
1879 }
1880
1881 # journalctl with times in the format the --since= and --until= options accept
1882 jrt() { journalctl -e -n100000 -o short-full "$@"; }
1883 jr() { journalctl -e -n100000 "$@" ; }
1884 jrf() { journalctl -n1000 -f "$@" ; }
1885 jru() {
1886 # the invocation id is "assigned each time the unit changes from an inactive
1887 # state into an activating or active state" man systemd.exec
1888 journalctl -e --no-tail -u exim4 _SYSTEMD_INVOCATION_ID="$(systemctl show -p InvocationID --value $1)"
1889 }
1890 ccomp journalctl jr jrf jru
1891
1892
1893
1894 l() {
1895 if [[ $PWD == /[iap] ]]; then
1896 command ls -A --color=auto -I lost+found "$@"
1897 else
1898 command ls -A --color=auto "$@"
1899 fi
1900 }
1901
1902 lcn() { locate -i "*$**"; }
1903
1904 lg() { LC_COLLATE=C.UTF-8 ll --group-directories-first "$@"; }
1905
1906 lt() { ll -tr "$@"; }
1907
1908 lld() { ll -d "$@"; }
1909
1910 ccomp ls l lg lt lld ll
1911
1912 # low recursively
1913 lowr() {
1914 local f dirs i a
1915 local -a all
1916 for dirs in false true; do
1917 for f; do
1918 if [[ -d $f ]]; then
1919 all=("$f"/**)
1920 # reverse the order to rename the nested dirs first.
1921 # note: 0 element is the dir itself
1922 for ((i=${#all[@]}-1; i>=1; i--)); do
1923 a="${all[i]}"
1924 if $dirs && [[ -d $a ]]; then
1925 # e dirs low "$a" # debug
1926 low "$a"
1927 elif ! $dirs && [[ ! -d $a && -e $a ]]; then
1928 # debug
1929 # e not dirs low "$a" # debug
1930 low "$a"
1931 fi
1932 done
1933 fi
1934 # just rename all the top level args on the second pass
1935 if $dirs; then
1936 # e final dirs low "$f" # debug
1937 low "$f"
1938 fi
1939 done
1940 done
1941 }
1942
1943 low() { # make filenames lowercase, remove bad chars
1944 local arg new dir f
1945 for arg; do
1946 arg="${arg%%+(/)}" # remove trailing slashes. assumes we have extglob on.
1947 dir="${arg%/*}"
1948 if (( ${#dir} == ${#arg} )); then
1949 dir=.
1950 fi
1951 f="${arg##*/}"
1952 new="${f,,}" # downcase
1953 # shellcheck disable=SC2031 # seems like a shellcheck bug
1954 new="${new//[^a-zA-Z0-9._-]/_}" # sub bad chars
1955 new="${new#"${new%%[[:alnum:]]*}"}" # remove leading/trailing non-alnum
1956 new="${new%"${new##*[[:alnum:]]}"}"
1957 # remove bad underscores, like __ and _._
1958 new=$(echo $new | sed -r 's/__+/_/g;s/_+([.-])|([.-])_+/\1/g')
1959 safe_rename "$dir/$f" "$dir/$new" || return 1
1960 done
1961 return 0
1962 }
1963
1964 lower() { # make first letter of filenames lowercase.
1965 local x
1966 for x in "$@"; do
1967 if [[ ${x::1} == [A-Z] ]]; then
1968 y=$(tr '[:upper:]' '[:lower:]' <<<"${x::1}")"${x:1}"
1969 safe_rename "$x" "$y" || return 1
1970 fi
1971 done
1972 }
1973
1974
1975 k() { # history search
1976 grep -iP --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | tail -n 80 || [[ $? == 1 ]];
1977 }
1978 ks() { # history search with context
1979 # args are an extended regex used by sed
1980 history | sed -nr "h;s/^\s*(\S+\s+){4}//;/$*/{g;p}" | tail -n 80 || [[ $? == 1 ]];
1981 }
1982 ksu() { # history search unique
1983 grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | uniq || [[ $? == 1 ]];
1984 }
1985
1986 # todo: id like to do maybe a daily or hourly cronjob to
1987 # check that my history file size is increasing. Ive had it
1988 # inexplicably truncated in the past.
1989 histrm() {
1990 history -n
1991 HISTTIMEFORMAT='' history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/'
1992 read -r -p "press anything but contrl-c to delete"
1993 for entry in $(HISTTIMEFORMAT='' history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/ { print a }' | tac); do
1994 history -d $entry
1995 done
1996 history -w
1997 }
1998
1999 # history without the date
2000 histplain() {
2001 history "$@" | cut -d' ' -f 7-
2002 }
2003
2004 ccomp grep k ks ksu histrm
2005
2006
2007 make-targets() {
2008 # show make targets, via http://stackoverflow.com/questions/3063507/list-goals-targets-in-gnu-make
2009 make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'
2010 }
2011
2012 mkc() {
2013 mkdir "$1"
2014 c "$1"
2015 }
2016 ccomp mkdir mkc
2017
2018 mkct() {
2019 mkc "$(mktemp -d)"
2020 }
2021 # mkdir the last arg, cp the rest into it
2022 mkcp() {
2023 mkdir -p "${@: -1}"
2024 cp "${@:1:$#-1}" "${@: -1}"
2025 }
2026 mkmv() {
2027 mkdir -p "${@: -1}"
2028 mv "${@:1:$#-1}" "${@: -1}"
2029 }
2030
2031 mkt() { # mkdir and touch file
2032 local path="$1"
2033 mkdir -p "$(dirname "$path")"
2034 touch "$path"
2035 }
2036
2037 # shellcheck disable=SC2032
2038 mkdir() { command mkdir -p "$@"; }
2039
2040 nags() {
2041 # https://github.com/HenriWahl/Nagstamon/issues/357
2042 if ! pgrep -f /usr/bin/dunst >/dev/null; then
2043 /usr/bin/dunst &
2044 fi
2045 /usr/bin/nagstamon &
2046 }
2047
2048 # profanity screen
2049 profsrc() {
2050 screen -RD -S profanity
2051 }
2052
2053 # i dont want to wait for konsole to exit...
2054 prof() {
2055 command prof &>/dev/null &
2056 }
2057 # self chat
2058 sc() {
2059 while read -r l; do
2060 printf '\033[1A\033[K'; printf "%s\n" "$l"| ts "%F %T" | tee -a /p/self-chat.log
2061 done
2062 }
2063
2064 nmt() {
2065 # cant use s because sudo -i doesnt work for passwordless sudo command
2066 case $EUID in
2067 0)
2068 sudo nmtui-connect "$@"
2069 ;;
2070 *)
2071 nmtui-connect "$@"
2072 ;;
2073 esac
2074 }
2075
2076
2077 ngset() {
2078 if shopt nullglob >/dev/null; then
2079 ngreset=false
2080 else
2081 shopt -s nullglob
2082 ngreset=true
2083 fi
2084 }
2085 ngreset() {
2086 if $ngreset; then
2087 shopt -u nullglob
2088 fi
2089 }
2090
2091 nopanic() {
2092 # shellcheck disable=SC2024
2093 ngset
2094 for f in /var/log/exim4/paniclog /var/log/exim4/*panic; do
2095 base=${f##*/}
2096 if [[ -s $f ]]; then
2097 echo ================== $f =============
2098 s tee -a /var/log/exim4/$base-archive <$f
2099 s truncate -s0 $f
2100 fi
2101 done
2102 ngreset
2103 }
2104
2105
2106 ping() { command ping -O "$@"; }
2107 p8() { ping "$@" 8.8.8.8; }
2108 p6() { ping6 "$@" 2001:4860:4860::8888; }
2109
2110 pkx() { # package extract
2111 local pkg cached tmp f
2112 c "$(mktemp -d)"
2113 pkg=$1
2114 # shellcheck disable=SC2012
2115 cached=$(ls -t /var/cache/apt/archives/${pkg}_* 2>/dev/null | tail -n1 2>/dev/null) ||:
2116 if [[ $cached ]]; then
2117 m cp $cached .
2118 else
2119 m aptitude download $pkg || return 1
2120 fi
2121 tmp=(*); f=${tmp[0]} # only 1 expected
2122 m ex $f
2123 m rm -f $f
2124 }
2125
2126 # pgrep and kill
2127 pk1() {
2128 local tmpf
2129 local -a pids
2130 tmpf=$(pgrep -f "$*")
2131 mapfile -t pids <<<"$tmpf"
2132 case ${#pids[@]} in
2133 1)
2134 # shellcheck disable=SC2128
2135 {
2136 ps -F ${pids[0]}
2137 m kill ${pids[0]}
2138 }
2139 ;;
2140 0) echo "no pid found" ;;
2141 *)
2142 ps -F ${pids[@]}
2143 ;;
2144 esac
2145 }
2146
2147 psg () {
2148 local x y help
2149 help="Usage: psg [--help] GREP_ARGS
2150 grep ps and output in a nice format"
2151 if [[ $1 == --help ]]; then
2152 echo "$help"
2153 return
2154 fi
2155 x=$(ps -eF)
2156 # final grep is because some commands tend to have a lot of trailing spaces
2157 y=$(echo "$x" | grep -iP "$@" | grep -o '.*[^ ]') ||:
2158 if [[ $y ]]; then
2159 echo "$x" | head -n 1 || [[ $? == 141 ]]
2160 echo "$y"
2161 fi
2162 }
2163
2164 pubip() { curl -4s https://icanhazip.com; }
2165 pubip6() { curl -6s https://icanhazip.com; }
2166 whatismyip() { pubip; }
2167
2168
2169 q() { # start / launch a program in the backround and redir output to null
2170 "$@" &> /dev/null &
2171 }
2172
2173 # shellcheck disable=SC2120
2174 r() {
2175 if [[ $HISTFILE ]]; then
2176 history -a # save history
2177 fi
2178 trap ERR # this avoids a segfault
2179 exit ${1:0}
2180 # i had this redir, not sure why
2181 # exit "$@" 2>/dev/null
2182 }
2183
2184 # scp is insecure and deprecated.
2185 scp() {
2186 rsync -Pt --inplace "$@"
2187 }
2188 ccomp rsync scp
2189
2190 randport() {
2191 # available high ports are 1024-65535,
2192 # but lets skip things that are more likely to be in use
2193 python3 <<'EOF'
2194 import secrets
2195 print(secrets.SystemRandom().randrange(10002,65500))
2196 EOF
2197 }
2198
2199 # reapply bashrc
2200 reb() {
2201 # shellcheck disable=SC1090 # expected to not follow
2202 source ~/.bashrc
2203 }
2204
2205 rl() {
2206 readlink -f "$@"
2207 }
2208 ccomp readlink rl
2209
2210 rsd() {
2211 # rsync, root is required to keep permissions right.
2212 # rsync --archive --human-readable --verbose --itemize-changes --checksum \(-ahvic\) \
2213 # --no-times --delete
2214 # basically, make an exact copy, use checksums instead of file times to be more accurate
2215 rsync -ahvic --delete "$@"
2216 }
2217 rsa() {
2218 # like rlu, but dont delete files on the target end which
2219 # do not exist on the original end.
2220 rsync -ahvic "$@"
2221 }
2222 rst() {
2223 # rl without preserving modification time.
2224 rsync -ahvic --delete --no-t "$@"
2225 }
2226 # [RSYNC_OPTS] HOST PATH
2227 rsu() {
2228 # eg. rsu -opts frodo /testpath
2229 # relative paths will expanded with readlink -f.
2230 opts=("${@:1:$#-2}") # 1 to last -2
2231 path="${*:$#}" # last
2232 host="${*:$#-1:1}" # last -1
2233 if [[ $path == .* ]]; then
2234 path=$(readlink -f $path)
2235 fi
2236 m rsync -ahvi --relative --no-implied-dirs "${opts[@]}" "$path" "root@$host:/";
2237 }
2238 ccomp rsync rsd rsa rst rsu
2239
2240 # find programs listening on a port
2241 ssp() {
2242 local port=$1
2243 # to figure out these args, i had to look at the man page from git version, as of 2022-04.
2244 s ss -lpn state listening sport = $port
2245 }
2246
2247 resolvcat() {
2248 local f
2249 if [[ $(systemctl is-active nscd ||:) != inactive ]]; then
2250 m s nscd -i hosts
2251 fi
2252 f=/etc/resolv.conf
2253 echo $f:; ccat $f
2254 hr; s ss -lpn sport = 53
2255 if systemctl is-enabled dnsmasq &>/dev/null || [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
2256 # this will fail is dnsmasq is failed
2257 hr; m ser status dnsmasq | cat || :
2258 f=/etc/dnsmasq.conf
2259 hr; echo $f:; ccat $f
2260 hr; m grr '^ *(servers-file|server) *=|^ *no-resolv *$' /etc/dnsmasq.conf /etc/dnsmasq.d
2261 f=/etc/dnsmasq-servers.conf
2262 hr; echo $f:; ccat $f
2263 fi
2264 hr
2265 echo /etc/nsswitch.conf:
2266 grep '^ *hosts:' /etc/nsswitch.conf
2267 if systemctl is-enabled systemd-resolved &>/dev/null || [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then
2268 hr; m ser status systemd-resolved | cat || :
2269 hr; m resolvectl status | cat
2270 fi
2271
2272 }
2273 rcat() {
2274 resolvcat | less
2275 }
2276 reresolv() {
2277 if [[ $(systemctl is-active nscd ||:) != inactive ]]; then
2278 m ser stop nscd
2279 sleep .5
2280 m ser start nscd
2281 m sudo nscd -i hosts
2282 fi
2283 if [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
2284 m sudo systemctl restart dnsmasq
2285 fi
2286 if [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then
2287 m sudo systemctl restart systemd-resolved
2288 fi
2289 if type -P resolvectl &>/dev/null; then
2290 resolvectl flush-caches
2291 fi
2292 }
2293
2294 # add annoyingly long argument which should be the default
2295 sedi() {
2296 sed -i --follow-symlinks "$@"
2297 }
2298
2299
2300
2301 # todo: test variable assignment with newlines here.
2302 # https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash
2303
2304 # beware that it only works on the assumption that any special
2305 # characters in the input string are intended to be escaped, not to work
2306 # as special chacters.
2307 shellescape() {
2308 LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'
2309 }
2310
2311 rmstrips() {
2312 ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less
2313 }
2314
2315 urun () {
2316 umask $1
2317 shift
2318 "$@"
2319 }
2320 sudo () {
2321 command sudo "$@" || return $?
2322 DID_SUDO=true
2323 }
2324 s() {
2325 # background
2326 # I use a function because otherwise we cant use in a script,
2327 # cant assign to variable.
2328 #
2329 # note: gksudo is recommended for X apps because it does not set the
2330 # home directory to the same, and thus apps writing to ~ fuck things up
2331 # with root owned files.
2332 #
2333 if [[ $EUID != 0 || $1 == -* ]]; then
2334 # shellcheck disable=SC2034
2335 SUDOD="$PWD" command sudo -i "$@"
2336 DID_SUDO=true
2337 else
2338 "$@"
2339 fi
2340 }
2341 sb() { # sudo bash -c
2342 # use sb instead of s is for sudo redirections,
2343 # eg. sb 'echo "ok fine" > /etc/file'
2344 # shellcheck disable=SC2034
2345 local SUDOD="$PWD"
2346 sudo -i bash -c "$@"
2347 }
2348 # secret sudo
2349 se() { s urun 0077 "$@"; }
2350 ccomp sudo s sb se
2351
2352 safe_rename() { # warn and dont rename if file exists.
2353 # mv -n exists, but it\'s silent
2354 if [[ $# != 2 ]]; then
2355 echo safe_rename error: $# args, need 2 >&2
2356 return 1
2357 fi
2358 if [[ $1 != "$2" ]]; then # yes, we want to silently ignore this
2359 if [[ -e $2 || -L $2 ]]; then
2360 echo "Cannot rename $1 to $2 as it already exists."
2361 else
2362 mv -vi "$1" "$2"
2363 fi
2364 fi
2365 }
2366
2367
2368 sd() {
2369 sudo dd status=none of="$1"
2370 }
2371
2372 ser() {
2373 if type -p systemctl &>/dev/null; then
2374 s systemctl "$@"
2375 else
2376 if (( $# >= 3 )); then
2377 echo iank: ser expected 2 or less arguments
2378 return 1
2379 fi
2380 s service $2 $1
2381 fi
2382 }
2383 serstat() {
2384 systemctl -n 40 status "$@"
2385 }
2386
2387 seru() { systemctl --user "$@"; }
2388 # like restart, but do nothing if its not already started
2389 srestart() {
2390 local service=$1
2391 if [[ $(s systemctl --no-pager show -p ActiveState $service ) == ActiveState=active ]]; then
2392 systemctl restart $service
2393 fi
2394 }
2395
2396 setini() { # set a value in a .ini style file
2397 key="$1" value="$2" section="$3" file="$4"
2398 if [[ -s $file ]]; then
2399 sed -ri -f - "$file" <<EOF
2400 # remove existing keys
2401 / *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d}
2402 # add key
2403 /^\s*\[$section\]/a $key=$value
2404 # from section to eof, do nothing
2405 /^\s*\[$section\]/,\$b
2406 # on the last line, if we haven't found section yet, add section and key
2407 \$a [$section]\\
2408 $key=$value
2409 EOF
2410 else
2411 cat >"$file" <<EOF
2412 [$section]
2413 $key=$value
2414 EOF
2415 fi
2416 }
2417
2418 sgo() { # service go
2419 service=$1
2420 ser restart $service || return 1
2421 if type -p systemctl &>/dev/null; then
2422 ser enable $service
2423 fi
2424 }
2425 soff () {
2426 for service; do
2427 # ignore services that dont exist
2428 if systemctl cat $service &>/dev/null; then
2429 ser stop $service;
2430 ser disable $service
2431 fi
2432 done
2433 }
2434
2435 sgu() {
2436 systemctl list-unit-files | rg "$@"
2437 }
2438
2439
2440 sk() {
2441 # see https://savannah.gnu.org/maintenance/fsf/bash-style-guide/ for justifications
2442 local quotes others ret
2443 quotes=2048,2068,2086,2206,2254
2444 others=2029,2032,2033,2054,2164,
2445 shellcheck -W 999 -x -e $quotes,$others "$@" || ret=$?
2446 if (( ret >= 1 )); then
2447 echo "A template comment to disable is now in clipboard. eg: # shellcheck disable=SC2206 # reason"
2448 cbs "# shellcheck disable=SC"
2449 return $ret
2450 fi
2451 }
2452 # sk with quotes. For checking scripts that we expect to take untrusted
2453 # input in order to verify we quoted vars.
2454 skq() {
2455 local others
2456 others=2029,2033,2054,2164
2457 shellcheck -W 999 -x -e $others "$@" || return $?
2458 }
2459
2460 # sk on all modified files in current git repo
2461 skmodified() {
2462 local f
2463 for f in $(i s | awk '$1 == "modified:" {print $2}'); do
2464 if [[ ! -L $f ]] && istext "$f" && [[ $(head -n1 "$f" 2>/dev/null) == '#!/bin/bash'* ]]; then
2465 sk $f ||:
2466 fi
2467 done
2468 }
2469
2470 # sk on all the files in current git repo (except those excluded)
2471 skgit() {
2472 local f toplevel orig_dir tmp skip e
2473 local -a ls_files excludes
2474 toplevel=$(git rev-parse --show-toplevel)
2475 if [[ $PWD != "$toplevel" ]]; then
2476 orig_dir=$PWD
2477 cd $toplevel
2478 fi
2479 excludes=(
2480 'disabled/*'
2481 # included from another file and checked there
2482 beet-data
2483 brc
2484 brc2
2485 )
2486 tmp=$(git ls-files)
2487 mapfile -t ls_files <<<"$tmp"
2488 for f in "${ls_files[@]}"; do
2489 skip=false
2490 for e in "${excludes[@]}"; do
2491 if [[ $f == $e ]]; then
2492 skip=true
2493 break
2494 fi
2495 done
2496 if $skip; then continue; fi
2497
2498 if istext "$f" && [[ $(head -n1 "$f" 2>/dev/null) == '#!/bin/bash'* ]]; then
2499 sk $f ||:
2500 fi
2501 done
2502 if [[ $orig_dir ]]; then
2503 cd $orig_dir
2504 fi
2505 }
2506
2507
2508 # sl: ssh, but firsh rsync our bashrc and related files to a special
2509 # directory on the remote host if needed.
2510
2511 # Some environment variables and files need to be setup for this to work
2512 # (mine are set at the beginning of this file)
2513
2514 # SL_FILES_DIR: Environment variable. Path to folder which should at
2515 # least have a .bashrc file or symlink. This dir will be rsynced to ~ on
2516 # remote hosts (top level symlinks are resolved) unless the host already
2517 # has a $SL_FILES_DIR/.bashrc. In that case, we assume it is a host you
2518 # control and sync files to separately and already has the ~/.bashrc you
2519 # want. The remote bash will also take its .inputrc config from this
2520 # folder (default of not existing is fine). Mine looks like this:
2521 # https://iankelling.org/git/?p=distro-setup;a=tree;f=sl/.iank
2522
2523 # SL_INFO_DIR: Environment variable. This folder stores info about what
2524 # we detected on the remote system and when we last synced. It will be created
2525 # if it does not exist. Sometimes you may want to forget about a
2526 # remote system, you can use sl --rsync, or the function for that slr
2527 # below.
2528
2529 # SL_TEST_CMD: Env var. Meant to be used to vary the files synced
2530 # depending on the remote host. Run this string on the remote host the
2531 # first time sl is run (or if we run slr). The result is passed to
2532 # SL_TEST_HOOK. For example,
2533 # export SL_TEST_CMD=". /etc/os-release ; echo \${VERSION//[^a-zA-Z0-9]/}"
2534
2535 # SL_TEST_HOOK: Env var. It is run as $SL_TEST_HOOK. This can set
2536 # $SL_FILES_DIR to vary the files synced.
2537
2538 # SL_RSYNC_ARGS: Env var. String of arguments passed to rsync. For
2539 # example to exclude files within a directory. Note, excluded
2540 # files wont be deleted on rsync, you can add --delete-excluded
2541 # to the rsync command if that is desired.
2542
2543 # SL_SSH_ARGS: Env var. Default arguments passed to ssh.
2544
2545 # For when ~/.bashrc is already customized on the remote server, you
2546 # might find it problematic that ~/.bashrc is sourced for ALL ssh
2547 # commands, even in scripts. This paragraph is all about that. bash
2548 # scripts dont source ~/.bashrc, but call ssh in scripts and you get
2549 # ~/.bashrc. You dont want this. .bashrc is meant for interactive shells
2550 # and if you customize it, probably has bugs from time to time. This is
2551 # bad. Here's how I fix it. I have a special condition to "return" in my
2552 # .bashrc for noninteractive ssh shells (copy that code). Then use this
2553 # function or similar that passes LC_USEBASHRC=t when sshing and I want
2554 # my bashrc. Also, I don't keep most of my bashrc in .bashrc, i source a
2555 # separate file because even if I return early on, the whole file gets
2556 # parsed which can fail if there is a syntax error.
2557 sl() {
2558 # Background on LC_USEBASHRC var (no need to read if you just want to
2559 # use this function): env variables sent across ssh are strictly
2560 # limited, but we get LC_* at least in debian based machines, so we
2561 # just make that * be something no normal program would use. Note, on
2562 # hosts that dont allow LC_* I start an inner shell with LC_USEBASHRC
2563 # set, and the inner shell also allows running a nondefault
2564 # .bashrc. This means the outer shell still ran the default .bashrc,
2565 # but that is the best we can do.
2566
2567 local now args remote dorsync haveinfo tmpa sshinfo tmp tmp2 type info_sec force_rsync \
2568 sync_dirname testcmd extra_info testbool files_sec sl_test_cmd sl_test_hook
2569 declare -a args tmpa
2570
2571 args=($SL_SSH_ARGS)
2572
2573 # ssh [-1246Antivivisectionist] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
2574 # [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L address]
2575 # [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option]
2576 # [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] [user@]hostname
2577 # [command]
2578
2579 # ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
2580 # [-D [bind_address:]port] [-E log_file] [-e escape_char]
2581 # [-F configfile] [-I pkcs11] [-i identity_file]
2582 # [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]
2583 # [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]
2584 # [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
2585
2586 force_rsync=false
2587 if [[ $1 == --rsync ]]; then
2588 force_rsync=true
2589 shift
2590 fi
2591 # shellcheck disable=SC2153 # intentional
2592 sl_test_cmd=$SL_TEST_CMD
2593 # shellcheck disable=SC2153 # intentional
2594 sl_test_hook=$SL_TEST_HOOK
2595 # shellcheck disable=SC2153 # intentional
2596 sl_rsync_args=$SL_RSYNC_ARGS
2597 while [[ $1 ]]; do
2598 case "$1" in
2599 --rsync)
2600 force_rsync=true
2601 ;;
2602 --sl-test-cmd)
2603 sl_test_cmd="$2"
2604 shift
2605 ;;
2606 --sl-test-hook)
2607 sl_test_hook="$2"
2608 shift
2609 ;;
2610 --sl-rsync-args)
2611 sl_rsync_args="$2"
2612 shift
2613 ;;
2614 *)
2615 break
2616 ;;
2617 esac
2618 shift
2619 done
2620
2621 while [[ $1 ]]; do
2622 case "$1" in
2623 # note we dont support things like -4oOption
2624 -[46AaCfGgKkMNnqsTtVvXxYy]*)
2625 args+=("$1"); shift
2626 ;;
2627 -[bcDEeFIiJLlmOopQRSWw]*)
2628 # -oOption etc is valid
2629 if (( ${#1} >= 3 )); then
2630 args+=("$1"); shift
2631 else
2632 args+=("$1" "$2"); shift 2
2633 fi
2634 ;;
2635 *)
2636 break
2637 ;;
2638 esac
2639 done
2640 remote="$1"
2641 if [[ ! $remote ]]; then
2642 echo $0: error hostname required >&2
2643 return 1
2644 fi
2645 shift
2646
2647 if [[ ! $SL_INFO_DIR ]]; then
2648 echo 'error: missing SL_INFO_DIR env var' >&2
2649 return 1
2650 fi
2651
2652 dorsync=false
2653 haveinfo=false
2654 tmpa=($SL_INFO_DIR/???????????"$remote")
2655 sshinfo=${tmpa[0]}
2656 if [[ -e $sshinfo ]]; then
2657 if $force_rsync; then
2658 rm -f $sshinfo
2659 else
2660 haveinfo=true
2661 fi
2662 fi
2663 if $haveinfo; then
2664 tmp=${sshinfo[0]##*/}
2665 tmp2=${tmp::11}
2666 type=${tmp2: -1}
2667 extra_info=$(cat $sshinfo)
2668 else
2669 # we test for string to know ssh succeeded
2670 testbool="test -e $SL_FILES_DIR/.bashrc -a -L .bashrc -a -v LC_USEBASHRC"
2671 testcmd="if $testbool; then printf y; else printf n; fi"
2672 if ! tmp=$(LC_USEBASHRC=y command ssh "${args[@]}" "$remote" "$testcmd; $sl_test_cmd"); then
2673 echo failed sl test. doing plain ssh -v
2674 command ssh -v "${args[@]}" "$remote"
2675 fi
2676 if [[ $tmp == y* ]]; then
2677 type=a
2678 else
2679 dorsync=true
2680 type=b
2681 fi
2682 extra_info="${tmp:1}"
2683 fi
2684 if [[ $sl_test_hook ]]; then
2685 RSYNC_RSH="ssh ${args[*]}" $sl_test_hook "$extra_info" "$remote"
2686 fi
2687
2688 if $haveinfo && [[ $type == b ]]; then
2689 info_sec=${tmp::10}
2690 read -r files_sec _ < <(find -L $SL_FILES_DIR -printf "%T@ %p\n" | sort -nr || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]] )
2691 files_sec=${files_sec%%.*}
2692 if (( files_sec > info_sec )); then
2693 dorsync=true
2694 rm -f $sshinfo
2695 fi
2696 fi
2697
2698 sync_dirname=${SL_FILES_DIR##*/}
2699
2700 if [[ ! $SL_FILES_DIR ]]; then
2701 echo 'error: missing SL_FILES_DIR env var' >&2
2702 return 1
2703 fi
2704
2705 if $dorsync; then
2706 RSYNC_RSH="ssh ${args[*]}" m rsync -rptL --delete $sl_rsync_args $SL_FILES_DIR "$remote":
2707 fi
2708 if $dorsync || ! $haveinfo; then
2709 sshinfo=$SL_INFO_DIR/$EPOCHSECONDS$type"$remote"
2710 [[ -e $SL_INFO_DIR ]] || mkdir -p $SL_INFO_DIR
2711 printf "%s\n" "$extra_info" >$sshinfo
2712 chmod 666 $sshinfo
2713 fi
2714 if [[ $type == b ]]; then
2715 if (( ${#@} )); then
2716 # Theres a couple ways to pass arguments, im not sure whats best,
2717 # but relying on bash 4.4+ escape quoting seems most reliable.
2718 command ssh "${args[@]}" "$remote" \
2719 LC_USEBASHRC=t bash -c '.\ '$sync_dirname'/.bashrc\;"\"\$@\""' bash ${@@Q}
2720 elif [[ ! -t 0 ]]; then
2721 # This case is when commands are being piped to ssh.
2722 # Normally, no bashrc gets sourced.
2723 # But, since we are doing all this, lets source it because we can.
2724 cat <(echo . $sync_dirname/.bashrc) - | command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
2725 else
2726 command ssh -t "${args[@]}" "$remote" LC_USEBASHRC=t INPUTRC=$sync_dirname/.inputrc bash --rcfile $sync_dirname/.bashrc
2727 fi
2728 else
2729 if [[ -t 0 ]]; then
2730 LC_USEBASHRC=t command ssh "${args[@]}" "$remote" ${@@Q}
2731 else
2732 command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
2733 fi
2734 fi
2735 # this function inspired from https://github.com/Russell91/sshrc
2736 }
2737
2738 slr() {
2739 sl --rsync "$@"
2740 }
2741 sss() { # ssh solo
2742 sl -oControlMaster=no -oControlPath=/ "$@"
2743 }
2744 # kill off old shared socket then ssh
2745 ssk() {
2746 m ssh -O exit "$@" || [[ $? == 255 ]]
2747 m sl "$@"
2748 }
2749 ccomp ssh sl slr sss ssk
2750 # plain ssh
2751 ssh() {
2752 LC_USEBASHRC=t command ssh "$@"
2753 }
2754
2755
2756 slog() {
2757 # log with script. timing is $1.t and script is $1.s
2758 # -l to save to ~/typescripts/
2759 # -t to add a timestamp to the filenames
2760 local logdir do_stamp arg_base
2761 (( $# >= 1 )) || { echo "arguments wrong"; return 1; }
2762 logdir="/a/dt/"
2763 do_stamp=false
2764 while getopts "lt" option
2765 do
2766 case $option in
2767 l) arg_base=$logdir ;;
2768 t) do_stamp=true ;;
2769 *)
2770 echo error: bad option
2771 return 1
2772 ;;
2773 esac
2774 done
2775 shift $((OPTIND - 1))
2776 arg_base+=$1
2777 [[ -e $logdir ]] || mkdir -p $logdir
2778 $do_stamp && arg_base+=$(date +%F.%T%z)
2779 script -t $arg_base.s 2> $arg_base.t
2780 }
2781 splay() { # script replay
2782 #logRoot="$HOME/typescripts/"
2783 #scriptreplay "$logRoot$1.t" "$logRoot$1.s"
2784 scriptreplay "$1.t" "$1.s"
2785 }
2786
2787 sr() {
2788 # sudo redo. be aware, this command may not work right on strange distros or earlier software
2789 if [[ $# == 0 ]]; then
2790 sudo -E bash -c -l "$(history -p '!!')"
2791 else
2792 echo this command redos last history item. no argument is accepted
2793 fi
2794 }
2795
2796 srm () {
2797 # with -ll, less secure but faster.
2798 command srm -ll "$@"
2799 }
2800
2801 srun() {
2802 scp $2 $1:/tmp
2803 ssh $1 "/tmp/${2##*/}" "$(printf "%q\n" "${@:2}")"
2804 }
2805
2806
2807 swap() {
2808 local tmp
2809 tmp=$(mktemp)
2810 mv $1 $tmp
2811 mv $2 $1
2812 mv $tmp $2
2813 }
2814
2815 tclock() { # terminal clock
2816 local x
2817 clear
2818 date +%l:%_M
2819 len=60
2820 # this goes to full width
2821 #len=${1:-$((COLUMNS -7))}
2822 x=1
2823 while true; do
2824 if (( x == len )); then
2825 end=true
2826 d="$(date +%l:%_M) "
2827 else
2828 end=false
2829 d=$(date +%l:%M:%_S)
2830 fi
2831 echo -en "\r"
2832 echo -n "$d"
2833 for ((i=0; i<x; i++)); do
2834 if (( i % 6 )); then
2835 echo -n _
2836 else
2837 echo -n .
2838 fi
2839 done
2840 if $end; then
2841 echo
2842 x=1
2843 else
2844 x=$((x+1))
2845 fi
2846 sleep 5
2847 done
2848 }
2849
2850
2851 te() {
2852 # test existence / exists
2853 local ret=0
2854 for x in "$@"; do
2855 [[ -e "$x" || -L "$x" ]] || ret=1
2856 done
2857 return $ret
2858 }
2859
2860 psoff() {
2861 # normally, i would just execute these commands in the function.
2862 # however, DEBUG is not inherited, so we need to run it outside a function.
2863 # And we want to run set -x afterwards to avoid spam, so we cram everything
2864 # in here, and then it will run after this function is done.
2865 # # set as array to satisfy shellcheck, but it is equivalent to setting it as non-array
2866 PROMPT_COMMAND=('trap DEBUG; unset PROMPT_COMMAND; PS1="\w \$ "')
2867 }
2868 pson() {
2869 PROMPT_COMMAND=(prompt-command)
2870 if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
2871 trap 'auto-window-title "$BASH_COMMAND"' DEBUG
2872 fi
2873 }
2874
2875 # prometheus node curl
2876 pnodecurl() {
2877 local host
2878 host=${1:-127.0.0.1}
2879 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
2880 }
2881
2882 tx() { # toggle set -x, and the prompt so it doesnt spam
2883 if [[ $- == *x* ]]; then
2884 set +x
2885 pson
2886 else
2887 psoff
2888 fi
2889 }
2890
2891 psnetns() {
2892 # show all processes in the network namespace $1.
2893 # blank entries appear to be subprocesses/threads
2894 local x netns
2895 netns=$1
2896 ps -w | head -n 1
2897 sudo find -L /proc/[1-9]*/task/*/ns/net -samefile /run/netns/$netns | cut -d/ -f5 | \
2898 while read -r l; do
2899 x=$(ps -w --no-headers -p $l);
2900 if [[ $x ]]; then echo "$x"; else echo $l; fi;
2901 done
2902 }
2903 nonet() {
2904 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2905 s ip netns add nonet
2906 fi
2907 sudo -E env /sbin/ip netns exec nonet sudo -E -u iank /bin/bash
2908 }
2909
2910 m() { printf "%s\n" "$*"; "$@"; }
2911 m2() { printf "%s\n" "$*" >&2; "$@"; }
2912
2913 # update file. note: duplicated in mail-setup.
2914 # updates $ur u result to true or false
2915 # updates $reload to true if file updated is in /etc/systemd/system
2916 u() {
2917 local tmp tmpdir dest="$1"
2918 local base="${dest##*/}"
2919 local dir="${dest%/*}"
2920 if [[ $dir != "$base" ]]; then
2921 # dest has a directory component
2922 mkdir -p "$dir"
2923 fi
2924 # shellcheck disable=SC2034 # see comment at top of function
2925 ur=false # u result
2926 tmpdir="$(mktemp -d)"
2927 cat >$tmpdir/"$base"
2928 tmp=$(rsync -ic $tmpdir/"$base" "$dest")
2929 if [[ $tmp ]]; then
2930 printf "%s\n" "$tmp"
2931 # shellcheck disable=SC2034 # see comment at top of function
2932 ur=true
2933 if [[ $dest == /etc/systemd/system/* ]]; then
2934 # shellcheck disable=SC2034 # see comment at top of function
2935 reload=true
2936 fi
2937 fi
2938 rm -rf $tmpdir
2939 }
2940
2941
2942 uptime() {
2943 if type -p uprecords &>/dev/null; then
2944 uprecords -B
2945 else
2946 command uptime
2947 fi
2948 }
2949
2950 virshrm() {
2951 for x in "$@"; do virsh destroy "$x"; virsh undefine "$x"; done
2952 }
2953
2954 vm-set-listen(){
2955 local t
2956 t=$(mktemp)
2957 local vm=$1
2958 local ip=$2
2959 sudo virsh dumpxml $vm | sed -r "s/(<listen.*address=')([^']+)/\1$ip/" | \
2960 sed -r "s/listen='[^']+/listen='$ip/"> $t
2961 sudo virsh undefine $vm
2962 sudo virsh define $t
2963 }
2964
2965
2966 vmshare() {
2967 vm-set-listen $1 0.0.0.0
2968 }
2969
2970
2971 vmunshare() {
2972 vm-set-listen $1 127.0.0.1
2973 }
2974
2975 myiwscan() {
2976 local i
2977 interfaces=$(iw dev | awk '$1 == "Interface" {print $2}')
2978 for i in $interfaces; do
2979 echo "myiwscan: considering $i"
2980 # find input, copy to pattern space, when we find the first field, print the copy in different order without newlines.
2981 # instead of using labels, we could just match a line and group, eg: /signal:/,{s/signal:(.*)/\1/h}
2982 sudo iw dev $i scan | sed -rn "
2983 s/^\Wcapability: (.*)/\1/;Ta;h;b
2984 :a;s/^\Wsignal: -([^.]+).*/\1/;Tb;H;b
2985 # padded to min width of 20
2986 :b;s/\WSSID: (.*)/\1 /;T;s/^(.{20}(.*[^ ])?) */\1/;H;g;s/(.*)\n(.*)\n(.*)/\2 \3 \1/gp;b
2987 "|sort -r
2988 done
2989 }
2990
2991 # Run script by copying it to a temporary location first,
2992 # and changing directory, so we don't have any open
2993 # directories or files that could cause problems when
2994 # remounting.
2995 zr() {
2996 local tmp
2997 tmp=$(type -p "$1")
2998 if [[ $tmp ]]; then
2999 cd "$(mktemp -d)"
3000 cp -a "$tmp" .
3001 shift
3002 ./"${tmp##*/}" "$@"
3003 else
3004 "$@"
3005 fi
3006 }
3007
3008
3009 # * spark
3010 # spark 1 5 22 13 53
3011 # # => ▁▁▃▂▇
3012
3013 # The MIT License
3014 # Copyright (c) Zach Holman, https://zachholman.com
3015 # https://github.com/holman/spark
3016
3017 # As of 2022-10-28, I reviewed github forks that had several newer
3018 # commits, none had anything interesting. I did a little refactoring
3019 # mostly to fix emacs indent bug.
3020
3021 # Generates sparklines.
3022 _spark_echo()
3023 {
3024 if [ "X$1" = "X-n" ]; then
3025 shift
3026 printf "%s" "$*"
3027 else
3028 printf "%s\n" "$*"
3029 fi
3030 }
3031
3032
3033 spark()
3034 {
3035 local f tc
3036 local n numbers=
3037
3038 # find min/max values
3039 local min=0xffffffff max=0
3040
3041 for n in ${@//,/ }
3042 do
3043 # on Linux (or with bash4) we could use `printf %.0f $n` here to
3044 # round the number but that doesn't work on OS X (bash3) nor does
3045 # `awk '{printf "%.0f",$1}' <<< $n` work, so just cut it off
3046 n=${n%.*}
3047 (( n < min )) && min=$n
3048 (( n > max )) && max=$n
3049 numbers=$numbers${numbers:+ }$n
3050 done
3051
3052 # print ticks
3053 local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █)
3054
3055 # use a high tick if data is constant
3056 (( min == max )) && ticks=(▅ ▆)
3057
3058 tc=${#ticks[@]}
3059 f=$(( ( (max-min) <<8)/( tc - 1) ))
3060 (( f < 1 )) && f=1
3061
3062 for n in $numbers
3063 do
3064 _spark_echo -n ${ticks[$(( (((n-min)<<8)/f) ))]}
3065 done
3066 _spark_echo
3067 }
3068
3069 pdfwc() { local f; for f; do echo "$f" "$(pdfinfo "$f" | awk '/^Pages:/ {print $2}')"; done }
3070
3071
3072 # nvm install script appended this to my .bashrc. I dont want to run it all the time,
3073 # so put it in a function.
3074 nvm-init() {
3075 export NVM_DIR="$HOME/.nvm"
3076 # shellcheck disable=SC1091 # may not exist, & third party
3077 [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" # This loads nvm
3078 # shellcheck disable=SC1091 # may not exist, & third party
3079 [ -s "$NVM_DIR/bash_completion" ] && source "$NVM_DIR/bash_completion" # This loads nvm bash_completion
3080 }
3081
3082
3083 leap-year() {
3084 if date -d 'february 29' &>/dev/null; then
3085 year_days=366
3086 else
3087 year_days=365
3088 fi
3089 echo $year_days
3090 }
3091
3092 # on-battery
3093 on-bat() {
3094 if [[ -e /sys/class/power_supply/AC/online && $(</sys/class/power_supply/AC/online) == 0 ]]; then
3095 return 0
3096 else
3097 return 1
3098 fi
3099 }
3100
3101 # make vim work with my light colortheme terminal.
3102 vim() {
3103 if [[ -e ~/.vimrc ]]; then
3104 command vim "$@"
3105 else
3106 command vim -c ':colorscheme peachpuff' "$@"
3107 fi
3108 }
3109
3110 # ls count. usage: pass a directory, get the number of files.
3111 # https://unix.stackexchange.com/questions/90106/whats-the-most-resource-efficient-way-to-count-how-many-files-are-in-a-director
3112 lsc() {
3113 # shellcheck disable=SC2790 disable=SC2012 # intentional
3114 ls -Uq "$@"|wc -l
3115 }
3116
3117 # run then notify. close notification after the next prompt.
3118 rn() {
3119 "$@"
3120 dunstify -u critical -h string:x-dunst-stack-tag:profanity "$*"
3121 _psrun=(dunstctl close-all)
3122 }
3123 n() {
3124 dunstify -u critical -h string:x-dunst-stack-tag:profanity n
3125 _psrun=(dunstctl close-all)
3126 }
3127
3128 catnew() {
3129 local dir file
3130 dir="$1"
3131 inotifywait -m "$dir" -e create -e moved_to | while read -r _ _ file; do
3132 hr
3133 cat "$dir/$file"
3134 done
3135 }
3136 # cat mail
3137 cm() {
3138 catnew /m/md/$1/new
3139 }
3140
3141
3142 fsf-sv-header() {
3143 local f
3144 local -a f_maybe
3145 if ! type -p sponge &>/dev/null; then
3146 echo "$0: error: missing dependency: sudo apt install moreutils" >&2
3147 return 1
3148 fi
3149
3150 for f; do
3151 echo "adding header to $f"
3152 if [[ -s $f ]]; then
3153 f_maybe=("$f")
3154 else
3155 f_maybe=()
3156 fi
3157 cat - "${f_maybe[@]}" <<EOF | sponge "$f"
3158 The following is the GNU All-permissive License as recommended in
3159 <https://www.gnu.org/licenses/license-recommendations.en.html>
3160
3161 Copyright (C) $(date +%Y) Free Software Foundation <sysadmin@fsf.org>
3162
3163 Copying and distribution of this file, with or without modification,
3164 are permitted in any medium without royalty provided the copyright
3165 notice and this notice are preserved. This file is offered as-is,
3166 without any warranty.
3167
3168 Contributions are welcome. See <https://savannah.gnu.org/maintenance/fsf/>.
3169
3170 EOF
3171 done
3172 }
3173
3174 # note, there is also the tool gron which is meant for this, but
3175 # this is good enough to not bother installing another tool
3176 jq-lines() {
3177 # https://stackoverflow.com/questions/59700329/how-to-print-path-and-key-values-of-json-file-using-jq
3178 jq --stream -r 'select(.[1]|scalars!=null) | "\(.[0]|join(".")): \(.[1]|tojson)"' "$@"
3179 }
3180
3181 tsr() { # ts run
3182 "$@" |& ts || return $?
3183 }
3184
3185
3186 # * misc stuff
3187
3188
3189 if $use_color && type -p tput &>/dev/null; then
3190 # this is nice for a dark background terminal:
3191 # https://github.com/trapd00r/LS_COLORS
3192 # I would like if there was something similar for light.
3193
3194 # https://www.bigsoft.co.uk/blog/2008/04/11/configuring-ls_colors
3195 # change the hard to read turqouise.
3196 # defaults dircolors --print-database.
3197
3198 # the default bold green is too light.
3199 # this explains the codes: https://gist.github.com/thomd/7667642
3200 export LS_COLORS="ex=1:ln=00;31"
3201
3202 term_bold="$(tput bold)"
3203 term_red="$(tput setaf 1)"
3204 term_green="$(tput setaf 2)"
3205 # shellcheck disable=SC2034 # expected
3206 term_yellow="$(tput setaf 3)"
3207 term_purple="$(tput setaf 5)"
3208 term_nocolor="$(tput sgr0)" # no font attributes
3209
3210 # unused so far. commented for shellcheck
3211 # term_underl="$(tput smul)"
3212 # term_blue="$(tput setaf 4)"
3213 # term_cyan="$(tput setaf 6)"
3214 fi
3215 # Try to keep environment pollution down, EPA loves us.
3216 unset safe_term match_lhs use_color
3217
3218 # * prompt
3219
3220
3221 if [[ $- == *i* ]]; then
3222
3223
3224 case $HOSTNAME in
3225 bk|je|li)
3226 if [[ $EUID == 1000 ]]; then
3227 system-status _ ||:
3228 fi
3229 ;;
3230 esac
3231
3232
3233 # this needs to come before next ps1 stuff
3234 # this stuff needs bash 4, feb 2009,
3235 # old enough to no longer condition on $BASH_VERSION anymore
3236 shopt -s autocd
3237 shopt -s dirspell
3238 PS1='\w'
3239 if [[ $- == *i* ]] && [[ ! $LC_INSIDE_EMACS ]]; then
3240 PROMPT_DIRTRIM=2
3241 bind -m vi-command B:shell-backward-word
3242 bind -m vi-command W:shell-forward-word
3243 fi
3244
3245 if [[ $SSH_CLIENT || $SUDO_USER ]]; then
3246 unset PROMPT_DIRTRIM
3247 PS1="\h:$PS1"
3248 fi
3249
3250 # emacs terminal has problems if this runs slowly,
3251 # so I've thrown a bunch of things at the wall to speed it up.
3252 prompt-command() {
3253 local return=$? # this MUST COME FIRST
3254
3255 # all usable colors:
3256 # black
3257 # green nonzero exit (pri 1)
3258 # purple default
3259 # purple bold
3260 # red pwd different owner & group & not writable (pri 2)
3261 # red bold pwd different owner & group & writable (pri 2)
3262 # yellow
3263
3264 local ps_char ps_color
3265 unset IFS
3266
3267 if [[ $HISTFILE ]]; then
3268 history -a # save history
3269 fi
3270
3271 ps_color="$term_purple"
3272 ps_char='\$'
3273 if [[ ! -O . ]]; then # not owner
3274 if [[ -w . ]]; then # writable
3275 ps_color="$term_bold$term_red"
3276 else
3277 ps_color="$term_red"
3278 fi
3279 fi
3280
3281 if [[ $return != 0 ]]; then
3282 ps_color="$term_green"
3283 ps_char="$return \\$"
3284 fi
3285
3286 # faster than sourceing the file im guessing
3287 if [[ -e /dev/shm/iank-status && ! -e /tmp/quiet-status ]]; then
3288 eval "$(< /dev/shm/iank-status)"
3289 fi
3290 if [[ $MAIL_HOST && $MAIL_HOST != "$HOSTNAME" ]]; then
3291 ps_char="@ $ps_char"
3292 fi
3293 jobs_char=
3294 if [[ $(jobs -p) ]]; then
3295 jobs_char='j\j '
3296 fi
3297
3298
3299 # allow a function to specify a command to run after we run the next
3300 # command. Use case: a function makes a persistent notification. If
3301 # we happen to be using that terminal, we can just keep working by
3302 # entering our next command, even a noop in order to dismiss the
3303 # notification, instead of having to explicitly dismiss it.
3304 if [[ ${_psrun[*]} ]]; then
3305 if (( _psrun_count >= 1 )); then
3306
3307 "${_psrun[@]}" ||:
3308 _psrun_count=0
3309 unset _psrun
3310 else
3311 _psrun_count=$(( _psrun_count + 1 ))
3312 fi
3313 else
3314 _psrun_count=0
3315 fi
3316
3317 # We could test if sudo is active with sudo -nv
3318 # but then we get an email and log of lots of failed sudo commands.
3319 # We could turn those off, but seems better not to.
3320 if [[ $EUID != 0 ]] && [[ $DID_SUDO ]]; then
3321 psudo="\[$term_bold$term_red\]s\[$term_nocolor\] "
3322 fi
3323 if [[ ! $HISTFILE ]]; then
3324 ps_char="NOHIST $ps_char"
3325 fi
3326 PS1="${PS1%"${PS1#*[wW]}"} $jobs_char$psudo\[$ps_color\]$ps_char\[$term_nocolor\] "
3327
3328 # copy of what is automatically added by guix.
3329 # adds [env] to PS1 if GUIX_ENVIRONMENT is set and PS1 contains '$';
3330 if [ -n "$GUIX_ENVIRONMENT" ]; then
3331 if [[ $PS1 =~ (.*)"\\$" ]]; then
3332 PS1="${BASH_REMATCH[1]} [env]\\\$ "
3333 fi
3334 fi
3335
3336
3337 # set titlebar. instead, using more advanced
3338 # titelbar below
3339 #echo -ne "$_title_escape $HOSTNAME ${PWD/#$HOME/~} \007"
3340 }
3341 PROMPT_COMMAND=(prompt-command)
3342
3343 if [[ $TERM == screen* ]]; then
3344 _title_escape="\033]..2;"
3345 else
3346 # somme sites recommend this, i dunno what the diff is.
3347 #_title_escape="\033]30;"
3348 _title_escape="\033]0;"
3349 fi
3350
3351 # make the titlebar be the last command and the current directory.
3352 auto-window-title () {
3353
3354
3355 # These are some checks to help ensure we dont set the title at
3356 # times that the debug trap is running other than the case we
3357 # want. Some of them might not be needed.
3358 if (( ${#FUNCNAME[@]} != 1 || ${#BASH_ARGC[@]} != 2 || BASH_SUBSHELL != 0 )); then
3359 return 0
3360 fi
3361 if [[ $1 == prompt-command ]]; then
3362 return 0
3363 fi
3364 echo -ne "$_title_escape ${PWD/#$HOME/~} "
3365 printf "%s" "$*"
3366 echo -ne "\007"
3367 }
3368
3369 # note, this wont work:
3370 # x=$(mktemp); cp a $x
3371 # I havnt figured out why, bigger fish to fry.
3372 #
3373 # for titlebar.
3374 # condition from the screen man page i think.
3375 # note: duplicated in tx()
3376 if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
3377 trap 'auto-window-title "$BASH_COMMAND"' DEBUG
3378 else
3379 trap DEBUG
3380 fi
3381
3382 fi
3383
3384 # * stuff that makes sense to be at the end
3385
3386
3387 # best practice
3388 unset IFS
3389
3390 if [[ -s "$HOME/.rvm/scripts/rvm" ]]; then
3391 # shellcheck disable=SC1091
3392 source "$HOME/.rvm/scripts/rvm"
3393 fi
3394
3395 # I had this idea to start a bash shell which would run an initial
3396 # command passed through this env variable, then continue on
3397 # interactively. But the use case I had in mind went away.
3398 #
3399 # if [[ $MY_INIT_CMD ]]; then
3400 # "${MY_INIT_CMD[@]}"
3401 # unset MY_INIT_CMD
3402 # fi
3403
3404 # ensure no bad programs appending to this file will have an affect
3405 return 0