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