Mainly add external monitoring of mail server
[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 source /a/bin/errhandle/err
7
8 # * settings
9
10 CDPATH=.
11
12 set -o pipefail
13
14 # remove all aliases. aliases provided by the system tend to get in the way,
15 # for example, error happens if I try to define a function the same name as an alias
16 unalias -a
17
18 # remove gnome keyring warning messages
19 # there is probably a more proper way, but I didnt find any easily on google
20 # now using xfce+xmonad instead of vanilla xmonad, so disabling this
21 #unset GNOME_KEYRING_CONTROL
22
23 # use extra globing features.
24 shopt -s extglob
25 # include .files when globbing, but ignore files name . and ..
26 # setting this also sets dotglob.
27 export GLOBIGNORE="*/.:*/.."
28
29 # broken with bash_completion package. Saw a bug for this once. dont anymore.
30 # still broken in wheezy
31 # still buggered in latest stable from the web, version 2.1
32 # perhaps its fixed in newer git version, which fails to make for me
33 # this note is from 6-2014.
34 # still broken in flidas.
35 #shopt -s nullglob
36
37 # make tab on an empty line do nothing
38 shopt -s no_empty_cmd_completion
39
40 # fix spelling errors for cd, only in interactive shell
41 shopt -s cdspell
42 # append history instead of overwritting it
43 shopt -s histappend
44 # for compatibility, per gentoo/debian bashrc
45 shopt -s checkwinsize
46 # attempt to save multiline single commands as single history entries.
47 shopt -s cmdhist
48 # enable **
49 shopt -s globstar
50
51
52 # inside emacs fixes
53 if [[ $RLC_INSIDE_EMACS ]]; then
54 # EMACS is used by bash on startup, but we dont need it anymore.
55 # plus I hit a bug in a makefile which inherited it
56 unset EMACS
57 export RLC_INSIDE_EMACS
58 export PAGER=cat
59 export MANPAGER=cat
60 # scp completion does not work, but this doesnt fix it. todo, figure this out
61 #complete -r scp &> /dev/null
62 # todo, remote file completion fails, figure out how to turn it off
63 export NODE_DISABLE_COLORS=1
64 # This gets rid of ugly terminal escape chars in node repl
65 # sometime, Id like to have completion working in emacs shell for node
66 # the offending chars can be found in lib/readline.js,
67 # things that do like:
68 # stream.write('\x1b[' + (x + 1) + 'G');
69 # We can remove them and keep readline, for example by doing this
70 # to start a repl:
71 #!/usr/bin/env nodejs
72 # var readline = require('readline');
73 # readline.cursorTo = function(a,b,c) {};
74 # readline.clearScreenDown = function(a) {};
75 # const repl = require('repl');
76 # var replServer = repl.start('');
77 #
78 # no prompt, or else readline complete seems to be confused, based
79 # on our column being different? node probably needs to send
80 # different kind of escape sequence that is not ugly. Anyways,
81 # completion doesnt work yet even with the ugly prompt, so whatever
82 #
83 export NODE_NO_READLINE=1
84
85 fi
86
87 # emacs has a different default search path than the info command. This
88 # adds the info defaults to emacs, but not the reverse, because I dun
89 # care much about the cli. The search path is only on the cli if you run
90 # "info xxx", or in emacs if you run '(info xxx)', so not that
91 # important, but might as well fix it.
92
93 # info info says this path is what was compiled, and its not documented
94 # anywhere. Through source grepping, i found it in filesys.h of the info
95 # source in trisquel flidas.
96 #
97 # Traling : means for emacs to add its own stuff on to the end.
98
99 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:.:
100
101 if [[ $- == *i* ]]; then
102 # for readline-complete.el
103 if [[ $RLC_INSIDE_EMACS ]]; then
104 # all for readline-complete.el
105 stty echo
106 bind 'set horizontal-scroll-mode on'
107 bind 'set print-completions-horizontally on'
108 bind '"\C-i": self-insert'
109 else
110
111 if [[ $KONSOLE_PROFILE_NAME ]]; then
112 TERM=xterm-256color
113 fi
114
115 # todo: not sure this works in sakura
116 #stty werase undef
117 #bind "\C-w": kill-region
118 # sakura == xterm-256color
119 # konsole == xterm
120 if [[ $TERM == xterm* ]]; then
121 # control + arrow keys. for other terminals, see http://unix.stackexchange.com/questions/10806/how-to-change-previous-next-word-shortcut-in-bash
122 bind '"\e[1;5C": shell-forward-word' 2>/dev/null
123 bind '"\e[1;5D": shell-backward-word' 2>/dev/null
124 else
125 # make ctrl-backspace work. for konsole, i fixed it through
126 # /home/iank/.local/share/konsole/default.keytab
127 stty werase '^h'
128 bind '"\eOc": shell-forward-word'
129 bind '"\eOd": shell-backward-word'
130 fi
131 # i cant remember why i did this, probably to free up some keys to bind
132 # to other things in bash.
133 # other than C-c and C-z, the rest defined by stty -a are, at least in
134 # gnome-terminal, overridden by bash, or disabled by the system
135 stty lnext undef stop undef start undef
136 fi
137
138 fi
139
140
141 # history number. History expansion is good.
142 PS4='$LINENO+ '
143 # history file size limit, set to unlimited.
144 # this needs to be different from the default because
145 # default HISTFILESIZE is 500 and could clobber our history
146 HISTFILESIZE=
147 # max commands 1 session can append/read from history
148 HISTSIZE=1000000
149 # the time format display when doing the history command
150 # also, setting this makes the history file record time
151 # of each command as seconds from the epoch
152 HISTTIMEFORMAT="%Y-%m-%d %I:%M %p "
153 # consecutive duplicate lines dont go in history
154 HISTCONTROL=ignoredups
155 # works in addition to HISTCONTROL to do more flexible things
156 # it could also do the same things as HISTCONTROL and thus replace it,
157 # but meh. dunno why, but just " *" does glob expansion, so use [ ] to avoid it.
158 HISTIGNORE='pass *:[ ]*:otp *:oathtool *'
159
160 export BC_LINE_LENGTH=0
161
162 export PROFILE_TASKS_TASK_OUTPUT_LIMIT=100
163
164 # note, if I use a machine I dont want files readable by all users, set
165 # umask 077 # If fewer than 4 digits are entered, leading zeros are assumed
166
167 # i for insensitive. the rest from
168 # X means dont remove the current screenworth of output upon exit
169 # R means to show colors n things
170 export LESS=RXi
171 export SYSTEMD_LESS=$LESS
172
173
174 # * include files
175
176
177
178 # if someone exported $SOE (stop on error), catch errors.
179 #
180 # Note, on debian this results in the following warning when in ssh,
181 # hich I haven't figured out how to fix. It doesn't happen if we source
182 # after the shell has started
183 #
184 # bash: /usr/share/bashdb/bashdb-main.inc: No such file or directory
185 # bash: warning: cannot start debugger; debugging mode disabled
186 if [[ $SOE ]]; then
187 if [[ -e /a/bin/errhandle/err ]]; then
188 source /a/bin/errhandle/err
189 fi
190 fi
191
192 # based on readme.debian. dunno if this will break on other distros.
193 if [[ -s /usr/share/wcd/wcd-include.sh ]]; then
194 source /usr/share/wcd/wcd-include.sh
195 fi
196
197 if [[ -s /a/bin/small-misc-bash/ll-function ]]; then
198 # shellcheck source=/a/bin/small-misc-bash/ll-function
199 source /a/bin/small-misc-bash/ll-function
200 elif [[ -s ~/.iank/ll-function ]]; then
201 source ~/.iank/ll-function
202 fi
203
204 # * functions
205
206
207 ..() { c ..; }
208 ...() { c ../..; }
209 ....() { c ../../..; }
210 .....() { c ../../../..; }
211 ......() { c ../../../../..; }
212
213 # file cut copy and paste, like the text buffers :)
214 # I havnt tested these.
215 _fbufferinit() { # internal use
216 ! [[ $my_f_tempdir ]] && my_f_tempdir=$(mktemp -d)
217 rm -rf "${my_f_tempdir:?}"/*
218 }
219 fcp() { # file cp
220 _fbufferinit
221 cp "$@" "$my_f_tempdir"/
222 }
223 fct() { # file cut
224 _fbufferinit
225 mv "$@" "$my_f_tempdir"/
226 }
227 fpst() { # file paste
228 [[ $2 ]] && { echo too many arguments; return 1; }
229 target=${1:-.}
230 cp "$my_f_tempdir"/* "$target"
231 }
232
233 _khfix_common() {
234 local host=${1##*@}
235 local ip port
236 read -r ip port < <(timeout 1 ssh -oBatchMode=yes -oControlMaster=no -oControlPath=/ -v $1 |& sed -rn "s/debug1: Connecting to $host \[([^\]*)] port ([0-9]+).*/\1 \2/p")
237 if [[ ! $ip ]]; then
238 echo "khfix: ssh failed"
239 return 1
240 fi
241 if [[ $port != 22 ]]; then
242 ip_entry="[$ip]:$port"
243 host_entry="[$host]:$port"
244 else
245 ip_entry=$ip
246 host_entry=$host
247 fi
248 ssh-keygen -R "$host_entry" -f $(readlink -f ~/.ssh/known_hosts)
249 echo "khfix: removing key for $ip_entry"
250 ssh-keygen -R "$ip_entry" -f $(readlink -f ~/.ssh/known_hosts)
251 }
252 khfix() { # known hosts fix
253 _khfix_common "$@" || return 1
254 ssh $1 :
255 }
256 khcopy() {
257 _khfix_common "$@"
258 ssh-copy-id $1
259 }
260
261 a() {
262 local x
263 x=$(readlink -nf "${1:-$PWD}")
264 # yes, its kinda dumb that xclip/xsel cant do this in one invocation
265 echo -n "$x" | xclip -selection clipboard
266 echo -n "$x" | xclip
267 }
268
269 ack() { ack-grep "$@"; }
270
271 b() {
272 # backwards
273 c -
274 }
275
276
277 # c. better cd
278 if type -p wcd &>/dev/null; then
279 if [[ $RLC_INSIDE_EMACS ]]; then
280 c() { wcd -c -z 50 -o "$@"; }
281 else
282 # lets see what the fancy terminal does from time to time
283 c() { wcd -c -z 50 "$@"; }
284 fi
285 else
286 c() { cd "$@"; }
287 fi
288
289 c4() { c /var/log/exim4; }
290
291 caa() { git commit --amend --no-edit -a; }
292
293 caf() {
294 # shellcheck disable=SC2033
295 find -L $1 -type f -not \( -name .svn -prune -o -name .git -prune \
296 -o -name .hg -prune -o -name .editor-backups -prune \
297 -o -name .undo-tree-history -prune \) \
298 -exec bash -lc 'hr; echo "$1"; hr; cat "$1"' _ {} \; 2>/dev/null
299
300 }
301
302 calc() { echo "scale=3; $*" | bc -l; }
303 # no having to type quotes, but also no command history:
304 clc() {
305 local x
306 read -r x
307 echo "scale=3; $x" | bc -l
308 }
309
310 cam() {
311 git commit -am "$*"
312 }
313
314
315 ccat () { # config cat. see a config without extra lines.
316 grep '^\s*[^;[:space:]#]' "$@"
317 }
318
319
320 _cdiff-prep() {
321 # join options which are continued to multiples lines onto one line
322 local first=true
323 while IFS= read -r line; do
324 # remove leading spaces/tabs. assumes extglob
325 if [[ $line == "[ ]*" ]]; then
326 line="${line##+( )}"
327 fi
328 if $first; then
329 pastline="$line"
330 first=false
331 elif [[ $line == *=* ]]; then
332 echo "$pastline" >> "$2"
333 pastline="$line"
334 else
335 pastline="$pastline $line"
336 fi
337 done < <(grep -vE '^([ \t]*#|^[ \t]*$)' "$1")
338 echo "$pastline" >> "$2"
339 }
340
341 cdiff() {
342 # diff config files,
343 # setup for format of postfix, eg:
344 # option = stuff[,]
345 # [more stuff]
346 local pastline unified f1 f2
347 unified="$(mktemp)"
348 f1="$(mktemp)"
349 f2="$(mktemp)"
350 _cdiff-prep "$1" "$f1"
351 _cdiff-prep "$2" "$f2"
352 cat "$f1" "$f2" | grep -Po '^[^=]+=' | sort | uniq > "$unified"
353 while IFS= read -r line; do
354 # the default bright red / blue doesnt work in emacs shell
355 dwdiff -cblue,red -A best -d " ," <(grep "^$line" "$f1" || echo ) <(grep "^$line" "$f2" || echo ) | colordiff
356 done < "$unified"
357 }
358
359
360 cat-new-files() {
361 local start=$SECONDS
362 local dir="$1"
363 inotifywait -m "$dir" -e create -e moved_to |
364 # shellcheck disable=SC2030
365 while read -r filedir _ file; do
366 cat "$filedir$file"
367 hr
368 calc $((SECONDS - start)) / 60
369 sleep 5
370 done
371
372 }
373
374 # shellcheck disable=SC2032
375 chown() {
376 # makes it so chown -R symlink affects the symlink and its target.
377 if [[ $1 == -R ]]; then
378 shift
379 command chown -h "$@"
380 command chown -R "$@"
381 else
382 command chown "$@"
383 fi
384 }
385
386 cim() {
387 git commit -m "$*"
388 }
389
390 cl() {
391 # choose recent directory. cl = cd list
392 c =
393 }
394
395 d() { builtin bg; }
396 complete -A stopped -P '"%' -S '"' d
397
398
399 dc() {
400 diff --strip-trailing-cr -w "$@" # diff content
401 }
402
403 despace() {
404 local x y
405 for x in "$@"; do
406 y="${x// /_}"
407 safe_rename "$x" "$y"
408 done
409 }
410
411 dig() {
412 command dig +nostats +nocmd "$@"
413 }
414 # Output with sections sorted, and removal of query id, so 2 dig outputs can be diffed.
415 digsort() {
416 local sec
417 sec=
418 dig +nordflag "$@" | sed -r 's/^(;; ->>HEADER<<-.*), id: .*/\1/' | while read -r l; do
419 if [[ $l == [^\;]* ]]; then
420 sec+="$l"$'\n'
421 else
422 if [[ $sec ]]; then
423 printf "%s" "$sec" | sort
424 sec=
425 fi
426 printf "%s\n" "$l"
427 fi
428 done
429 }
430 # compare digs to the 2 servers
431 # usage: digdiff @server1 @server2 DIG_ARGS
432 # note: only the soa master nameserver will respond with
433 # ra "recursive answer" flag. That difference is meaningless afaik.
434 digdiff() {
435 local s1 s2
436 s1=$1
437 shift
438 s2=$1
439 shift
440 digsort $s1 "$@" | tee /tmp/digdiff
441 diff -u /tmp/digdiff <(digsort $s2 "$@")
442 }
443
444 dt() {
445 date "+%A, %B %d, %r" "$@"
446 }
447
448 dus() { # du, sorted, default arg of
449 du -sh ${@:-*} | sort -h
450 }
451
452
453
454 e() { echo "$@"; }
455
456 # echo args
457 ea() {
458 if (( ! $# )); then
459 echo no args
460 fi
461 for arg; do
462 printf "%qEOL\n" "${arg}"
463 printf "%s" "${arg}" |& hexdump -C
464 done
465 }
466 # echo vars. print var including escapes, etc
467 ev() {
468 if (( ! $# )); then
469 echo no args
470 fi
471 for arg; do
472 printf "%qEOL\n" "${!arg}"
473 printf "%s" "${!arg}" |& hexdump -C
474 done
475 }
476
477
478 ediff() {
479 [[ ${#@} == 2 ]] || { echo "error: ediff requires 2 arguments"; return 1; }
480 emacs --eval "(ediff-files \"$1\" \"$2\")"
481 }
482
483 # mail related
484 etail() {
485 tail -F /var/log/exim4/mainlog -n 200
486 }
487 eless() {
488 less /var/log/exim4/mainlog
489 }
490 eqcat() {
491 exiqgrep -i | while read i; do
492 exim -Mvh $i; hr; exim -Mvb $i; hr;
493 exigrep $i /var/log/exim4/mainlog; hr
494 done
495 }
496
497
498 # shellcheck disable=SC2032
499 f() {
500 # cd forward
501 c +
502 }
503
504 fa() {
505 # find array. make an array of file names found by find into $x
506 # argument: find arguments
507 # return: find results in an array $x
508 while read -rd ''; do
509 x+=("$REPLY");
510 done < <(find "$@" -print0);
511 }
512
513 faf() { # find all files. use -L to follow symlinks
514 find $@ -not \( -name .svn -prune -o -name .git -prune \
515 -o -name .hg -prune -o -name .editor-backups -prune \
516 -o -name .undo-tree-history -prune \) -type f 2>/dev/null
517 }
518
519 # mail related
520 frozen() {
521 rm -rf /tmp/frozen
522 s mailq |gr frozen|awk '{print $3}' | while read -r id; do
523 s exim -Mvl $id
524 echo
525 s exim -Mvh $id
526 echo
527 s exim -Mvb $id
528 echo -e '\n\n##############################\n'
529 done | tee -a /tmp/frozen
530 }
531 frozenrm() {
532 local ids=()
533 while read -r line; do
534 printf '%s\n' "$line"
535 ids+=($(printf '%s\n' "$line" |gr frozen|awk '{print $3}'))
536 done < <(s mailq)
537 echo "sleeping for 2 in case you change your mind"
538 sleep 2
539 s exim -Mrm "${ids[@]}"
540 }
541
542 funce() {
543 # like -e for functions. returns on error.
544 # at the end of the function, disable with:
545 # trap ERR
546 trap 'echo "${BASH_COMMAND:+BASH_COMMAND=\"$BASH_COMMAND\" }
547 ${FUNCNAME:+FUNCNAME=\"$FUNCNAME\" }${LINENO:+LINENO=\"$LINENO\" }\$?=$?"
548 trap ERR
549 return' ERR
550 }
551
552 getdir () {
553 local help="Usage: getdir [--help] PATH
554 Output the directory of PATH, or just PATH if it is a directory."
555 if [[ $1 == --help ]]; then
556 echo "$help"
557 return 0
558 fi
559 if [[ $# -ne 1 ]]; then
560 echo "getdir error: expected 1 argument, got $#"
561 return 1
562 fi
563 if [[ -d $1 ]]; then
564 echo "$1"
565 else
566 local dir
567 dir="$(dirname "$1")"
568 if [[ -d $dir ]]; then
569 echo "$dir"
570 else
571 echo "getdir error: directory does not exist"
572 return 1
573 fi
574 fi
575 }
576
577 git_empty_branch() { # start an empty git branch. carefull, it deletes untracked files.
578 [[ $# == 1 ]] || { echo 'need a branch name!'; return 1;}
579 local root
580 root=$(gitroot) || return 1 # function to set gitroot
581 builtin cd "$root"
582 git symbolic-ref HEAD refs/heads/$1
583 rm .git/index
584 git clean -fdx
585 }
586
587 # shellcheck disable=SC2120
588 gitroot() {
589 local help="Usage: gitroot [--help]
590 Print the full path to the root of the current git repo
591
592 Handles being within a .git directory, unlike git rev-parse --show-toplevel,
593 and works in older versions of git which did not have that."
594 if [[ $1 == --help ]]; then
595 echo "$help"
596 return
597 fi
598 local p
599 p=$(git rev-parse --git-dir) || { echo "error: not in a git repo" ; return 1; }
600 [[ $p != /* ]] && p=$PWD
601 echo "${p%%/.git}"
602 }
603
604 gh() {
605 # i got an error, gh not found when doing a pull request, it seems like it wants itself in it\'s path.
606 local _oldpath="$PATH"
607 PATH="$PATH:$HOME/node_modules/.bin"
608 command gh "$@"
609 PATH="$_oldpath"
610 }
611
612 gmacs() {
613 # quit will prompt if the program crashes.
614 gdb -ex=r -ex=quit --args emacs "$@"; r;
615 }
616
617 gdkill() {
618 # kill the emacs daemon
619 pk1 emacs --daemon
620 }
621
622 gr() {
623 grep -iIP --color=auto "$@"
624 }
625
626 grr() { # grep recursive
627 if [[ ${#@} == 1 ]]; then
628 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -RiIP --color=auto "$@" .
629 else
630 grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -RiIP --color=auto "$@"
631 fi
632 }
633 rg() {
634 command rg -i -M 200 "$@"
635 }
636
637 hr() { # horizontal row. used to break up output
638
639 printf "$(tput setaf 5)â–ˆ$(tput sgr0)%.0s" $(eval echo {1..${COLUMNS:-60}})
640 echo
641 }
642
643 hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; done }
644
645 # get latest hub and run it
646 # main command to use:
647 # hub pull-request --no-edit
648 # --no-edit means to use the first commit\'s message as the pull request message.
649 # Also, you need to use a feature branch, not master in your fork.
650 # On first use, you input username/pass and it gets an oath token so you dont have to repeat
651 # it\'s at ~/.config/hub
652 hub() {
653 local up uptar updir p
654 p=/github/hub/releases/
655 up=https://github.com/$(curl -s https://github.com$p| grep -o $p'download/[^/]*/hub-linux-amd64[^"]*' | head -n1)
656 uptar=${up##*/}
657 updir=${uptar%.tgz}
658 if [[ ! -e /a/opt/$updir ]]; then
659 rm -rf /a/opt/hub-linux-amd64*
660 wget -P /a/opt $up
661 tar -C /a/opt -zxf /a/opt/$uptar
662 rm -f /a/opt/$uptar
663 s /a/opt/$updir/install
664 fi
665
666 # save token across computers
667 if [[ ! -L ~/.config/hub ]]; then
668 if [[ -e ~/.config/hub ]]; then
669 mv ~/.config/hub /p/c/subdir_files/.config/
670 fi
671 if [[ -e /p/c/subdir_files/.config/hub ]]; then
672 conflink
673 fi
674 fi
675 command hub "$@"
676 }
677
678 i() { git "$@"; }
679 # modified from ~/local/bin/git-completion.bash
680 # other completion commands are mostly taken from bash_completion package
681 complete -o bashdefault -o default -o nospace -F _git i 2>/dev/null \
682 || complete -o default -o nospace -F _git i
683
684 if ! type service &>/dev/null; then
685 service() {
686 echo actually running: systemctl $2 $1
687 systemctl $2 $1
688 }
689 fi
690
691
692
693 ic() {
694 # fast commit all
695 git commit -am "$*"
696 }
697
698
699
700 ifn() {
701 # insensitive find
702 find -L . -not \( -name .svn -prune -o -name .git -prune \
703 -o -name .hg -prune -o -name .editor-backups -prune \
704 -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null
705 }
706
707 ipdrop() {
708 s iptables -A INPUT -s $1 -j DROP
709 }
710
711
712 istext() {
713 grep -Il "" "$@" &>/dev/null
714 }
715
716 jtail() {
717 journalctl -n 10000 -f "$@"
718 }
719 jr() { journalctl "$@" ; }
720 jrf() { journalctl -f "$@" ; }
721
722 l() {
723 if [[ $PWD == /[iap] ]]; then
724 command ls -A --color=auto -I lost+found "$@"
725 else
726 command ls -A --color=auto "$@"
727 fi
728 }
729
730
731 lcn() { locate -i "*$**"; }
732
733 lg() { LC_COLLATE=C.UTF-8 ll --group-directories-first; }
734
735 lt() { ll -tr "$@"; }
736
737 lld() { ll -d "$@"; }
738
739 low() { # make filenames lowercase, remove bad chars
740 local f new
741 for f in "$@"; do
742 new="${f,,}" # downcase
743 new="${new//[^[:alnum:]._-]/_}" # sub bad chars
744 new="${new#"${new%%[[:alnum:]]*}"}" # remove leading/trailing non-alnum
745 new="${new%"${new##*[[:alnum:]]}"}"
746 # remove bad underscores, like __ and _._
747 new=$(echo $new | sed -r 's/__+/_/g;s/_+([.-])|([.-])_+/\1/g')
748 safe_rename "$f" "$new" || return 1
749 done
750 return 0
751 }
752
753 lower() { # make first letter of filenames lowercase.
754 local x
755 for x in "$@"; do
756 if [[ ${x::1} == [A-Z] ]]; then
757 y=$(tr '[:upper:]' '[:lower:]' <<<"${x::1}")"${x:1}"
758 safe_rename "$x" "$y" || return 1
759 fi
760 done
761 }
762
763
764 k() { # history search
765 grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | tail -n 80;
766 }
767
768 ks() { # history search
769 grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | uniq;
770 }
771
772
773 make-targets() {
774 # show make targets, via http://stackoverflow.com/questions/3063507/list-goals-targets-in-gnu-make
775 make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'
776 }
777
778 mkc() {
779 mkdir "$1"
780 c "$1"
781 }
782
783 mkct() {
784 mkc $(mktemp -d)
785 }
786
787 mkt() { # mkdir and touch file
788 local path="$1"
789 mkdir -p "$(dirname "$path")"
790 touch "$path"
791 }
792
793 # shellcheck disable=SC2032
794 mkdir() { command mkdir -p "$@"; }
795
796 nopanic() {
797 # shellcheck disable=SC2024
798 sudo tee -a /var/log/exim4/paniclog-archive </var/log/exim4/paniclog; sudo truncate -s0 /var/log/exim4/paniclog
799 }
800
801 p8() { ping 8.8.8.8; }
802 p6() { ping6 2001:4860:4860::8888; }
803
804 pkx() { # package extract
805 local pkg cached tmp f
806 c $(mktemp -d)
807 pkg=$1
808 # shellcheck disable=SC2012
809 cached=$(ls -t /var/cache/apt/archives/$pkg* | tail -n1 2>/dev/null)
810 if [[ $cached ]]; then
811 cp $cached .
812 else
813 aptitude download $pkg || return 1
814 fi
815 tmp=(*); f=${tmp[0]} # only 1 expected
816 ex $f
817 rm -f $f
818 }
819
820 # pgrep and kill
821 pk1() {
822 local pid
823 pid=($(pgrep -f "$*"))
824 case ${#pid[@]} in
825 1)
826 # shellcheck disable=SC2128
827 {
828 ps -F $pid
829 m kill $pid
830 }
831 ;;
832 0) echo "no pid found" ;;
833 *)
834 ps -F ${pid[@]}
835 ;;
836 esac
837 }
838
839 pubip() { curl -4s https://icanhazip.com; }
840 pubip6() { curl -6s https://icanhazip.com; }
841 whatismyip() { pubip; }
842
843 pwgen() {
844 # -m = min length
845 # -x = max length
846 # -t = print pronunciation
847 apg -m 14 -x 17 -t
848 for (( i=0; i<10; i++ )); do
849 shuf -n3 /usr/share/hunspell/en_US.dic | sed 's,/.*,,' | paste -sd . -
850
851 done
852 }
853
854 pwlong() {
855 # -M CLN = use Caps, Lowercase, Numbers
856 # -n 1 = 1 password
857 # -a 1 = use random instead of pronounceable algorithm
858 apg -m 50 -x 70 -n 1 -a 1 -M CLN
859 }
860
861
862 q() { # start / launch a program in the backround and redir output to null
863 "$@" &> /dev/null &
864 }
865
866 # shellcheck disable=SC2120
867 r() {
868 history -a # save history
869 exit ${1:0}
870 # i had this redir, not sure why
871 # exit "$@" 2>/dev/null
872 }
873
874 rl() {
875 # rsync, root is required to keep permissions right.
876 # rsync --archive --human-readable --verbose --itemize-changes --checksum \(-ahvic\) \
877 # --no-times --delete
878 # basically, make an exact copy, use checksums instead of file times to be more accurate
879 rsync -ahvic --delete "$@"
880 }
881 rld() {
882 # like rlu, but dont delete files on the target end which
883 # do not exist on the original end.
884 rsync -ahvic "$@"
885 }
886 complete -F _rsync -o nospace rld rl rlt
887
888 rlt() {
889 # rl without preserving modification time.
890 rsync -ahvic --delete --no-t "$@"
891 }
892
893 rlu() { # [OPTS] HOST PATH
894 # eg. rlu -opts frodo /testpath
895 # relative paths will expanded with readlink -f.
896 opts=("${@:1:$#-2}") # 1 to last -2
897 path="${*:$#}" # last
898 host="${*:$#-1:1}" # last -1
899 if [[ $path == .* ]]; then
900 path=$(readlink -f $path)
901 fi
902 # rync here uses checksum instead of time so we dont mess with
903 # unison relying on time as much. g is for group, same reason
904 # to keep up with unison.
905 s rsync -rlpchviog --relative "${opts[@]}" "$path" "root@$host:/";
906 }
907
908 rmstrips() {
909 ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less
910 }
911
912 s() {
913 # background
914 # I use a function because otherwise we cant use in a script,
915 # cant assign to variable.
916 #
917 # note: gksudo is recommended for X apps because it does not set the
918 # home directory to the same, and thus apps writing to ~ fuck things up
919 # with root owned files.
920 #
921 if [[ $EUID != 0 || $1 == -* ]]; then
922 SUDOD="$PWD" sudo -i "$@"
923 else
924 "$@"
925 fi
926 }
927
928 safe_rename() { # warn and dont rename if file exists.
929 # mv -n exists, but it\'s silent
930 if [[ $# != 2 ]]; then
931 echo safe_rename error: $# args, need 2 >2
932 return 1
933 fi
934 if [[ $1 != "$2" ]]; then # yes, we want to silently ignore this
935 if [[ -e $2 || -L $2 ]]; then
936 echo "Cannot rename $1 to $2 as it already exists."
937 else
938 mv -vi "$1" "$2"
939 fi
940 fi
941 }
942
943
944 sb() { # sudo bash -c
945 # use sb instead of s is for sudo redirections,
946 # eg. sb 'echo "ok fine" > /etc/file'
947 local SUDOD="$PWD"
948 sudo -i bash -c "$@"
949 }
950 complete -F _root_command s sb
951
952
953 ser() {
954 local s; [[ $EUID != 0 ]] && s=s
955 if type -p systemctl &>/dev/null; then
956 $s systemctl $1 $2
957 else
958 $s service $2 $1
959 fi
960 }
961 # like restart, but do nothing if its not already started
962 srestart() {
963 local service=$1
964 if [[ $(s systemctl --no-pager show -p ActiveState $service ) == ActiveState=active ]]; then
965 systemctl restart $service
966 fi
967 }
968
969 setini() { # set a value in a .ini style file
970 key="$1" value="$2" section="$3" file="$4"
971 if [[ -s $file ]]; then
972 sed -ri -f - "$file" <<EOF
973 # remove existing keys
974 / *\[$section\]/,/^ *\[[^]]+\]/{/^\s*$key[[:space:]=]/d}
975 # add key
976 /^\s*\[$section\]/a $key=$value
977 # from section to eof, do nothing
978 /^\s*\[$section\]/,\$b
979 # on the last line, if we haven't found section yet, add section and key
980 \$a [$section]\\
981 $key=$value
982 EOF
983 else
984 cat >"$file" <<EOF
985 [$section]
986 $key=$value
987 EOF
988 fi
989 }
990
991 sgo() { # service go
992 service=$1
993 ser restart $service || return 1
994 if type -p systemctl &>/dev/null; then
995 ser enable $service
996 fi
997 }
998
999 sgu() {
1000 systemctl list-unit-files | rg "$@"
1001 }
1002
1003
1004 sk() {
1005 # 2086: unquoted $var
1006 # 2046: unquoted $(cmd)
1007 # 2068: Double quote array expansions to avoid re-splitting elements.
1008 # 2119: Functions with optional args get bad warnings when none are passed.
1009 # 2033: too many false positives for thing that will never work, passing shell function to find.
1010 # i had -x as an arg, but debian testing(stretch) doesn\'t support it
1011 shellcheck -x -e 2086,2046,2068,2119,2033 "$@"
1012 # had this before. not sure what it is 2119
1013 }
1014
1015
1016 slog() {
1017 # log with script. timing is $1.t and script is $1.s
1018 # -l to save to ~/typescripts/
1019 # -t to add a timestamp to the filenames
1020 local logdir do_stamp arg_base
1021 (( $# >= 1 )) || { echo "arguments wrong"; return 1; }
1022 logdir="/a/dt/"
1023 do_stamp=false
1024 while getopts "lt" option
1025 do
1026 case $option in
1027 l ) arg_base=$logdir ;;
1028 t ) do_stamp=true ;;
1029 esac
1030 done
1031 shift $((OPTIND - 1))
1032 arg_base+=$1
1033 [[ -e $logdir ]] || mkdir -p $logdir
1034 $do_stamp && arg_base+=$(date +%F.%T%z)
1035 script -t $arg_base.s 2> $arg_base.t
1036 }
1037 splay() { # script replay
1038 #logRoot="$HOME/typescripts/"
1039 #scriptreplay "$logRoot$1.t" "$logRoot$1.s"
1040 scriptreplay "$1.t" "$1.s"
1041 }
1042
1043 sr() {
1044 # sudo redo. be aware, this command may not work right on strange distros or earlier software
1045 if [[ $# == 0 ]]; then
1046 sudo -E bash -c -l "$(history -p '!!')"
1047 else
1048 echo this command redos last history item. no argument is accepted
1049 fi
1050 }
1051
1052 srm () {
1053 # with -ll, less secure but faster.
1054 command srm -ll "$@"
1055 }
1056
1057 srun() {
1058 scp $2 $1:/tmp
1059 ssh $1 /tmp/${2##*/} $(printf "%q\n" "${@:2}")
1060 }
1061
1062
1063 swap() {
1064 local tmp
1065 tmp=$(mktemp)
1066 mv $1 $tmp
1067 mv $2 $1
1068 mv $tmp $2
1069 }
1070
1071 tclock() { # terminal clock
1072 local x
1073 clear
1074 date +%l:%_M
1075 len=60
1076 # this goes to full width
1077 #len=${1:-$((COLUMNS -7))}
1078 x=1
1079 while true; do
1080 if (( x == len )); then
1081 end=true
1082 d="$(date +%l:%_M) "
1083 else
1084 end=false
1085 d=$(date +%l:%M:%_S)
1086 fi
1087 echo -en "\r"
1088 echo -n "$d"
1089 for ((i=0; i<x; i++)); do
1090 if (( i % 6 )); then
1091 echo -n _
1092 else
1093 echo -n .
1094 fi
1095 done
1096 if $end; then
1097 echo
1098 x=1
1099 else
1100 x=$((x+1))
1101 fi
1102 sleep 5
1103 done
1104 }
1105
1106
1107 te() {
1108 # test existence / exists
1109 local ret=0
1110 for x in "$@"; do
1111 [[ -e "$x" || -L "$x" ]] || ret=1
1112 done
1113 return $ret
1114 }
1115
1116
1117 tx() { # toggle set -x, and the prompt so it doesnt spam
1118 if [[ $- == *x* ]]; then
1119 set +x
1120 PROMPT_COMMAND=prompt-command
1121 # disabled due to issue on stretch, running ll we get error. something
1122 # about the DEBUG trap is broken
1123 # if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
1124 # trap 'settitle "$BASH_COMMAND"' DEBUG
1125 # fi
1126 else
1127 # normally, i would just execute these commands in the function.
1128 # however, DEBUG is not inherited, so we need to run it outside a function.
1129 # And we want to run set -x afterwards to avoid spam, so we cram everything
1130 # in here, and then it will run after this function is done.
1131 #PROMPT_COMMAND='trap DEBUG; unset PROMPT_COMMAND; PS1="\w \$ "; set -x'
1132
1133 unset PROMPT_COMMAND
1134 PS1="\w \$ "
1135 set -x
1136 fi
1137 }
1138
1139 psnetns() {
1140 # show all processes in the network namespace $1.
1141 # blank entries appear to be subprocesses/threads
1142 local x netns
1143 netns=$1
1144 ps -w | head -n 1
1145 s find -L /proc/[1-9]*/task/*/ns/net -samefile /run/netns/$netns | cut -d/ -f5 | \
1146 while read -r l; do
1147 x=$(ps -w --no-headers -p $l);
1148 if [[ $x ]]; then echo "$x"; else echo $l; fi;
1149 done
1150 }
1151
1152 m() { printf "%s\n" "$*"; "$@"; }
1153
1154
1155 virshrm() {
1156 for x in "$@"; do virsh destroy "$x"; virsh undefine "$x"; done
1157 }
1158
1159 vm-set-listen(){
1160 local t
1161 t=$(mktemp)
1162 local vm=$1
1163 local ip=$2
1164 s virsh dumpxml $vm | sed -r "s/(<listen.*address=')([^']+)/\1$ip/" | \
1165 sed -r "s/listen='[^']+/listen='$ip/"> $t
1166 s virsh undefine $vm
1167 s virsh define $t
1168 }
1169
1170
1171 vmshare() {
1172 vm-set-listen $1 0.0.0.0
1173 }
1174
1175
1176 vmunshare() {
1177 vm-set-listen $1 127.0.0.1
1178 }
1179
1180 # * misc stuff
1181
1182
1183 # temporary variables to test colorization
1184 # some copied from gentoo /etc/bash/bashrc,
1185 use_color=false
1186 # dircolors --print-database uses its own built-in database
1187 # instead of using /etc/DIR_COLORS. Try to use the external file
1188 # first to take advantage of user additions.
1189 safe_term=${TERM//[^[:alnum:]]/?} # sanitize TERM
1190 match_lhs=""
1191 [[ -f ~/.dir_colors ]] && match_lhs="${match_lhs}$(<~/.dir_colors)"
1192 [[ -f /etc/DIR_COLORS ]] && match_lhs="${match_lhs}$(</etc/DIR_COLORS)"
1193 [[ -z ${match_lhs} ]] \
1194 && type -P dircolors >/dev/null \
1195 && match_lhs=$(dircolors --print-database)
1196 # test if our $TERM is in the TERM values in dircolor
1197 [[ $'\n'${match_lhs} == *$'\n'"TERM "${safe_term}* ]] && use_color=true
1198
1199
1200 if ${use_color} && [[ $- == *i* ]]; then
1201
1202 term_bold="$(tput bold)"
1203 term_red="$(tput setaf 1)"
1204 term_green="$(tput setaf 2)"
1205 term_yellow="$(tput setaf 3)"
1206 term_purple="$(tput setaf 5)"
1207 term_nocolor="$(tput sgr0)" # no font attributes
1208
1209 # unused so far. commented for shellcheck
1210 # term_underl="$(tput smul)"
1211 # term_blue="$(tput setaf 4)"
1212 # term_cyan="$(tput setaf 6)"
1213
1214 fi
1215 # Try to keep environment pollution down, EPA loves us.
1216 unset safe_term match_lhs use_color
1217
1218 # * prompt
1219
1220
1221 if [[ $- == *i* ]]; then
1222
1223 # this needs to come before next ps1 stuff
1224 # this stuff needs bash 4, feb 2009,
1225 # old enough to no longer condition on $BASH_VERSION anymore
1226 shopt -s autocd
1227 shopt -s dirspell
1228 PS1='\w'
1229 if [[ $- == *i* ]] && [[ ! $RLC_INSIDE_EMACS ]]; then
1230 PROMPT_DIRTRIM=2
1231 bind -m vi-command B:shell-backward-word
1232 bind -m vi-command W:shell-forward-word
1233 fi
1234
1235 if [[ $SSH_CLIENT || $SUDO_USER ]]; then
1236 PS1="\h $PS1"
1237 fi
1238
1239 # emacs terminal has problems if this runs slowly,
1240 # so I've thrown a bunch of things at the wall to speed it up.
1241 prompt-command() {
1242 local return=$? # this MUST COME FIRST
1243 local ps_char ps_color
1244 unset IFS
1245
1246 history -a # save history
1247
1248 case $return in
1249 0) ps_color="$term_purple"
1250 ps_char='\$'
1251 ;;
1252 1) ps_color="$term_green"
1253 ps_char="$return \\$"
1254 ;;
1255 *) ps_color="$term_yellow"
1256 ps_char="$return \\$"
1257 ;;
1258 esac
1259 if [[ ! -O . ]]; then # not owner
1260 if [[ -w . ]]; then # writable
1261 ps_color="$term_bold$term_red"
1262 else
1263 ps_color="$term_bold$term_green"
1264 fi
1265 fi
1266
1267 # faster than sourceing the file im guessing
1268 if [[ -e /dev/shm/iank-status ]]; then
1269 eval $(< /dev/shm/iank-status)
1270 fi
1271 if [[ ! $SSH_CLIENT && $MAIL_HOST != "$HOSTNAME" ]]; then
1272 ps_char="@ $ps_char"
1273 fi
1274 PS1="${PS1%"${PS1#*[wW]}"} \[$ps_color\]$ps_char\[$term_nocolor\] "
1275 }
1276 PROMPT_COMMAND=prompt-command
1277
1278 settitle () {
1279 if [[ $TERM == screen* ]]; then
1280 local title_escape="\033]..2;"
1281 else
1282 local title_escape="\033]0;"
1283 fi
1284 if [[ $0 != prompt-command ]]; then
1285 echo -ne "$title_escape$USER@$HOSTNAME ${PWD/#$HOME/~} "
1286 printf "%s" "$*"
1287 echo -ne "\007"
1288 fi
1289 }
1290
1291 # for titlebar.
1292 # condition from the screen man page i think.
1293 # note: duplicated in tx()
1294 # disabled. see note in tx
1295 # if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
1296 # trap 'settitle "$BASH_COMMAND"' DEBUG
1297 # else
1298 # trap DEBUG
1299 # fi
1300
1301 fi
1302
1303 # * stuff that makes sense to be at the end
1304
1305
1306 # best practice
1307 unset IFS
1308
1309 # shellcheck disable=SC1090
1310 [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
1311
1312
1313 # ensure no bad programs appending to this file will have an affect
1314 return 0