various improvements
[distro-setup] / brc
1 #!/bin/bash
2 # Copyright (C) 2019 Ian Kelling
3 # SPDX-License-Identifier: AGPL-3.0-or-later
4 # this gets sourced. shebang is just for file mode detection
5
6 # Use source ~/.bashrc instead of doing bash -l when running a script
7 # so this can set extdebug and avoid the bash debugger.
8 if [[ -s /a/bin/errhandle/err ]]; then
9 source /a/bin/errhandle/err
10 elif [[ -s $bashrc_dir/err ]]; then
11 # shellcheck source=/a/bin/errhandle/err
12 source $bashrc_dir/err
13 fi
14
15 # In t8, it runs clear_console for login shells by default. I don't want
16 # my console cleared. And linux ttys get cleared without this.
17 if shopt login_shell >/dev/null && [[ -e ~/.bash_logout ]]; then
18 rm ~/.bash_logout
19 fi
20
21 # if [[ -s /usr/share/bash-completion/completions/git ]]; then
22 # source /usr/share/bash-completion/completions/git
23 # fi
24 # if [[ -s /usr/share/bash-completion/completions/gitk ]]; then
25 # source /usr/share/bash-completion/completions/gitk
26 # fi
27
28 # for testing error catching:
29 # t2() {
30 # echo t2
31 # grep sdf sdfd
32 # echo wtf
33 # }
34 # t1() {
35 # echo t1
36 # t2 a b c
37 # }
38
39 # * settings
40
41 CDPATH=.
42
43
44 # remove all aliases. aliases provided by the system tend to get in the way,
45 # for example, error happens if I try to define a function the same name as an alias
46 unalias -a
47
48 # remove gnome keyring warning messages
49 # there is probably a more proper way, but I didnt find any easily on google
50 # now using xfce+xmonad instead of vanilla xmonad, so disabling this
51 #unset GNOME_KEYRING_CONTROL
52
53 # use extra globing features.
54 shopt -s extglob
55 # include .files when globbing, but ignore files name . and ..
56 # setting this also sets dotglob.
57 export GLOBIGNORE="*/.:*/.."
58
59 # Useful info. see man bash.
60 PS4='$LINENO+ '
61
62
63 # broken with bash_completion package. Saw a bug for this once. dont anymore.
64 # still broken in wheezy
65 # still buggered in latest stable from the web, version 2.1
66 # perhaps its fixed in newer git version, which fails to make for me
67 # this note is from 6-2014.
68 # still broken in flidas.
69 #shopt -s nullglob
70
71 # make tab on an empty line do nothing
72 shopt -s no_empty_cmd_completion
73
74 # fix spelling errors for cd, only in interactive shell
75 shopt -s cdspell
76 # append history instead of overwritting it
77 shopt -s histappend
78 # for compatibility, per gentoo/debian bashrc
79 shopt -s checkwinsize
80 # attempt to save multiline single commands as single history entries.
81 shopt -s cmdhist
82 # enable **
83 shopt -s globstar
84
85
86 # inside emacs fixes
87 if [[ $LC_INSIDE_EMACS ]]; then
88 # EMACS is used by bash on startup, but we dont need it anymore.
89 # plus I hit a bug in a makefile which inherited it
90 unset EMACS
91 export LC_INSIDE_EMACS
92 export PAGER=cat
93 export MANPAGER=cat
94 # scp completion does not work, but this doesnt fix it. todo, figure this out
95 #complete -r scp &> /dev/null
96 # todo, remote file completion fails, figure out how to turn it off
97 export NODE_DISABLE_COLORS=1
98 # This gets rid of ugly terminal escape chars in node repl
99 # sometime, Id like to have completion working in emacs shell for node
100 # the offending chars can be found in lib/readline.js,
101 # things that do like:
102 # stream.write('\x1b[' + (x + 1) + 'G');
103 # We can remove them and keep readline, for example by doing this
104 # to start a repl:
105 #!/usr/bin/env nodejs
106 # var readline = require('readline');
107 # readline.cursorTo = function(a,b,c) {};
108 # readline.clearScreenDown = function(a) {};
109 # const repl = require('repl');
110 # var replServer = repl.start('');
111 #
112 # no prompt, or else readline complete seems to be confused, based
113 # on our column being different? node probably needs to send
114 # different kind of escape sequence that is not ugly. Anyways,
115 # completion doesnt work yet even with the ugly prompt, so whatever
116 #
117 export NODE_NO_READLINE=1
118
119 fi
120
121 export SSH_CONFIG_FILE_OVERRIDE=/root/.ssh/confighome
122
123 # emacs has a different default search path than the info command. This
124 # adds the info defaults to emacs, but not the reverse, because I dun
125 # care much about the cli. The search path is only on the cli if you run
126 # "info xxx", or in emacs if you run '(info xxx)', so not that
127 # important, but might as well fix it.
128
129 # info info says this path is what was compiled, and its not documented
130 # anywhere. Through source grepping, i found it in filesys.h of the info
131 # source in trisquel flidas.
132 #
133 # Traling : means for emacs to add its own stuff on to the end.
134
135 export INFOPATH=$PATH:/usr/local/info:/usr/info:/usr/local/lib/info:/usr/lib/info:/usr/local/gnu/info:/usr/local/gnu/lib/info:/usr/gnu/info:/usr/gnu/lib/info:/opt/gnu/info:/usr/share/info:/usr/share/lib/info:/usr/local/share/info:/usr/local/share/lib/info:/usr/gnu/lib/emacs/info:/usr/local/gnu/lib/emacs/info:/usr/local/lib/emacs/info:/usr/local/emacs/info:.:
136
137 # for openwrt system that has no stty, this is easier than
138 # guarding every time i use it.
139 if ! type -p stty >/dev/null; then
140 stty() { :; }
141 fi
142
143
144 if [[ $- == *i* ]]; then
145 # for readline-complete.el
146 if [[ $LC_INSIDE_EMACS ]]; then
147 # all for readline-complete.el
148 stty echo
149 bind 'set horizontal-scroll-mode on'
150 bind 'set print-completions-horizontally on'
151 bind '"\C-i": self-insert'
152 else
153
154 if [[ $KONSOLE_PROFILE_NAME ]]; then
155 TERM=xterm-256color
156 fi
157
158 if [[ $TERM == alacritty && ! -e /usr/share/terminfo/a/alacritty ]]; then
159 # todo: we should try installing the alacritty terminfo if it is not found
160 # https://github.com/alacritty/alacritty/issues/2838
161 TERM=xterm-256color
162 fi
163
164 # copying from the alacritty example above,
165 if [[ $TERM == xterm-kitty ]]; then
166 if [[ ! -e /usr/share/terminfo/x/xterm-kitty ]]; then
167 TERM=xterm-256color
168 else
169 if [[ -e /a/opt/kitty/shell-integration/bash/kitty.bash ]]; then
170 KITTY_SHELL_INTEGRATION=t
171 source /a/opt/kitty/shell-integration/bash/kitty.bash
172 fi
173 fi
174 fi
175
176 # todo: not sure this works in sakura
177 #stty werase undef
178 #bind "\C-w": kill-region
179 # sakura == xterm-256color
180 # konsole == xterm
181 if [[ $TERM != xterm-kitty && $TERM == xterm* ]]; then
182 # control + arrow keys. for other terminals, see http://unix.stackexchange.com/questions/10806/how-to-change-previous-next-word-shortcut-in-bash
183 bind '"\e[1;5C": shell-forward-word' 2>/dev/null
184 bind '"\e[1;5D": shell-backward-word' 2>/dev/null
185 else
186 # make ctrl-backspace work. for konsole, i fixed it through
187 # /home/iank/.local/share/konsole/default.keytab
188 stty werase ^h
189 bind '"\eOc": shell-forward-word'
190 bind '"\eOd": shell-backward-word'
191 fi
192 # i cant remember why i did this, probably to free up some keys to bind
193 # to other things in bash.
194 # other than C-c and C-z, the rest defined by stty -a are, at least in
195 # gnome-terminal, overridden by bash, or disabled by the system
196 stty lnext undef stop undef start undef
197 fi
198
199 fi
200
201 export BC_LINE_LENGTH=0
202
203 # ansible option
204 export PROFILE_TASKS_TASK_OUTPUT_LIMIT=100
205
206 # note, if I use a machine I dont want files readable by all users, set
207 # umask 077 # If fewer than 4 digits are entered, leading zeros are assumed
208
209 # i for insensitive. the rest from
210 # X means dont remove the current screenworth of output upon exit
211 # R means to show colors n things
212 export LESS=RXij12
213 export SYSTEMD_LESS=$LESS
214
215 export NNN_COLORS=2136
216
217 export SL_FILES_DIR=/b/ds/sl/.iank
218 export SL_INFO_DIR=/p/sshinfo
219
220
221 # * include files
222
223 if [[ -s $bashrc_dir/path-add-function ]]; then
224 source $bashrc_dir/path-add-function
225 if [[ $SSH_CLIENT ]]; then
226 # [[ -d /home/iank/.iank/e/e ]] mounts it unnecessarily, so use this.
227 if grep -qF /home/iank/.iank/e/e /etc/auto.iank /etc/exports &>/dev/null; then
228 export EMACSDIR=/home/iank/.iank/e/e
229 fi
230 path-add $bashrc_dir
231 fi
232 fi
233
234 # if someone exported $SOE (stop on error), catch errors.
235 #
236 # Note, on debian this results in the following warning when in ssh,
237 # hich I haven't figured out how to fix. It doesn't happen if we source
238 # after the shell has started
239 #
240 # bash: /usr/share/bashdb/bashdb-main.inc: No such file or directory
241 # bash: warning: cannot start debugger; debugging mode disabled
242 if [[ $SOE ]]; then
243 if [[ -e /a/bin/errhandle/err ]]; then
244 source /a/bin/errhandle/err
245 fi
246 fi
247
248 # based on readme.debian. dunno if this will break on other distros.
249 if [[ -s /usr/share/wcd/wcd-include.sh ]]; then
250 source /usr/share/wcd/wcd-include.sh
251 fi
252
253
254 mysrc() {
255 local path dir file
256 path=$1
257 dir=${path%/*}
258 file=${path##*/}
259 if [[ -s $path ]]; then
260 source $path
261 elif [[ -s $bashrc_dir/$file ]]; then
262 source $bashrc_dir/$file
263 fi
264 }
265
266
267 mysrc /a/bin/small-misc-bash/ll-function
268 mysrc /a/bin/distro-functions/src/package-manager-abstractions
269
270
271 # * functions
272 ccomp() { # copy completion
273 local src=$1
274 local c
275 shift
276 if ! c=$(complete -p $src 2>/dev/null); then
277 _completion_loader $src &>/dev/null ||:
278 c=$(complete -p $src 2>/dev/null) || return 0
279 fi
280 # remove $src( .*|$)
281 c=${c% $src}
282 c=${c%% $src *}
283 eval $c $*
284 }
285
286
287 ..() { c ..; }
288 ...() { c ../..; }
289 ....() { c ../../..; }
290 .....() { c ../../../..; }
291 ......() { c ../../../../..; }
292
293 chere() {
294 local f path
295 for f; do
296 path=$(readlink -e "$f")
297 echo "cat >$path <<'EOF'"
298 cat "$f"
299 echo EOF
300 done
301 }
302
303
304 # file cut copy and paste, like the text buffers :)
305 # I havnt tested these.
306 _fbufferinit() { # internal use
307 ! [[ $my_f_tempdir ]] && my_f_tempdir=$(mktemp -d)
308 rm -rf "${my_f_tempdir:?}"/*
309 }
310 fcp() { # file cp
311 _fbufferinit
312 cp "$@" "$my_f_tempdir"/
313 }
314 fct() { # file cut
315 _fbufferinit
316 mv "$@" "$my_f_tempdir"/
317 }
318 fpst() { # file paste
319 [[ $2 ]] && { echo too many arguments; return 1; }
320 target=${1:-.}
321 cp "$my_f_tempdir"/* "$target"
322 }
323
324 _khfix_common() {
325 local host ip port file key
326 read -r host ip port < <(timeout -s 9 2 ssh -oBatchMode=yes -oControlMaster=no -oControlPath=/ -v $1 |& sed -rn "s/debug1: Connecting to ([^ ]+) \[([^\]*)] port ([0-9]+).*/\1 \2 \3/p" ||: )
327 file=$(readlink -f ~/.ssh/known_hosts)
328 if [[ ! $ip ]]; then
329 echo "khfix: ssh failed"
330 return 1
331 fi
332 if [[ $port != 22 ]]; then
333 ip_entry="[$ip]:$port"
334 host_entry="[$host]:$port"
335 else
336 ip_entry=$ip
337 host_entry=$host
338 fi
339 tmpfile=$(mktemp)
340 if [[ $host != $ip ]]; then
341 key=$(ssh-keygen -F "$host_entry" -f $file | sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/')
342 if [[ $key ]]; then
343 grep -Fv "$key" "$file" | sponge "$file"
344 fi
345 fi
346 key=$(ssh-keygen -F "$ip_entry" -f $file | sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/')
347 if [[ $key ]]; then
348 grep -Fv "$key" "$file" | sponge "$file"
349 fi
350 ll ~/.ssh/known_hosts
351 rootsshsync
352 }
353 khfix() { # known hosts fix
354 _khfix_common "$@" || return 1
355 ssh $1 :
356 }
357 khcopy() {
358 _khfix_common "$@"
359 ssh-copy-id $1
360 }
361
362 a() {
363 local x
364 x=$(readlink -nf "${1:-$PWD}")
365 # yes, its kinda dumb that xclip/xsel cant do this in one invocation
366 echo -n "$x" | xclip -selection clipboard
367 echo -n "$x" | xclip
368 }
369
370 # a1 = awk {print $1}
371 for field in {1..20}; do
372 eval a$field"() { awk '{print \$$field}'; }"
373 done
374 # h1 = head -n1
375 for num in {1..9}; do
376 eval h$num"() { head -n$num; }"
377 done
378
379
380 b() {
381 # backwards
382 c -
383 }
384
385
386 # c. better cd
387 if type -p wcd &>/dev/null; then
388 if [[ $LC_INSIDE_EMACS ]]; then
389 c() { wcd -c -z 50 -o "$@"; }
390 else
391 # lets see what the fancy terminal does from time to time
392 c() { wcd -c -z 50 "$@"; }
393 fi
394 else
395 c() { cd "$@"; }
396 fi
397 ccomp cd c
398
399 c4() { c /var/log/exim4; }
400
401 caa() { git commit --amend --no-edit -a; }
402
403 cf() {
404 for f; do
405 hr
406 echo "$f"
407 hr
408 cat "$f"
409 done
410 }
411 caf() {
412 # shellcheck disable=SC2033
413 find -L "$@" -type f -not \( -name .svn -prune -o -name .git -prune \
414 -o -name .hg -prune -o -name .editor-backups -prune \
415 -o -name .undo-tree-history -prune \) \
416 -exec bash -c '. ~/.bashrc; hr; echo "$1"; hr; cat "$1"' _ {} \; 2>/dev/null
417
418 }
419 ccomp cat cf caf
420
421 calc() { echo "scale=3; $*" | bc -l; }
422 # no having to type quotes, but also no command history:
423 clc() {
424 local x
425 read -r x
426 echo "scale=3; $x" | bc -l
427 }
428
429 cam() {
430 git commit -am "$*"
431 }
432
433 ccat () { # config cat. see a config without extra lines.
434 sed -r '/^[[:space:]]*([;#]|--|\/\/|$)/d' "$@"
435 }
436 ccomp grep ccat
437
438 chrbind() {
439 local d
440 # dev/pts needed for pacman signature check
441 for d in dev proc sys dev/pts; do
442 [[ -d $d ]]
443 if ! mountpoint $d &>/dev/null; then
444 s mount -o bind /$d $d
445 fi
446 done
447 }
448
449
450 _cdiff-prep() {
451 # join options which are continued to multiples lines onto one line
452 local first=true
453 while IFS= read -r line; do
454 # remove leading spaces/tabs. assumes extglob
455 if [[ $line == "[ ]*" ]]; then
456 line="${line##+( )}"
457 fi
458 if $first; then
459 pastline="$line"
460 first=false
461 elif [[ $line == *=* ]]; then
462 echo "$pastline" >> "$2"
463 pastline="$line"
464 else
465 pastline="$pastline $line"
466 fi
467 done < <(grep -vE '^([ \t]*#|^[ \t]*$)' "$1")
468 echo "$pastline" >> "$2"
469 }
470
471 cdiff() {
472 # diff config files,
473 # setup for format of postfix, eg:
474 # option = stuff[,]
475 # [more stuff]
476 local pastline unified f1 f2
477 unified="$(mktemp)"
478 f1="$(mktemp)"
479 f2="$(mktemp)"
480 _cdiff-prep "$1" "$f1"
481 _cdiff-prep "$2" "$f2"
482 cat "$f1" "$f2" | grep -Po '^[^=]+=' | sort | uniq > "$unified"
483 while IFS= read -r line; do
484 # the default bright red / blue doesnt work in emacs shell
485 dwdiff -cblue,red -A best -d " ," <(grep "^$line" "$f1" || echo ) <(grep "^$line" "$f2" || echo ) | colordiff
486 done < "$unified"
487 }
488
489
490 cat-new-files() {
491 local start=$SECONDS
492 local dir="$1"
493 inotifywait -m "$dir" -e create -e moved_to |
494 # shellcheck disable=SC2030
495 while read -r filedir _ file; do
496 cat "$filedir$file"
497 hr
498 calc $((SECONDS - start)) / 60
499 sleep 5
500 done
501
502 }
503
504 # shellcheck disable=SC2032
505 chown() {
506 # makes it so chown -R symlink affects the symlink and its target.
507 if [[ $1 == -R ]]; then
508 shift
509 command chown -h "$@"
510 command chown -R "$@"
511 else
512 command chown "$@"
513 fi
514 }
515
516 cim() {
517 git commit -m "$*"
518 }
519
520 cl() {
521 # choose recent directory. cl = cd list
522 c =
523 }
524
525 d() { builtin bg "$@"; }
526 ccomp bg d
527
528 dc() {
529 diff --strip-trailing-cr -w "$@" # diff content
530 }
531 ccomp diff dc
532
533 despace() {
534 local x y
535 for x in "$@"; do
536 y="${x// /_}"
537 safe_rename "$x" "$y"
538 done
539 }
540
541 dig() {
542 command dig +nostats +nocmd "$@"
543 }
544 # Output with sections sorted, and removal of query id, so 2 dig outputs can be diffed.
545 digsort() {
546 local sec
547 sec=
548 dig +nordflag "$@" | sed -r 's/^(;; ->>HEADER<<-.*), id: .*/\1/' | while read -r l; do
549 if [[ $l == [^\;]* ]]; then
550 sec+="$l"$'\n'
551 else
552 if [[ $sec ]]; then
553 printf "%s" "$sec" | sort
554 sec=
555 fi
556 printf "%s\n" "$l"
557 fi
558 done
559 }
560 ccomp dig digsort
561 # compare digs to the 2 servers
562 # usage: digdiff @server1 @server2 DIG_ARGS
563 # note: only the soa master nameserver will respond with
564 # ra "recursive answer" flag. That difference is meaningless afaik.
565 digdiff() {
566 local s1 s2
567 s1=$1
568 shift
569 s2=$1
570 shift
571 digsort $s1 "$@" | tee /tmp/digdiff
572 diff -u /tmp/digdiff <(digsort $s2 "$@")
573 }
574
575 dt() {
576 date "+%A, %B %d, %r" "$@"
577 }
578 ccomp date dt
579
580 dus() { # du, sorted, default arg of
581 du -sh ${@:-*} | sort -h
582 }
583 ccomp du dus
584
585
586 e() { echo "$@"; }
587
588 # echo args
589 ea() {
590 if (( ! $# )); then
591 echo no args
592 fi
593 for arg; do
594 printf "%qEOL\n" "${arg}"
595 printf "%s" "${arg}" |& hexdump -C
596 done
597 }
598 # echo vars. print var including escapes, etc
599 ev() {
600 if (( ! $# )); then
601 echo no args
602 fi
603 for arg; do
604 if [[ -v $arg ]]; then
605 printf "%qEOL\n" "${!arg}"
606 printf "%s" "${!arg}" |& hexdump -C
607 else
608 echo arg $arg is unset
609 fi
610 done
611 }
612
613 ediff() {
614 [[ ${#@} == 2 ]] || { echo "error: ediff requires 2 arguments"; return 1; }
615 emacs --eval "(ediff-files \"$1\" \"$2\")"
616 }
617
618 # mail related
619 etail() {
620 tail -F /var/log/exim4/mainlog -n 200 "$@"
621 }
622 ccomp tail etail
623
624 # print exim old pids
625 eoldpids() {
626 local configtime pid piduptime now daemonpid
627 printf -v now '%(%s)T' -1
628 configtime=$(stat -c%Y /var/lib/exim4/config.autogenerated)
629 if [[ -s /run/exim4/exim.pid ]]; then
630 daemonpid=$(cat /run/exim4/exim.pid)
631 fi
632 for pid in $(pgrep -f '^/usr/sbin/exim4( |$)'); do
633 # the daemonpid gets reexeced on HUP (service reloads), keeping its same old timestamp
634 if [[ $pid == $daemonpid ]]; then
635 continue
636 fi
637 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
638 if (( configtime > now - piduptime )); then
639 echo $pid
640 fi
641 done
642 }
643
644 # exim tail but only watch lines from new pids
645 etailnew() {
646 local pid oldpids
647 for pid in $(eoldpids); do
648 oldpids+="$pid|"
649 done
650 if [[ $oldpids ]]; then
651 etail | awk '$3 !~ /^\[('"${oldpids%|}"')\]$/'
652 else
653 etail
654 fi
655 }
656 # exim watch as old pids go away
657 ewatchold() {
658 local configtime pid piduptime now
659 local -i count
660 local -a oldpids
661 count=0
662 while true; do
663 oldpids=($(eoldpids))
664 if (( ! ${#oldpids[@]} )); then
665 return
666 fi
667 # print the date every 20 iterations
668 if (( ! count % 20 )); then
669 date
670 fi
671 count+=1
672 ps -f -p "${oldpids[*]}"
673 sleep 1
674 done
675 }
676
677 eless() {
678 less /var/log/exim4/mainlog
679 }
680 ccomp less eless
681 eqcat() {
682 exiqgrep -i -o 60 | while read -r i; do
683 hlm exim -Mvc $i
684 echo
685 hlm exigrep $i /var/log/exim4/mainlog | cat ||:
686 done
687 }
688 eqrmf() {
689 exiqgrep -i | xargs exim -Mrm
690 }
691
692 econfdevnew() {
693 rm -rf /tmp/edev
694 mkdir -p /tmp/edev/etc
695 cp -ra /etc/exim4 /tmp/edev/etc
696 cp -ra /etc/alias* /tmp/edev/etc
697 find /tmp/edev/etc/exim4 -type f -execdir sed -i "s,/etc/,/tmp/edev/etc/,g" '{}' +
698 econfdev
699 }
700 econfdev() {
701 update-exim4.conf -d /tmp/edev/etc/exim4 -o /tmp/edev/e.conf
702 }
703
704
705
706 # shellcheck disable=SC2032
707 f() {
708 # cd forward
709 c +
710 }
711
712 fa() {
713 # find array. make an array of file names found by find into $x
714 # argument: find arguments
715 # return: find results in an array $x
716 while read -rd ''; do
717 x+=("$REPLY");
718 done < <(find "$@" -print0);
719 }
720
721 faf() { # find all files. use -L to follow symlinks
722 find "$@" -not \( -name .svn -prune -o -name .git -prune \
723 -o -name .hg -prune -o -name .editor-backups -prune \
724 -o -name .undo-tree-history -prune \) -type f 2>/dev/null
725 }
726
727 # todo: id like to do maybe a daily or hourly cronjob to
728 # check that my history file size is increasing. Ive had it
729 # inexplicably truncated in the past.
730 histrm() {
731 history -n
732 history | awk -v IGNORECASE=1 '{ a=$1; sub(/^( *[^ ]+){4} */, "") }; /'"$*"'/'
733 read -p "press anything but contrl-c to delete"
734 for entry in $(history | awk -v IGNORECASE=1 '{ a=$1; sub(/^( *[^ ]+){4} */, "") }; /'"$*"'/ { print a }' | tac); do
735 history -d $entry
736 done
737 history -w
738 }
739
740 # mail related
741 frozen() {
742 rm -rf /tmp/frozen
743 sudo mailq |gr frozen|awk '{print $3}' | while read -r id; do
744 sudo exim -Mvl $id
745 echo
746 sudo exim -Mvh $id
747 echo
748 sudo exim -Mvb $id
749 echo -e '\n\n##############################\n'
750 done | tee -a /tmp/frozen
751 }
752 frozenrm() {
753 local ids=()
754 while read -r line; do
755 printf '%s\n' "$line"
756 ids+=($(printf '%s\n' "$line" |gr frozen|awk '{print $3}'))
757 done < <(s mailq)
758 echo "sleeping for 2 in case you change your mind"
759 sleep 2
760 sudo exim -Mrm "${ids[@]}"
761 }
762
763 funce() {
764 # like -e for functions. returns on error.
765 # at the end of the function, disable with:
766 # trap ERR
767 trap 'echo "${BASH_COMMAND:+BASH_COMMAND=\"$BASH_COMMAND\" }
768 ${FUNCNAME:+FUNCNAME=\"$FUNCNAME\" }${LINENO:+LINENO=\"$LINENO\" }\$?=$?"
769 trap ERR
770 return' ERR
771 }
772
773 getdir () {
774 local help="Usage: getdir [--help] PATH
775 Output the directory of PATH, or just PATH if it is a directory."
776 if [[ $1 == --help ]]; then
777 echo "$help"
778 return 0
779 fi
780 if [[ $# -ne 1 ]]; then
781 echo "getdir error: expected 1 argument, got $#"
782 return 1
783 fi
784 if [[ -d $1 ]]; then
785 echo "$1"
786 else
787 local dir
788 dir="$(dirname "$1")"
789 if [[ -d $dir ]]; then
790 echo "$dir"
791 else
792 echo "getdir error: directory does not exist"
793 return 1
794 fi
795 fi
796 }
797
798 git_empty_branch() { # start an empty git branch. carefull, it deletes untracked files.
799 [[ $# == 1 ]] || { echo 'need a branch name!'; return 1;}
800 local root
801 root=$(gitroot) || return 1 # function to set gitroot
802 builtin cd "$root"
803 git symbolic-ref HEAD refs/heads/$1
804 rm .git/index
805 git clean -fdx
806 }
807
808 # shellcheck disable=SC2120
809 gitroot() {
810 local help="Usage: gitroot [--help]
811 Print the full path to the root of the current git repo
812
813 Handles being within a .git directory, unlike git rev-parse --show-toplevel,
814 and works in older versions of git which did not have that."
815 if [[ $1 == --help ]]; then
816 echo "$help"
817 return
818 fi
819 local p
820 p=$(git rev-parse --git-dir) || { echo "error: not in a git repo" ; return 1; }
821 [[ $p != /* ]] && p=$PWD
822 echo "${p%%/.git}"
823 }
824
825 g() {
826
827 # todo: patch emacs so it will look elsewhere. this is kinda sad:
828 # https://emacs.stackexchange.com/questions/4253/how-to-start-emacs-with-a-custom-user-emacs-directory
829
830 local args gdb=false
831
832 if [[ $EMACSDIR ]]; then
833 path-add "$EMACSDIR/lib-src" "$EMACSDIR/src"
834 fi
835
836 if [[ $DISPLAY ]]; then
837 args=-n
838 fi
839
840 if (( $# == 0 )); then
841 args+=" -c"
842 fi
843 # duplicate -c, but oh well
844 if ! pgrep -u $EUID emacsclient; then
845 if (( $# == 0 )) && type -p gdb &>/dev/null; then
846 gdb=true
847 else
848 args+=" -c"
849 fi
850 fi
851 if [[ $EMACSDIR ]]; then
852 # Alter the path here, otherwise the nfs mount gets triggered on the
853 # first path lookup when emacs is not being used.
854 PATH="$EMACSDIR/lib-src:$EMACSDIR/src:$PATH" EHOME=$HOME HOME=$EMACSDIR m emacsclient -a "" $args "$@"
855 else
856 if $gdb; then
857 # due to a bug, we cant debug from the start unless we get a new gdb
858 # https://sourceware.org/bugzilla/show_bug.cgi?id=24454
859 # m gdb -ex="set follow-fork-mode child" -ex=r -ex=quit --args emacs --daemon
860 m emacsclient -a "" $args "$@"
861 sleep 1
862 cd /a/opt/emacs-$(distro-name)$(distro-num)
863 s gdb -p $(pgrep -f 'emacs --daemon') -ex c
864 cd -
865 else
866 m emacsclient -a "" $args "$@"
867 fi
868 fi
869 }
870
871 # force terminal version
872 gn() {
873 g -n "$@"
874 }
875
876 gmacs() {
877 # quit will prompt if the program crashes.
878 gdb -ex=r -ex=quit --args emacs "$@"; r;
879 }
880
881 gdkill() {
882 # kill the emacs daemon
883 pk1 emacs --daemon
884 }
885
886 gr() {
887 grep -iIP --color=auto "$@" || return $?
888 }
889 grr() { # grep recursive
890 # Don't return 1 on nonmatch because this is meant to be
891 # interactive, not in a conditional.
892 if [[ ${#@} == 1 ]]; then
893 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" . || [[ $? == 1 ]]
894 else
895 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" || [[ $? == 1 ]]
896 fi
897 }
898 ccomp grep gr grr
899
900 rg() { grr "$@"; }
901 ccomp grep rg
902
903 hr() { # horizontal row. used to break up output
904 printf "$(tput setaf 5 2>/dev/null ||:)â–ˆ$(tput sgr0 2>/dev/null||:)%.0s" $(eval echo "{1..${COLUMNS:-60}}")
905 echo
906 }
907 # highlight
908 hl() {
909 local col input_len=0
910 for arg; do
911 input_len=$((input_len + 1 + ${#arg}))
912 done
913 col=$((60 - input_len))
914 printf "\e[1;97;41m%s" "$*"
915 if (( col > 0 )); then
916 printf "\e[1;97;41m \e[0m%.0s" $(eval echo "{1..${col}}")
917 fi
918 echo
919 }
920 hlm() { hl "$*"; "$@"; }
921
922 hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; done }
923
924
925 # get latest hub and run it
926 # main command to use:
927 # hub pull-request --no-edit
928 # --no-edit means to use the first commit\'s message as the pull request message.
929 # If that fails, try doing
930 # hub pull-request --no-edit -b UPSTREAM_OWNER:branch
931 # where branch is usually master. it does the pr against your current branch.
932 #
933 # On first use, you input username/pass and it gets an oath token so you dont have to repeat
934 # it\'s at ~/.config/hub
935 hub() {
936 local up uptar updir p
937 p=/github/hub/releases/
938 up=https://github.com/$(curl -s https://github.com$p| grep -o $p'download/[^/]*/hub-linux-amd64[^"]*' | head -n1)
939 uptar=${up##*/}
940 updir=${uptar%.tgz}
941 if [[ ! -e /a/opt/$updir ]]; then
942 rm -rf /a/opt/hub-linux-amd64*
943 wget -P /a/opt $up
944 tar -C /a/opt -zxf /a/opt/$uptar
945 rm -f /a/opt/$uptar
946 fi
947 if ! which hub &>/dev/null; then
948 sudo /a/opt/$updir/install
949 fi
950
951 # save token across computers
952 if [[ ! -L ~/.config/hub ]]; then
953 if [[ -e ~/.config/hub ]]; then
954 mv ~/.config/hub /p/c/subdir_files/.config/
955 fi
956 if [[ -e /p/c/subdir_files/.config/hub ]]; then
957 conflink
958 fi
959 fi
960 command hub "$@"
961 }
962
963 i() { git "$@"; }
964 ccomp git i
965
966 ic() {
967 # fast commit all
968 git commit -am "$*"
969 }
970
971
972 ifn() {
973 # insensitive find
974 find -L . -not \( -name .svn -prune -o -name .git -prune \
975 -o -name .hg -prune -o -name .editor-backups -prune \
976 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
977 }
978
979 ifd() {
980 # insensitive find directory
981 find -L . -type d -not \( -name .svn -prune -o -name .git -prune \
982 -o -name .hg -prune -o -name .editor-backups -prune \
983 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
984 }
985
986
987 ipdrop() {
988 sudo iptables -A INPUT -s $1 -j DROP
989 }
990
991
992 istext() {
993 grep -Il "" "$@" &>/dev/null
994 }
995
996 jtail() {
997 journalctl -n 10000 -f "$@"
998 }
999 jr() { journalctl "$@" ; }
1000 jrf() { journalctl -f "$@" ; }
1001 jru() {
1002 journalctl -u exim4 _SYSTEMD_INVOCATION_ID=$(systemctl show -p InvocationID --value $1)
1003 }
1004
1005 l() {
1006 if [[ $PWD == /[iap] ]]; then
1007 command ls -A --color=auto -I lost+found "$@"
1008 else
1009 command ls -A --color=auto "$@"
1010 fi
1011 }
1012
1013 lcn() { locate -i "*$**"; }
1014
1015 lg() { LC_COLLATE=C.UTF-8 ll --group-directories-first "$@"; }
1016
1017 lt() { ll -tr "$@"; }
1018
1019 lld() { ll -d "$@"; }
1020
1021 ccomp ls l lg lt lld ll
1022
1023 # low recursively
1024 lowr() {
1025 local f dirs i a
1026 local -a all
1027 for dirs in false true; do
1028 for f; do
1029 if [[ -d $f ]]; then
1030 all=("$f"/**)
1031 # reverse the order to rename the nested dirs first.
1032 # note: 0 element is the dir itself
1033 for ((i=${#all[@]}-1; i>=1; i--)); do
1034 a="${all[i]}"
1035 if $dirs && [[ -d $a ]]; then
1036 # e dirs low "$a" # debug
1037 low "$a"
1038 elif ! $dirs && [[ ! -d $a && -e $a ]]; then
1039 # debug
1040 # e not dirs low "$a" # debug
1041 low "$a"
1042 fi
1043 done
1044 fi
1045 # just rename all the top level args on the second pass
1046 if $dirs; then
1047 # e final dirs low "$f" # debug
1048 low "$f"
1049 fi
1050 done
1051 done
1052 }
1053
1054 low() { # make filenames lowercase, remove bad chars
1055 local arg new dir f
1056 for arg; do
1057 arg="${arg%%+(/)}" # remove trailing slashes. assumes we have extglob on.
1058 dir="${arg%/*}"
1059 if (( ${#dir} == ${#arg} )); then
1060 dir=.
1061 fi
1062 f="${arg##*/}"
1063 new="${f,,}" # downcase
1064 new="${new//[^[:alnum:]._-]/_}" # sub bad chars
1065 new="${new#"${new%%[[:alnum:]]*}"}" # remove leading/trailing non-alnum
1066 new="${new%"${new##*[[:alnum:]]}"}"
1067 # remove bad underscores, like __ and _._
1068 new=$(echo $new | sed -r 's/__+/_/g;s/_+([.-])|([.-])_+/\1/g')
1069 safe_rename "$dir/$f" "$dir/$new" || return 1
1070 done
1071 return 0
1072 }
1073
1074 lower() { # make first letter of filenames lowercase.
1075 local x
1076 for x in "$@"; do
1077 if [[ ${x::1} == [A-Z] ]]; then
1078 y=$(tr '[:upper:]' '[:lower:]' <<<"${x::1}")"${x:1}"
1079 safe_rename "$x" "$y" || return 1
1080 fi
1081 done
1082 }
1083
1084
1085 k() { # history search
1086 grep -iP --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | tail -n 80 || [[ $? == 1 ]];
1087 }
1088 ks() { # history search
1089 grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | uniq || [[ $? == 1 ]];
1090 }
1091 ccomp grep k ks
1092
1093
1094 make-targets() {
1095 # show make targets, via http://stackoverflow.com/questions/3063507/list-goals-targets-in-gnu-make
1096 make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'
1097 }
1098
1099 mkc() {
1100 mkdir "$1"
1101 c "$1"
1102 }
1103 ccomp mkdir mkc
1104
1105 mkct() {
1106 mkc $(mktemp -d)
1107 }
1108
1109 mkt() { # mkdir and touch file
1110 local path="$1"
1111 mkdir -p "$(dirname "$path")"
1112 touch "$path"
1113 }
1114
1115 # shellcheck disable=SC2032
1116 mkdir() { command mkdir -p "$@"; }
1117
1118 nags() {
1119 # https://github.com/HenriWahl/Nagstamon/issues/357
1120 if ! pgrep -f /usr/lib/notification-daemon/notification-daemon >/dev/null; then
1121 /usr/lib/notification-daemon/notification-daemon &
1122 fi
1123 /usr/bin/nagstamon &
1124 }
1125
1126 nmt() {
1127 s nmtui-connect "$@"
1128 }
1129
1130 nopanic() {
1131 # shellcheck disable=SC2024
1132 sudo tee -a /var/log/exim4/paniclog-archive </var/log/exim4/paniclog; sudo truncate -s0 /var/log/exim4/paniclog
1133 }
1134
1135 p8() { ping "$@" 8.8.8.8; }
1136 p6() { ping6 "$@" 2001:4860:4860::8888; }
1137
1138 pkx() { # package extract
1139 local pkg cached tmp f
1140 c $(mktemp -d)
1141 pkg=$1
1142 # shellcheck disable=SC2012
1143 cached=$(ls -t /var/cache/apt/archives/$pkg* | tail -n1 2>/dev/null) ||:
1144 if [[ $cached ]]; then
1145 cp $cached .
1146 else
1147 aptitude download $pkg || return 1
1148 fi
1149 tmp=(*); f=${tmp[0]} # only 1 expected
1150 ex $f
1151 rm -f $f
1152 }
1153
1154 # pgrep and kill
1155 pk1() {
1156 local pid
1157 pid=($(pgrep -f "$*"))
1158 case ${#pid[@]} in
1159 1)
1160 # shellcheck disable=SC2128
1161 {
1162 ps -F $pid
1163 m kill $pid
1164 }
1165 ;;
1166 0) echo "no pid found" ;;
1167 *)
1168 ps -F ${pid[@]}
1169 ;;
1170 esac
1171 }
1172
1173 psg () {
1174 local x y help
1175 help="Usage: psg [--help] GREP_ARGS
1176 grep ps and output in a nice format"
1177 if [[ $1 == --help ]]; then
1178 echo "$help"
1179 return
1180 fi
1181 x=$(ps -eF)
1182 # final grep is because some commands tend to have a lot of trailing spaces
1183 y=$(echo "$x" | grep -iP "$@" | grep -o '.*[^ ]') ||:
1184 if [[ $y ]]; then
1185 echo "$x" | head -n 1 || [[ $? == 141 ]]
1186 echo "$y"
1187 fi
1188 }
1189
1190 pubip() { curl -4s https://icanhazip.com; }
1191 pubip6() { curl -6s https://icanhazip.com; }
1192 whatismyip() { pubip; }
1193
1194
1195 q() { # start / launch a program in the backround and redir output to null
1196 "$@" &> /dev/null &
1197 }
1198
1199 # shellcheck disable=SC2120
1200 r() {
1201 if [[ $HISTFILE ]]; then
1202 history -a # save history
1203 fi
1204 trap ERR # this avoids a segfault
1205 exit ${1:0}
1206 # i had this redir, not sure why
1207 # exit "$@" 2>/dev/null
1208 }
1209
1210 # scp is insecure and deprecated.
1211 scp() {
1212 rsync --inplace "$@"
1213 }
1214
1215 randport() {
1216 # available high ports are 1024-65535,
1217 # but lets skip things that are more likely to be in use
1218 python3 <<'EOF'
1219 import secrets
1220 print(secrets.SystemRandom().randrange(10002,65500))
1221 EOF
1222 }
1223
1224 # reapply bashrc
1225 reb() {
1226 source ~/.bashrc
1227 }
1228
1229 rl() {
1230 readlink -f "$@"
1231 }
1232 ccomp readlink rl
1233
1234 rsd() {
1235 # rsync, root is required to keep permissions right.
1236 # rsync --archive --human-readable --verbose --itemize-changes --checksum \(-ahvic\) \
1237 # --no-times --delete
1238 # basically, make an exact copy, use checksums instead of file times to be more accurate
1239 rsync -ahvic --delete "$@"
1240 }
1241 rsa() {
1242 # like rlu, but dont delete files on the target end which
1243 # do not exist on the original end.
1244 rsync -ahvic "$@"
1245 }
1246 rst() {
1247 # rl without preserving modification time.
1248 rsync -ahvic --delete --no-t "$@"
1249 }
1250 rsu() { # [OPTS] HOST PATH
1251 # eg. rlu -opts frodo /testpath
1252 # relative paths will expanded with readlink -f.
1253 opts=("${@:1:$#-2}") # 1 to last -2
1254 path="${*:$#}" # last
1255 host="${*:$#-1:1}" # last -1
1256 if [[ $path == .* ]]; then
1257 path=$(readlink -f $path)
1258 fi
1259 # rync here uses checksum instead of time so we dont mess with
1260 # unison relying on time as much. g is for group, same reason
1261 # to keep up with unison.
1262 m s rsync -rlpchviog --relative "${opts[@]}" "$path" "root@$host:/";
1263 }
1264 ccomp rsync rsd rsa rst rsu
1265
1266 resolvcat() {
1267 local f
1268 if [[ $(systemctl is-active nscd ||:) != inactive ]]; then
1269 m s nscd -i hosts
1270 fi
1271 f=/etc/resolv.conf
1272 echo $f:; ccat $f
1273 hr; s ss -lpn 'sport = 53'
1274 if systemctl is-enabled dnsmasq &>/dev/null || [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
1275 # this will fail is dnsmasq is failed
1276 hr; m ser status dnsmasq | cat || :
1277 f=/etc/dnsmasq.conf
1278 hr; echo $f:; ccat $f
1279 hr; m grr '^ *(servers-file|server) *=|^ *no-resolv *$' /etc/dnsmasq.conf /etc/dnsmasq.d
1280 f=/etc/dnsmasq-servers.conf
1281 hr; echo $f:; ccat $f
1282 fi
1283 hr
1284 echo /etc/nsswitch.conf:
1285 grep '^ *hosts:' /etc/nsswitch.conf
1286 if systemctl is-enabled systemd-resolved &>/dev/null || [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then
1287 hr; m ser status systemd-resolved | cat || :
1288 hr; m systemd-resolve --status | cat
1289 fi
1290
1291 }
1292 rcat() {
1293 resolvcat | less
1294 }
1295 reresolv() {
1296 if [[ $(systemctl is-active nscd ||:) != inactive ]]; then
1297 m ser stop nscd
1298 sleep .5
1299 m ser start nscd
1300 m sudo nscd -i hosts
1301 fi
1302 if [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
1303 m sudo systemctl restart dnsmasq
1304 fi
1305 if [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then
1306 m sudo systemctl restart systemd-resolved
1307 fi
1308 if type -P resolvectl &>/dev/null; then
1309 resolvectl flush-caches
1310 fi
1311 }
1312
1313 rmstrips() {
1314 ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less
1315 }
1316
1317 s() {
1318 # background
1319 # I use a function because otherwise we cant use in a script,
1320 # cant assign to variable.
1321 #
1322 # note: gksudo is recommended for X apps because it does not set the
1323 # home directory to the same, and thus apps writing to ~ fuck things up
1324 # with root owned files.
1325 #
1326 if [[ $EUID != 0 || $1 == -* ]]; then
1327 # shellcheck disable=SC2034
1328 SUDOD="$PWD" command sudo -i "$@"
1329 DID_SUDO=true
1330 else
1331 "$@"
1332 fi
1333 }
1334 sb() { # sudo bash -c
1335 # use sb instead of s is for sudo redirections,
1336 # eg. sb 'echo "ok fine" > /etc/file'
1337 # shellcheck disable=SC2034
1338 local SUDOD="$PWD"
1339 sudo -i bash -c "$@"
1340 }
1341 ccomp sudo s sb
1342
1343 safe_rename() { # warn and dont rename if file exists.
1344 # mv -n exists, but it\'s silent
1345 if [[ $# != 2 ]]; then
1346 echo safe_rename error: $# args, need 2 >2
1347 return 1
1348 fi
1349 if [[ $1 != "$2" ]]; then # yes, we want to silently ignore this
1350 if [[ -e $2 || -L $2 ]]; then
1351 echo "Cannot rename $1 to $2 as it already exists."
1352 else
1353 mv -vi "$1" "$2"
1354 fi
1355 fi
1356 }
1357
1358
1359
1360 sd() {
1361 sudo dd status=none of="$1"
1362 }
1363
1364 ser() {
1365 if type -p systemctl &>/dev/null; then
1366 s systemctl "$@"
1367 else
1368 if (( $# >= 3 )); then
1369 echo iank: ser expected 2 or less arguments
1370 return 1
1371 fi
1372 s service $2 $1
1373 fi
1374 }
1375 seru() { systemctl --user "$@"; }
1376 # like restart, but do nothing if its not already started
1377 srestart() {
1378 local service=$1
1379 if [[ $(s systemctl --no-pager show -p ActiveState $service ) == ActiveState=active ]]; then
1380 systemctl restart $service
1381 fi
1382 }
1383
1384 setini() { # set a value in a .ini style file
1385 key="$1" value="$2" section="$3" file="$4"
1386 if [[ -s $file ]]; then
1387 sed -ri -f - "$file" <<EOF
1388 # remove existing keys
1389 / *\[$section\]/,/^ *\[[^]]+\]/{/^\s*$key[[:space:]=]/d}
1390 # add key
1391 /^\s*\[$section\]/a $key=$value
1392 # from section to eof, do nothing
1393 /^\s*\[$section\]/,\$b
1394 # on the last line, if we haven't found section yet, add section and key
1395 \$a [$section]\\
1396 $key=$value
1397 EOF
1398 else
1399 cat >"$file" <<EOF
1400 [$section]
1401 $key=$value
1402 EOF
1403 fi
1404 }
1405
1406 sgo() { # service go
1407 service=$1
1408 ser restart $service || return 1
1409 if type -p systemctl &>/dev/null; then
1410 ser enable $service
1411 fi
1412 }
1413 soff () {
1414 for service; do
1415 # ignore services that dont exist
1416 if systemctl cat $service &>/dev/null; then
1417 ser stop $service;
1418 ser disable $service
1419 fi
1420 done
1421 }
1422
1423 sgu() {
1424 systemctl list-unit-files | rg "$@"
1425 }
1426
1427
1428 sk() {
1429
1430
1431 # note, if you do something like this
1432 # x=( prefix* )
1433 # then disable the warning with:
1434 # shellcheck disable=SC2206 # globbing is intended
1435
1436 # 2029: "unescaped, this expands on the client side.": yes, I know how ssh works
1437 # 2164: "Use 'cd ... || exit' or 'cd ... || return' in case cd fails.": i have automatic error handling
1438 # 2086: unquoted $var: Quoting every var I set is way too much quotes.
1439 # 2068: Double quote array expansions to avoid re-splitting elements: same as above.
1440 # 2033: command arg is a function name: too many false positives.
1441
1442
1443 # these ones I had disabled, but without a good written explanation, so enabling them temporarily
1444 # 2046: unquoted $(cmd)
1445 # 2119: Functions with optional args get bad warnings when none are passed.
1446
1447 shellcheck -W 999 -x -e 2029,2164,2086,2068,2033 "$@" || return $?
1448 }
1449
1450
1451 # sl: ssh, but firsh rsync our bashrc and related files to a special
1452 # directory on the remote host if needed.
1453
1454 # Some environment variables and files need to be setup for this to work
1455 # (mine are set at the beginning of this file)
1456
1457 # SL_FILES_DIR: Environment variable. Path to folder which should at
1458 # least have a .bashrc file or symlink. This dir will be rsynced to ~ on
1459 # remote hosts (top level symlinks are resolved) unless the host already
1460 # has a $SL_FILES_DIR/.bashrc. In that case, we assume it is a host you
1461 # control and sync files to separately and already has the ~/.bashrc you
1462 # want. The remote bash will also take its .inputrc config from this
1463 # folder (default of not existing is fine). Mine looks like this:
1464 # https://iankelling.org/git/?p=distro-setup;a=tree;f=sl/.iank
1465
1466 # SL_INFO_DIR: Environment variable. This folder stores info about what
1467 # we detected on the remote system and when we last synced. It will be created
1468 # if it does not exist. Sometimes you may want to forget about a
1469 # remote system, you can use sl --rsync, or the function for that slr
1470 # below.
1471
1472 # SL_TEST_CMD: Env var. Meant to be used to vary the files synced
1473 # depending on the remote host. Run this string on the remote host the
1474 # first time sl is run (or if we run slr). The result is passed to
1475 # SL_TEST_HOOK. For example,
1476 # export SL_TEST_CMD=". /etc/os-release ; echo \${VERSION//[^a-zA-Z0-9]/}"
1477
1478 # SL_TEST_HOOK: Env var. It is run as $SL_TEST_HOOK. This can set
1479 # $SL_FILES_DIR to vary the files synced.
1480
1481 # SL_RSYNC_ARGS: Env var. String of arguments passed to rsync. For
1482 # example to exclude files within a directory. Note, excluded
1483 # files wont be deleted on rsync, you can add --delete-excluded
1484 # to the rsync command if that is desired.
1485
1486 # SL_SSH_ARGS: Env var. Default arguments passed to ssh.
1487
1488 # For when ~/.bashrc is already customized on the remote server, you
1489 # might find it problematic that ~/.bashrc is sourced for ALL ssh
1490 # commands, even in scripts. This paragraph is all about that. bash
1491 # scripts dont source ~/.bashrc, but call ssh in scripts and you get
1492 # ~/.bashrc. You dont want this. .bashrc is meant for interactive shells
1493 # and if you customize it, probably has bugs from time to time. This is
1494 # bad. Here's how I fix it. I have a special condition to "return" in my
1495 # .bashrc for noninteractive ssh shells (copy that code). Then use this
1496 # function or similar that passes LC_USEBASHRC=t when sshing and I want
1497 # my bashrc. Also, I don't keep most of my bashrc in .bashrc, i source a
1498 # separate file because even if I return early on, the whole file gets
1499 # parsed which can fail if there is a syntax error.
1500 sl() {
1501 # Background on LC_USEBASHRC var (no need to read if you just want to
1502 # use this function): env variables sent across ssh are strictly
1503 # limited, but we get LC_* at least in debian based machines, so we
1504 # just make that * be something no normal program would use. Note, on
1505 # hosts that dont allow LC_* I start an inner shell with LC_USEBASHRC
1506 # set, and the inner shell also allows running a nondefault
1507 # .bashrc. This means the outer shell still ran the default .bashrc,
1508 # but that is the best we can do.
1509
1510 local now args remote dorsync haveinfo tmpa sshinfo tmp tmp2 type info_sec force_rsync \
1511 sync_dirname testcmd extra_info testbool files_sec sl_test_cmd sl_test_hook
1512 declare -a args tmpa
1513
1514 args=($SL_SSH_ARGS)
1515
1516 # ssh [-1246Antivivisectionist] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
1517 # [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L address]
1518 # [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option]
1519 # [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] [user@]hostname
1520 # [command]
1521
1522 # ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
1523 # [-D [bind_address:]port] [-E log_file] [-e escape_char]
1524 # [-F configfile] [-I pkcs11] [-i identity_file]
1525 # [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]
1526 # [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]
1527 # [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
1528
1529 force_rsync=false
1530 if [[ $1 == --rsync ]]; then
1531 force_rsync=true
1532 shift
1533 fi
1534
1535 sl_test_cmd=$SL_TEST_CMD
1536 sl_test_hook=$SL_TEST_HOOK
1537 sl_rsync_args=$SL_RSYNC_ARGS
1538 while [[ $1 ]]; do
1539 case "$1" in
1540 --rsync)
1541 force_rsync=true
1542 ;;
1543 --sl-test-cmd)
1544 sl_test_cmd="$2"
1545 shift
1546 ;;
1547 --sl-test-hook)
1548 sl_test_hook="$2"
1549 shift
1550 ;;
1551 --sl-rsync-args)
1552 sl_rsync_args="$2"
1553 shift
1554 ;;
1555 *)
1556 break
1557 ;;
1558 esac
1559 shift
1560 done
1561
1562 while [[ $1 ]]; do
1563 case "$1" in
1564 # note we dont support things like -4oOption
1565 -[46AaCfGgKkMNnqsTtVvXxYy]*)
1566 args+=("$1"); shift
1567 ;;
1568 -[bcDEeFIiJLlmOopQRSWw]*)
1569 # -oOption etc is valid
1570 if (( ${#1} >= 3 )); then
1571 args+=("$1"); shift
1572 else
1573 args+=("$1" "$2"); shift 2
1574 fi
1575 ;;
1576 *)
1577 break
1578 ;;
1579 esac
1580 done
1581 remote="$1"
1582 if [[ ! $remote ]]; then
1583 echo $0: error hostname required >&2
1584 return 1
1585 fi
1586 shift
1587
1588 if [[ ! $SL_INFO_DIR ]]; then
1589 echo error: missing '$SL_INFO_DIR' env var >&2
1590 return 1
1591 fi
1592
1593 now=$(date +%s)
1594 dorsync=false
1595 haveinfo=false
1596 tmpa=($SL_INFO_DIR/???????????"$remote")
1597 sshinfo=${tmpa[0]}
1598 if [[ -e $sshinfo ]]; then
1599 if $force_rsync; then
1600 rm -f $sshinfo
1601 else
1602 haveinfo=true
1603 fi
1604 fi
1605 if $haveinfo; then
1606 tmp=${sshinfo[0]##*/}
1607 tmp2=${tmp::11}
1608 type=${tmp2: -1}
1609 extra_info=$(cat $sshinfo)
1610 else
1611 # we test for string to know ssh succeeded
1612 testbool="test -e $SL_FILES_DIR/.bashrc -a -L .bashrc -a -v LC_USEBASHRC"
1613 testcmd="if $testbool; then printf y; else printf n; fi"
1614 if ! tmp=$(LC_USEBASHRC=y command ssh "${args[@]}" "$remote" "$testcmd; $sl_test_cmd"); then
1615 echo failed sl test. doing plain ssh -v
1616 command ssh -v "${args[@]}" "$remote"
1617 fi
1618 if [[ $tmp == y* ]]; then
1619 type=a
1620 else
1621 dorsync=true
1622 type=b
1623 fi
1624 extra_info="${tmp:1}"
1625 fi
1626 if [[ $sl_test_hook ]]; then
1627 RSYNC_RSH="ssh ${args[*]}" $sl_test_hook "$extra_info" "$remote"
1628 fi
1629
1630 if $haveinfo && [[ $type == b ]]; then
1631 info_sec=${tmp::10}
1632 read files_sec _ < <(find -L $SL_FILES_DIR -printf "%T@ %p\n" | sort -nr || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]] )
1633 files_sec=${files_sec%%.*}
1634 if (( files_sec > info_sec )); then
1635 dorsync=true
1636 rm -f $sshinfo
1637 fi
1638 fi
1639
1640 sync_dirname=${SL_FILES_DIR##*/}
1641
1642 if [[ ! $SL_FILES_DIR ]]; then
1643 echo error: missing '$SL_FILES_DIR' env var >&2
1644 return 1
1645 fi
1646
1647 if $dorsync; then
1648 RSYNC_RSH="ssh ${args[*]}" m rsync -rptL --delete $sl_rsync_args $SL_FILES_DIR "$remote":
1649 fi
1650 if $dorsync || ! $haveinfo; then
1651 sshinfo=$SL_INFO_DIR/$now$type"$remote"
1652 [[ -e $SL_INFO_DIR ]] || mkdir -p $SL_INFO_DIR
1653 printf "%s\n" "$extra_info" >$sshinfo
1654 chmod 666 $sshinfo
1655 fi
1656 if [[ $type == b ]]; then
1657 if (( ${#@} )); then
1658 # Theres a couple ways to pass arguments, im not sure whats best,
1659 # but relying on bash 4.4+ escape quoting seems most reliable.
1660 command ssh "${args[@]}" "$remote" \
1661 LC_USEBASHRC=t bash -c '.\ '$sync_dirname'/.bashrc\;"\"\$@\""' bash ${@@Q}
1662 elif [[ ! -t 0 ]]; then
1663 # This case is when commands are being piped to ssh.
1664 # Normally, no bashrc gets sourced.
1665 # But, since we are doing all this, lets source it because we can.
1666 cat <(echo . $sync_dirname/.bashrc) - | command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
1667 else
1668 command ssh -t "${args[@]}" "$remote" LC_USEBASHRC=t INPUTRC=$sync_dirname/.inputrc bash --rcfile $sync_dirname/.bashrc
1669 fi
1670 else
1671 if [[ -t 0 ]]; then
1672 LC_USEBASHRC=t command ssh "${args[@]}" "$remote" ${@@Q}
1673 else
1674 command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
1675 fi
1676 fi
1677 # this function inspired from https://github.com/Russell91/sshrc
1678 }
1679
1680 slr() {
1681 sl --rsync "$@"
1682 }
1683 sss() { # ssh solo
1684 sl -oControlMaster=no -oControlPath=/ "$@"
1685 }
1686 # kill off old shared socket then ssh
1687 ssk() {
1688 m ssh -O exit "$@" || [[ $? == 255 ]]
1689 m sl "$@"
1690 }
1691 ccomp ssh sl slr sss ssk
1692 # plain ssh
1693 ssh() {
1694 if [[ $TERM == alacritty || $TERM == xterm-kitty ]]; then
1695 TERM=xterm-256color LC_USEBASHRC=t command ssh "$@"
1696 else
1697 LC_USEBASHRC=t command ssh "$@"
1698 fi
1699 }
1700
1701
1702 slog() {
1703 # log with script. timing is $1.t and script is $1.s
1704 # -l to save to ~/typescripts/
1705 # -t to add a timestamp to the filenames
1706 local logdir do_stamp arg_base
1707 (( $# >= 1 )) || { echo "arguments wrong"; return 1; }
1708 logdir="/a/dt/"
1709 do_stamp=false
1710 while getopts "lt" option
1711 do
1712 case $option in
1713 l ) arg_base=$logdir ;;
1714 t ) do_stamp=true ;;
1715 esac
1716 done
1717 shift $((OPTIND - 1))
1718 arg_base+=$1
1719 [[ -e $logdir ]] || mkdir -p $logdir
1720 $do_stamp && arg_base+=$(date +%F.%T%z)
1721 script -t $arg_base.s 2> $arg_base.t
1722 }
1723 splay() { # script replay
1724 #logRoot="$HOME/typescripts/"
1725 #scriptreplay "$logRoot$1.t" "$logRoot$1.s"
1726 scriptreplay "$1.t" "$1.s"
1727 }
1728
1729 sr() {
1730 # sudo redo. be aware, this command may not work right on strange distros or earlier software
1731 if [[ $# == 0 ]]; then
1732 sudo -E bash -c -l "$(history -p '!!')"
1733 else
1734 echo this command redos last history item. no argument is accepted
1735 fi
1736 }
1737
1738 srm () {
1739 # with -ll, less secure but faster.
1740 command srm -ll "$@"
1741 }
1742
1743 srun() {
1744 scp $2 $1:/tmp
1745 ssh $1 /tmp/${2##*/} $(printf "%q\n" "${@:2}")
1746 }
1747
1748
1749 swap() {
1750 local tmp
1751 tmp=$(mktemp)
1752 mv $1 $tmp
1753 mv $2 $1
1754 mv $tmp $2
1755 }
1756
1757 tclock() { # terminal clock
1758 local x
1759 clear
1760 date +%l:%_M
1761 len=60
1762 # this goes to full width
1763 #len=${1:-$((COLUMNS -7))}
1764 x=1
1765 while true; do
1766 if (( x == len )); then
1767 end=true
1768 d="$(date +%l:%_M) "
1769 else
1770 end=false
1771 d=$(date +%l:%M:%_S)
1772 fi
1773 echo -en "\r"
1774 echo -n "$d"
1775 for ((i=0; i<x; i++)); do
1776 if (( i % 6 )); then
1777 echo -n _
1778 else
1779 echo -n .
1780 fi
1781 done
1782 if $end; then
1783 echo
1784 x=1
1785 else
1786 x=$((x+1))
1787 fi
1788 sleep 5
1789 done
1790 }
1791
1792
1793 te() {
1794 # test existence / exists
1795 local ret=0
1796 for x in "$@"; do
1797 [[ -e "$x" || -L "$x" ]] || ret=1
1798 done
1799 return $ret
1800 }
1801
1802 psoff() {
1803 # normally, i would just execute these commands in the function.
1804 # however, DEBUG is not inherited, so we need to run it outside a function.
1805 # And we want to run set -x afterwards to avoid spam, so we cram everything
1806 # in here, and then it will run after this function is done.
1807 PROMPT_COMMAND='trap DEBUG; unset PROMPT_COMMAND; PS1="\w \$ "'
1808 }
1809 pson() {
1810 PROMPT_COMMAND=prompt-command
1811 if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
1812 trap 'settitle "$BASH_COMMAND"' DEBUG
1813 fi
1814 }
1815
1816 tx() { # toggle set -x, and the prompt so it doesnt spam
1817 if [[ $- == *x* ]]; then
1818 set +x
1819 pson
1820 else
1821 psoff
1822 fi
1823 }
1824
1825 psnetns() {
1826 # show all processes in the network namespace $1.
1827 # blank entries appear to be subprocesses/threads
1828 local x netns
1829 netns=$1
1830 ps -w | head -n 1
1831 sudo find -L /proc/[1-9]*/task/*/ns/net -samefile /run/netns/$netns | cut -d/ -f5 | \
1832 while read -r l; do
1833 x=$(ps -w --no-headers -p $l);
1834 if [[ $x ]]; then echo "$x"; else echo $l; fi;
1835 done
1836 }
1837
1838 m() { printf "%s\n" "$*"; "$@"; }
1839
1840 uptime() {
1841 if type -p uprecords &>/dev/null; then
1842 uprecords -B
1843 else
1844 command uptime
1845 fi
1846 }
1847
1848 virshrm() {
1849 for x in "$@"; do virsh destroy "$x"; virsh undefine "$x"; done
1850 }
1851
1852 vm-set-listen(){
1853 local t
1854 t=$(mktemp)
1855 local vm=$1
1856 local ip=$2
1857 sudo virsh dumpxml $vm | sed -r "s/(<listen.*address=')([^']+)/\1$ip/" | \
1858 sed -r "s/listen='[^']+/listen='$ip/"> $t
1859 sudo virsh undefine $vm
1860 sudo virsh define $t
1861 }
1862
1863
1864 vmshare() {
1865 vm-set-listen $1 0.0.0.0
1866 }
1867
1868
1869 vmunshare() {
1870 vm-set-listen $1 127.0.0.1
1871 }
1872
1873 myiwscan() {
1874 # find input, copy to pattern space, when we find the first field, print the copy in different order without newlines.
1875 # instead of using labels, we could just match a line and group, eg: /signal:/,{s/signal:(.*)/\1/h}
1876 sudo iw dev wls1 scan | sed -rn "
1877 s/^\Wcapability: (.*)/\1/;Ta;h;b
1878 :a;s/^\Wsignal: -([^.]+).*/\1/;Tb;H;b
1879 # padded to min width of 20
1880 :b;s/\WSSID: (.*)/\1 /;T;s/^(.{20}(.*[^ ])?) */\1/;H;g;s/(.*)\n(.*)\n(.*)/\2 \3 \1/gp;b
1881 "|sort -r
1882 }
1883
1884 # * misc stuff
1885
1886
1887 # temporary variables to test colorization
1888 # some copied from gentoo /etc/bash/bashrc,
1889 use_color=false
1890 # dircolors --print-database uses its own built-in database
1891 # instead of using /etc/DIR_COLORS. Try to use the external file
1892 # first to take advantage of user additions.
1893 safe_term=${TERM//[^[:alnum:]]/?} # sanitize TERM
1894 match_lhs=""
1895 [[ -f ~/.dir_colors ]] && match_lhs="${match_lhs}$(<~/.dir_colors)"
1896 [[ -f /etc/DIR_COLORS ]] && match_lhs="${match_lhs}$(</etc/DIR_COLORS)"
1897 [[ -z ${match_lhs} ]] \
1898 && type -P dircolors >/dev/null \
1899 && match_lhs=$(dircolors --print-database)
1900 # test if our $TERM is in the TERM values in dircolor
1901 [[ $'\n'${match_lhs} == *$'\n'"TERM "${safe_term}* ]] && use_color=true
1902
1903
1904 if ${use_color} && [[ $- == *i* ]]; then
1905
1906 term_bold="$(tput bold)"
1907 term_red="$(tput setaf 1)"
1908 term_green="$(tput setaf 2)"
1909 term_yellow="$(tput setaf 3)"
1910 term_purple="$(tput setaf 5)"
1911 term_nocolor="$(tput sgr0)" # no font attributes
1912
1913 # unused so far. commented for shellcheck
1914 # term_underl="$(tput smul)"
1915 # term_blue="$(tput setaf 4)"
1916 # term_cyan="$(tput setaf 6)"
1917
1918 fi
1919 # Try to keep environment pollution down, EPA loves us.
1920 unset safe_term match_lhs use_color
1921
1922 # * prompt
1923
1924
1925 if [[ $- == *i* ]]; then
1926
1927 # this needs to come before next ps1 stuff
1928 # this stuff needs bash 4, feb 2009,
1929 # old enough to no longer condition on $BASH_VERSION anymore
1930 shopt -s autocd
1931 shopt -s dirspell
1932 PS1='\w'
1933 if [[ $- == *i* ]] && [[ ! $LC_INSIDE_EMACS ]]; then
1934 PROMPT_DIRTRIM=2
1935 bind -m vi-command B:shell-backward-word
1936 bind -m vi-command W:shell-forward-word
1937 fi
1938
1939 if [[ $SSH_CLIENT || $SUDO_USER ]]; then
1940 unset PROMPT_DIRTRIM
1941 PS1="\h:$PS1"
1942 fi
1943
1944 # emacs terminal has problems if this runs slowly,
1945 # so I've thrown a bunch of things at the wall to speed it up.
1946 prompt-command() {
1947 local return=$? # this MUST COME FIRST
1948 local ps_char ps_color
1949 unset IFS
1950
1951 if [[ $HISTFILE ]]; then
1952 history -a # save history
1953 fi
1954
1955 # assigned in brc2
1956 # shellcheck disable=SC1303
1957 if [[ $jr_pid ]]; then
1958 if [[ -e /proc/$jr_pid ]]; then
1959 kill $jr_pid
1960 fi
1961 unset jr_pid
1962 fi
1963
1964 case $return in
1965 0) ps_color="$term_purple"
1966 ps_char='\$'
1967 ;;
1968 1) ps_color="$term_green"
1969 ps_char="$return \\$"
1970 ;;
1971 *) ps_color="$term_yellow"
1972 ps_char="$return \\$"
1973 ;;
1974 esac
1975 if [[ ! -O . ]]; then # not owner
1976 if [[ -w . ]]; then # writable
1977 ps_color="$term_bold$term_red"
1978 else
1979 ps_color="$term_bold$term_green"
1980 fi
1981 fi
1982
1983 # faster than sourceing the file im guessing
1984 if [[ -e /dev/shm/iank-status && ! -e /tmp/quiet-status ]]; then
1985 eval $(< /dev/shm/iank-status)
1986 fi
1987 if [[ ! $SSH_CLIENT && $MAIL_HOST != "$HOSTNAME" ]]; then
1988 ps_char="@ $ps_char"
1989 fi
1990 # We could test if sudo is active with sudo -nv
1991 # but then we get an email and log of lots of failed sudo commands.
1992 # We could turn those off, but seems better not to.
1993 if [[ $EUID != 0 ]] && [[ $DID_SUDO ]]; then
1994 ps_char="SUDO $ps_char"
1995 fi
1996 if [[ ! $HISTFILE ]]; then
1997 ps_char="NOHIST $ps_char"
1998 fi
1999 PS1="${PS1%"${PS1#*[wW]}"} \[$ps_color\]$ps_char\[$term_nocolor\] "
2000
2001 # set titlebar. instead, using more advanced
2002 # titelbar below
2003 #echo -ne "$_title_escape $HOSTNAME ${PWD/#$HOME/~} \007"
2004 }
2005 PROMPT_COMMAND=prompt-command
2006
2007 if [[ $TERM == screen* ]]; then
2008 _title_escape="\033]..2;"
2009 else
2010 # somme sites recommend this, i dunno what the diff is.
2011 #_title_escape="\033]30;"
2012 _title_escape="\033]0;"
2013 fi
2014
2015 settitle () {
2016 # this makes it so we show the current command if
2017 # one is running, otherwise, show nothing
2018
2019 if [[ $1 == prompt-command ]]; then
2020 return 0
2021 fi
2022 if (( ${#BASH_ARGC[@]} == 1 && BASH_SUBSHELL == 0 )); then
2023 echo -ne "$_title_escape ${PWD/#$HOME/~} "
2024 printf "%s" "$*"
2025 echo -ne "\007"
2026 fi
2027 }
2028
2029 # note, this wont work:
2030 # x=$(mktemp); cp a $x
2031 # I havnt figured out why, bigger fish to fry.
2032 #
2033 # for titlebar.
2034 # condition from the screen man page i think.
2035 # note: duplicated in tx()
2036 if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
2037 trap 'settitle "$BASH_COMMAND"' DEBUG
2038 else
2039 trap DEBUG
2040 fi
2041
2042 fi
2043
2044 # * stuff that makes sense to be at the end
2045
2046
2047 # best practice
2048 unset IFS
2049
2050 # shellcheck disable=SC1090
2051 [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
2052
2053 # I had this idea to start a bash shell which would run an initial
2054 # command passed through this env variable, then continue on
2055 # interactively. But the use case I had in mind went away.
2056 #
2057 # if [[ $MY_INIT_CMD ]]; then
2058 # "${MY_INIT_CMD[@]}"
2059 # unset MY_INIT_CMD
2060 # fi
2061
2062 # ensure no bad programs appending to this file will have an affect
2063 return 0