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