------------------------------------------------------------------------------- A random collection of general shell functions to perform various tasks. This file can be shell sourced to define all the functions given For stand alone shell scripts it is recomended that the individual routines be copy and pasted into the shell script itself. NOTE: some of the following routines use the following file descriptors 0, 1, 2 Default STDIN, STDOUT, STDERR 3, 4, 5 Save of STDIN, STDOUT, STDERR # ----------------------------------------------------------------------------- # cat using shell builtins only cat() { while read line; do echo $line; done < "$1" } # ----------------------------------------------------------------------------- # echo without return (that works on all systems) if [ "X`echo -n`" = "X-n" ]; then echo_n() { echo ${1+"$@"}"\c"; } else echo_n() { echo -n ${1+"$@"}; } fi # ----------------------------------------------------------------------------- # Is command available # Eg: if cmd_found COMMAND; then COMMAND args; fi cmd_found() { expr match "`type $1 2>&1`" '.*not found' == 0 >/dev/null } # ----------------------------------------------------------------------------- # start a shell function in background with nohup # https://stackoverflow.com/questions/16435629 # Signals SIGHUP and SIGINT are ignored. execute::nohup_function() { ( trap '' HUP INT "$@" ) >"log" & } # ----------------------------------------------------------------------------- # Replacement 'seq' with format and start incr end options # Just like the real thing (missing floating-point and reverse options) # Requires "printf" (or some equivelent) to be available. # # Examples # seq_format -f "%03d" 15 # seq_format 1 10 # seq_format 1 2 15 # if cmd_found seq; then :; else seq() { if [ "X$1" = "X-f" ] then format="$2"; shift; shift else format="%d" fi case $# in 1) i=1 incr=1 end=$1 ;; 2) i=$1 incr=1 end=$2 ;; *) i=$1 incr=$2 end=$3 ;; esac while [ $i -le $end ]; do printf "$format\n" $i; i=`expr $i + $incr`; done } fi # Simplier restricted forms of "seq" # seq_count 10 seq_count() { i=1; while [ $i -le $1 ]; do echo $i; i=`expr $i + 1`; done } # simple_seq 1 2 10 simple_seq() { i=$1; while [ $i -le $3 ]; do echo $i; i=`expr $i + $2`; done } # ----------------------------------------------------------------------------- # Messages in a Box # # The "boxes" package that has a lot of options, BUT does not handle Unicode! # It seems designed more for large scale ASCII Art style boxes, which is good # for email signatures but not for a 'simple box' around the text. # # ASCII Box wrapped around single line (extra space) box_line() { local s="$*" printf "+-${s//?/-}-+\n" printf "| ${s//?/ } |\n" printf "| $s |\n" printf "| ${s//?/ } |\n" printf "+-${s//?/-}-+\n" } # box_line " message to put in the box " # ----- # ASCII box to fit to multi-line text box_fit() { local s=("$@") line edge width for line in "${s[@]}"; do # determine box width ((width<${#line})) && { edge="${line//?/-}"; width="${#line}"; } done printf "+-%s-+\n" "$edge" # output boxed message printf "| %*s |\n" "-$width" for line in "${s[@]}"; do printf "| %*s |\n" "-$width" "$line" done printf "| %*s |\n" "-$width" printf "+-%s-+\n" "$edge" } #box_fit 'first line' 'one more line' 'even more lines' # TTY wide box... # With different styles of simple boxes. # See script... https://antofthy.gitlab.io/software/#box_text # Select the box style box_style="rounded" case "$box_style" in # ASCII Art banner) l="#" v="#" ctl="#"; ctr="#" cbl="#" cbr="#" ;; curses) l="-" v="|" ctl="+"; ctr="+" cbl="+" cbr="+" ;; ascii) l="-" v="|" ctl="."; ctr="." cbl="'" cbr="'" ;; lines) l="-" v=" " ctl="-"; ctr="-" cbl="-" cbr="-" ;; # Unicode box) l="─" v="│" ctl="┌"; ctr="┐" cbl="└" cbr="┘" ;; rounded) l="─" v="│" ctl="╭"; ctr="╮" cbl="╰" cbr="╯" ;; double) l="═" v="║" ctl="╔"; ctr="╗" cbl="╚" cbr="╝" ;; # Error *) echo >&2 "Unknown Box Style -- ABORTING"; exit 10 ;; esac box() { printf -v line "%*s" 75 printf " %s%s%s\n" "$ctl" "${line//?/$l}" "$ctr" while read; do printf " %s %*s %s\n" "$v" -73 "$REPLY" "$v" done printf " %s%s%s\n" "$cbl" "${line//?/$l}" "$cbr" } #echo " message to put in the box " | box # ----------------------------------------------------------------------------- # -------------------- User Input ------------------- # Set to read single key strokes # EG; cbreak_on; key=`getkey`; cbreak_off # If using this style of input you should turn cbreak on continuously # BUT ensure it is turned off on exit using a trap command # exec 0<&3 # Save STDIN, -- must be terminal input cbreak_on() { stty 0<&3 cbreak -echo; } cbreak_off() { stty 0<&3 -cbreak echo; } getkey() { dd bs=1 count=1 0<&3 2>/dev/null } # Design your own trap # trap "cbreak_off; exit 0" 0 # trap "cbreak_off; exit 1" 1 2 3 15 # ----------------------------------------------------------------------------- # Turn of echo for password reads # EG; echo_off; read passwd; echo_on; echo '' echo_off() { stty -echo; } echo_on() { stty echo; } # ----------------------------------------------------------------------------- # User user a y/n/number question, with default answer (y,n,number) # For example: # install=`ask_user "Install this package" y` # install=`ask_user "Danger! Do you want to proceed?" n` # index=`ask_user "What file [1-10]" 0` # Output will be either "true", a empty string, or a number as appropriate # exec 4>&1 # save the default stdout for the next routine ask_user() { # Usage: ask question y/n case "$2" in y) echo_n >&4 "$1 (Y/n)? " ;; n) echo_n >&4 "$1 (y/N)? " ;; [0-9]*) echo_n >&4 "$1 (def=$2)? " ;; *) echo >&4 "$1 (def=$2)? " exit 1 ;; esac input='' read input [ -z "$input" ] && input="$2" case "$2" in n) [ "$input" = y -o "$input" = Y ] && echo "true" ;; y) [ "$input" = n -o "$input" = N ] || echo "true" ;; [0-9]*) expr "$input" + 0 2>/dev/null || echo '0' ;; # convert to a number *) echo "$input" ;; esac } # ----------------------------------------------------------------------------- # ---------------------- Network --------------------- # Convert Hostname <-> IP using the local machines named cache or /etc/hosts # Usally a lot faster than directly using the slow nslookup. # NOTE: The FQDN of an IP is the IP! fqdn() { perl -e 'print( (gethostbyname(shift))[0] || "", "\n");' $1 } ip_name() { perl -MSocket \ -e 'print( (gethostbyaddr( inet_aton(shift), AF_INET))[0] || "", "\n");' $1 } name_ip() { # return machines IP address or empty string perl -MSocket -e ' $ip = (gethostbyname(shift))[4] || ""; print $ip ? inet_ntoa( $ip ) : "", "\n"; ' $1 } ifconfig_ip() { # WARNING: for GU subnet use only ifconfig -a | sed -n 's/.*inet [^1]*\(132\.234\.[0-9.]*\).*/\1/p' } # Do a nslookup for FQDN with timeout - even if primary namserver is down # EG: fqdn=`nslookup_fqdn "$4.$3.$2.$1.in-addr.arpa"` # This can be simplified using a "timeout" program # https://antofthy.gitlab.io/software/#timeout # TIMEOUT=5 nslookup_fqdn() { exec 5>&2 # save the default stderr for real offical errors { # Background the required command - note the process id nslookup <<-"EOF" | sed -n '/arpa/s/.*name \= //p' & set type=PTR $1 EOF cmd_pid=$! # # Wait for it (sleep pipeline) - note sleep's process id sleep $TIMEOUT | ( read nothing # wait on sleep to finish kill -0 $cmd_pid || exit # nslookup finished? - exit kill $cmd_pid # no -- kill it! echo >&5 "WARNING: nslookup failed to return in $TIMEOUT seconds!" ) & sleep_pid=$! # # wait for nslookup to finish or be killed wait $cmd_pid kill -0 $sleep_pid || return; # sleep finished? kill $sleep_pid # no - kill it } 2>/dev/null # get rid of all normal error output exec 5>&- } # -----------------------------------------------------------------------------