#!/bin/bash # # functions_vk.bash # # Volker's function library for bash shell scripts. # This file must be sourced, not executed!! # # Load it at the start of the script with # checkbash2=1 requireversion=NN source functions_vk.bash # The two variable assignments may be left out if that functionality is not # required. # # Copyright (C) 1999-2013 by Volker Kuhlmann # http://volker.top.geek.nz/contact.html # All rights reserved. # Released under the terms of the GNU General Public License (GPL) Version 2. # See http://www.gnu.org/ for details. # # Volker Kuhlmann # 16 Oct 1999 # 28, 29 Mar; 12 Apr; 23, 27 May; 27 Jul; 21 Aug; 10, 24, 25 Oct 2000 # 2, 4, 6, 19 Sep 2001 # 26 Jan; 5 Apr; 16 May; 1 Sep; 9 Nov; 5 Dec 2002 # 6 Mar; 10, 11, 17, 29, 30 May; 11, 25, 27 Jun; 27 Jul; # 5, 6, 8, 9, 23, 24 Aug; 7 Sep; 4, 9, 12, 24 Dec 2003 # 3 Mar; 27 Nov; 4 Dec 2004 # 28 Jan; 21, 30 Oct 2005 # 27, 31 Jan; 26 Feb; 3 Jun; 3 Oct; 6, 11, 13, 19 Dec 2006 # 7 Jan 2007 # 17 Oct; 29 Dec 2008 # 19, 20 Mar 2011 # 22 Jan 2012 1.9.26 63 # 11 Dec 2012 1.9.27 64 # Help() pipes to $PAGER if output is to a terminal. # Add expandtimeunits(). # 14 Dec 2012 1.9.28 65 # Add checkarg4(). # 21 Jan 2013 1.9.29 66 # Help() uses $PAGER also when called with optional argument. # 07 Mar 2013 1.9.30 67 # Add mypager(). Change download URL in library version check. # 17 Mar 2013 1.9.31 68 # Add ErrNothingToDo (exit status 0). # 17 Jun 2013 1.9.32 69 # Improve error handling in expandtimeunits(). # Added get_tempfile() arguments 2 and 3. Added get_tempdir(). # NOTE: dd bs=M count=N exits zero if not enough data was read! # 19 Aug 2013 1.9.33 70 # Fix display bug in RunLog() if dryrun is on. # Add sec2m(). # 27 Aug 2013 1.9.34 71 # Add DryNoLog(). # 13 Nov 2013 1.9.35 72 # makedir() returns failure status. # 21 Sep 2014 1.9.36 73 # Add $DEL. # 10 Jan 2018 1.9.37 74 # Quote variables in checkforXXX(). # Reliably remove temp file in cdimg_check_md5_(). # Clarify commment in expandtimeunits(). # Use 14 instead of 10 random characters in get_tempfile(), get_tempdir(). # 07 Jul 2018 1.9.38 75 # Update VERSION_FUNCLIB correctly. # Add ErrNoAction. # Remove trailing white space. # 21 Aug 2018 1.9.39 76 # Add ErrNoFunction. # chr0(){ echo @;};chr1(){ echo .;} VERSION_FUNCLIB_NUM="76" # This integer will be incremented with each change! VERSION_FUNCLIB="VK 1.9.39 ($VERSION_FUNCLIB_NUM), 21 Aug 2018" AUTHOR_FUNCLIB="Volker Kuhlmann " COPYRIGHT_FUNCLIB="Copyright (C) 1999-2018" #### Check bash is version 2 #### # # Only if checkbash2 is non-empty: check bash version, and run under 2.x # if the current one is 1.x and a 2.x can be found. Error if no 2.x available. # if [ -n "$checkbash2" ]; then if [ ${BASH_VERSION%%.*} -lt 2 ]; then b="`type -p -a bash2 bash-2 bash_2 bash-2.x bash-2.X bash-2.03.0 | head -1`" if [ -x "$b" ]; then v="`$b -version &2 echo "This script requires bash version 2. Sorry, but" echo " - bash 1 is too pathetic" echo " - ksh is neither syntax compatible nor freely available" echo " - pdksh is no advantage over bash 2" echo " - tcsh lacks essential features (although some things are nicer" echo " than in bash)" exit 49 fi fi fi #### Check library version number #### # if [ -n "$requireversion" ]; then : ${lib:=functions_vk.bash} : ${url:=http://volker.top.geek.nz/soft/} if [ "${VERSION_FUNCLIB_NUM:-0}" -lt "$requireversion" ]; then echo 1>&2 "\ Your version of $lib ($VERSION_FUNCLIB) in $(dirname $(type -p $lib)) is too old for ${0##*/}. Please get a new one from e.g. $url" exit 51 fi unset lib url fi #### Version, Usage, Help #### # # Call only these from application: Version, Usage, Help # Application must define: show_usage, show_help # Of course these functions here may also be redefined (or ignored). # show_version() { echo "${0##*/} version $VERSION" test -n "$COPYRIGHT" && echo -n "$COPYRIGHT" || echo -n "Copyright (C)" test -n "$AUTHOR" && echo " by $AUTHOR" || echo "" #echo "(Using library $VERSION_FUNCLIB${AUTHOR_FUNCLIB:+ by $AUTHOR_FUNCLIB})" echo "(Using functions_vk.bash $VERSION_FUNCLIB)" } Version() { show_version; exitwith ErrOK; } # obsolete, for compatibility only: version() { show_version; } show_usage_withhelp() { show_usage show_usage_helptext } show_usage_helptext() { test -n "$CALL_WITH_HELP_TEXT" && echo "$CALL_WITH_HELP_TEXT " } Usage() { test -n "$1" && exitwith ErrUsage show_usage_withhelp show_usage exitwith ErrOK } show_help_pager() { # Not using mypager -nodefault here to avoid unnecessary cat. if [ -t 1 -a -n "$PAGER" ]; then show_help | $PAGER else show_help fi } Help() { test -n "$1" && exitwith ErrHelp show_help_pager show_help_pager exitwith ErrOK } #### Error/Exit codes # # User progam needs to redefine exitwith (by copying this exitwith() and # extending it) and provide error messages for new error codes introduced by # the user program. The user program may use the error names defined in # exitwith_funclib() as well. # $CALL_WITH_HELP_TEXT may be used, as well as modified, by caller. # CALL_WITH_HELP_TEXT="Call with --help for help." exitwith_funclib() { exec 1>&2 # write stdout on stderr instead case "$1" in ErrOK|ErrVersion) exit 0;; ErrNothingToDo) echo "Nothing to do." exit 0;; ErrUsage|ErrHelp|ErrUnspecified) # Output generated by function/program $2, if given. test -n "$2" && { shift; "$@"; } exit 1;; ErrNoAction) echo "No action was given. Tell me what to do. See --help for details." exit 1;; ## I/O errors ErrExist) echo "Already exists: '$2'." exit 2;; ErrNoExist) echo "Doesn't exist: '$2'." exit 2;; ErrNoPath) echo "Path '$2' doesn't exist." exit 2;; ErrInput) #echo "Doesn't exist or can't read: '$2'." echo "Error reading: '$2'." exit 2;; ErrNoDir) #echo "Directory '$2' doesn't exist, (or isn't readable)." echo "Directory '$2' doesn't exist, isn't readable," echo " or isn't a directory." exit 2;; ErrNoFile) #echo "File '$2' doesn't exist (or isn't readable)." echo "File '$2' doesn't exist, isn't readable," echo " or isn't a file." exit 2;; ErrNoCDFS) echo "Can't determine filesystem type of '$2'" exit 2;; ErrNoCDBlock) echo "Can't establish block size or count ($3) of filesystem '$2'" exit 2;; ErrCreate) echo "Can't create directory or file '$2'." exit 3;; ErrCreateTmp) echo "Can't create temporary file/directory${2+ '$2'}." exit 3;; ErrLock) echo "Could not obtain lock '$2'. Giving up." exit 3;; ErrIllValue) echo "Bad value: '$2'." exit 4;; ErrTimeValue) echo "Error in numerical expression for TIMEOFFSET ($2). Use e.g. '14h+33m'. Available multipliers (units): d, h, m, s." exit 4;; ## Essential external parts are missing ErrNoProg) echo "Program '$2' is required but not found. Please install it, and/or" echo "ensure it's in the path for programs (\$PATH variable)." exit 8;; # more codes in here ---> # <--- more codes in here ## Argument errors ErrBadoption) echo "${0##*/}: Bad option '$2'." echo "$CALL_WITH_HELP_TEXT" exit 9;; ErrMissingParameter) echo "${0##*/}: A required parameter for option $2 is missing." echo "$CALL_WITH_HELP_TEXT" exit 9;; ErrNoFunction) echo "There is no function '$2'$3." exit 9;; ErrNoFunctionArgs) echo "Function $2() called without arguments." exit 18;; ErrInternal) echo "Internal error${2:+ ($2)}. Sorry, giving up." exit 18;; esac } exitwith_illegal() { echo "Internal error: exitwith() was called with unknown error code '$1'" exit 19 } # Default implementation. Re-define as needed. exitwith() { exitwith_funclib "$@" case "$1" in # more codes in here ---> # <--- more codes in here *) exitwith_illegal "$@";; esac } #### Command arg scanning #### # checkargmm() { test "$1" != "--" -a "$2" != "--" \ && test \( "${1:0:1}" != "-" -o "$1" == "-" \) \ -a \( "${2:0:1}" != "-" -o "$2" == "-" \) # uncomment after && above to allow arguments starting with "-" to options # (a single "-" by itself is always allowed) #true # uncomment this to allow "--" as argument to an option } # Check whether an argument for an option starts with "-", which could indicate # that the argument has been left out. # A "--" always indicates end-of-options, therefore any more required arguments # to options are missing. # A single "-" is always allowed as argument to an option. # To allow arguments starting with "-" to an option but to still treat "--" # as end-of-options, call with allowoptargminus set non-empty. # To disable these checks for an option, redefine the function to be # checkargmm() { true; } allowoptargminus= checkargmm() { test "$1" != "--" -a "$2" != "--" \ && { test -n "$allowoptargminus" \ || test \( "${1:0:1}" != "-" -o "$1" == "-" \) \ -a \( "${2:0:1}" != "-" -o "$2" == "-" \); } } # Check for required, and optional, arguments to options checkarg2() { # 1 required test $# -ge 2 && checkargmm "$2" && return exitwith ErrMissingParameter "$1" } checkarg3() { # 2 required test $# -ge 3 && checkargmm "$2" "$3" && return exitwith ErrMissingParameter "$1" } checkarg4() { # 3 required test $# -ge 4 && checkargmm "$2" "$3" && checkargmm "$4" && return exitwith ErrMissingParameter "$1" } checkarg2o() { # 0 required, 1 optional (returning true if present) test $# -ge 2 || return # no optional arg if $2 missing test "$2" == "${2#-}" # no optional arg if $2 starts with "-" } checkarg3o() { # 1 required, 1 optional (returning true if present) checkarg2 "$@" test $# -ge 3 || return # no optional arg if $3 missing test "$3" == "${3#-}" # $3 is no optional arg if starts with "-" } checkarg4o() { # 2 required, 1 optional (returning true if present) checkarg3 "$@" test $# -ge 4 || return # no optional arg if $4 missing test "$4" == "${4#-}" # $4 is no optional arg if starts with "-" } #### GNU programs #### # # Make sure we have GNU versions of some programs PROG available as gnuPROG. # If the GNU version is not available gnuPROG is undefined. # Use: if you need a GNU version in your script, use gnuPROG. On a non-GNU unix # system install the GNU programs as gnuPROG. # # Bash aliases are somewhat limited. In non-posix mode (#!/bin/bash, or # set +p posix) a command defined as alias will not be found. Even in posix # mode, the alias must be defined before any function is defined in which it is # used, this means find_GNU_prog() must be called before any functions are # defined. Trying to use functions instead. # find_GNU_prog1() { # THIS HAS to be changed somehow. Aliases in bash are useless for this. local p type -p gnutar >/dev/null || { p="`type -p tar`" && "$p" --version >/dev/null 2>&1 && alias gnutar="$p" } type -p gnucpio >/dev/null || { p="`type -p cpio`" && "$p" --version >/dev/null 2>&1 && alias gnucpio="$p" } type -p gnuawk >/dev/null || { p="`type -p gawk`" && "$p" --version >/dev/null 2>&1 \ && alias gnuawk="$p" \ || p="`type -p awk`" \ && test "`$p --version /dev/null`" != "" \ && alias gnuawk="$p" } # doesn't recognise /bin/ls as GNU (type -p ls returns nothing) type -p gnuls >/dev/null || { p="`type -p ls`" && "$p" --version >/dev/null 2>&1 && alias gnuls="$p" } type -p gnufind >/dev/null || { p="`type -p find`" && "$p" --version >/dev/null 2>&1 && alias gnufind="$p" } type -p gnuxargs >/dev/null || { p="`type -p xargs`" && "$p" --version >/dev/null 2>&1 \ && alias gnuxargs="$p" } type -p gnutouch >/dev/null || { p="`type -p touch`" && "$p" --version >/dev/null 2>&1 \ && alias gnutouch="$p" } type -p gnudiff >/dev/null || { p="`type -p diff`" && "$p" --version >/dev/null 2>&1 && alias gnudiff="$p" } } # Check if $1 is an external program. If yes, it is assumed to be the GNU # program. If not, check whether remaining arguments are a GNU program, and # define a function $1 to run the first program found. If none are found, $1 is # not an executable and the function returns false. find_GNU_prog_() { local p prog="$1"; shift type -p "$prog" >/dev/null || { while [ $# -gt 0 ]; do p="`type -p "$1"`" \ && "$p" --version /dev/null 2>&1 \ && { eval function "$prog" '{ "'$p'" "$@"; }' return 0 } shift done false } } # Define functions gnuXXX for a few common programs XXX, if gnuXXX does not # already exist, and if a GNU version of XXX can be found. find_GNU_prog() { local p find_GNU_prog_ gnutar gtar tar find_GNU_prog_ gnucpio gcpio cpio #find_GNU_prog_ gnuawk gawk awk #test fails for solaris awk find_GNU_prog_ gnuawk gawk || { # Solaris 2.7 "awk /dev/null 2>&1 \ && test "`$p --version /dev/null`" != "" \ && eval function gnuawk '{ "'$p'" "$@"; }' } # doesn't recognise /bin/ls as GNU (type -p ls returns nothing) find_GNU_prog_ gnuls ls /bin/ls find_GNU_prog_ gnufind gfind find find_GNU_prog_ gnuxargs gxargs xargs find_GNU_prog_ gnutouch gtouch touch find_GNU_prog_ gnudiff gdiff diff } # Find a better version of awk. # Accepted are gawk, nawk (in that order). If found, AWK() is defined. find_better_prog() { local p type -p AWK >/dev/null || { #p="`type -p gnuawk gawk nawk | head -1`" && alias AWK="$p" p="`type -p gnuawk gawk nawk | head -1`" && \ eval 'AWK() { '"$p"' "$@"; }' } } find_GNU_md5sum() { # Debian 3.1 ships a cheap version of md5sum (package dpkg) which has a few # problems with handling stdin and stdout. There is a decent GNU one in # /usr/bin/md5sum.textutils (package coreutils). # Debian problems: 'md5sum -' barfs (but 'md5sum' instead works), 'md5sum # -c file' can't check data from stdin, -v is required with -c to give more # verbose output. if [ -e /etc/debian_version -a -x /usr/bin/md5sum.textutils ]; then gnumd5sum() { md5sum.textutils "$@"; } else gnumd5sum() { md5sum "$@"; } fi } debug_find_GNU_prog() { echo "gnutar= `type gnutar 2>/dev/null`" echo "gnucpio= `type gnucpio 2>/dev/null`" echo "gnuawk= `type gnuawk 2>/dev/null`" echo "gnuls= `type gnuls 2>/dev/null`" echo "gnufind= `type gnufind 2>/dev/null`" echo "gnuxargs=`type gnuxargs 2>/dev/null`" echo "gnutouch=`type gnutouch 2>/dev/null`" echo "gnudiff= `type gnudiff 2>/dev/null`" echo "AWK= `type AWK 2>/dev/null`" } #### Logging #### # # Aid for screen logging of executed commands (remotely similar to "set echo" in # tcsh, or set -x in bash). # # Usage: # Log [-e] CMD [ARGS..] # DryLog [-e] CMD [ARGS...] # RunLog [-e] CMD [ARGS...] # DryNoLog [-e] CMD [ARGS...] # -e causes eval to be used with CMD. # Log logs and executes the command. # DryLog logs the command, but doesn't execute it if dryrun is set. # RunLog either logs the command if dryrun is set, or executes it. # DryNoLog logs and runs the command, unless dry-run is on (in which case it # does nothing). # $CMD_LOG_PREFIX may be set by caller, the string printed at start of each # logged line. # # If $CMD_LOG_TRACE is set, the shell trace function is used to log commands # to screen. This quotes arguments for display, logs to stderr, not stdout, # and also shows any eval commands used. # The downside is that it runs in a sub-shell, so if CMD modifies the # environment, that change doesn't come back to the caller! # Available from library version 63. # # set -x; "$@" # affects all following # (set -x; "$@") # works but starts another shell, and logs 2 lines if # # command is a shell function # set -x; "$@"; set +x # logs "set +x" # # Log + run given command. Log() { #echo "$@"; "$@" local _e= test "$1" = "-e" && { shift; _e=eval; } if ifset CMD_LOG_TRACE; then ( set -x; $_e "$@" ) else echo "$CMD_LOG_PREFIX""$@" $_e "$@" fi } # Log given command, and run it unless dry-run is on. DryLog() { local _e= # If $1!=-e, test fails and DryLog sometimes(!) exits 141 (bash 3, SuSE 9.2) #if [ "$1" = "-e" ]; then shift; _e=eval; fi test "$1" = "-e" && { shift; _e=eval; } if ifset CMD_LOG_TRACE; then # This shows a ": " in front of commands not run with dryrun. local _d= ifset dryrun && _d=: ( set -x; $_d $_e "$@" ) # This implementation avoids printing the ": " in front of commands # that are shown but not run with dryrun set. # if ifset dryrun; then # local _d # _d="$( ( set -x; : $_e "$@" ) 2>&1 )" # _d="${_d#${PS4:0:1}}" # Remove 1 prefix level # _d="${_d/: /}" # Remove ": " # echo 1>&2 "$_d" # else # ( set -x; $_e "$@" ) # fi else echo "$CMD_LOG_PREFIX""$@" ifset dryrun && return $_e "$@" fi } # Log and run given command, unless dry-run is on. DryNoLog() { if ifnset dryrun; then Log "$@" fi } # Either log (if dry-run is on) or run given command. RunLog() { local _e= test "$1" = "-e" && { shift; _e=eval; } if ifset dryrun; then if ifset CMD_LOG_TRACE; then ( set -x; : $_e "$@" ) # See DryLog for implementattion that doesn't print ": ". else echo "$CMD_LOG_PREFIX""$@" fi else $_e "$@" fi } # Until version 26: #Log_e() { Log -e "$@"; } #Nolog() { RunLog "$@"; } # # Show $0 and all cmd line arguments. show_args() { local n i=0 echo "line (#=$#): ""$@" while [ $i -le $# ]; do echo "$i: '${!i}'" i=$(($i + 1)) done } debug_show_args() { echo --------; show_args "$@"; echo -------- show_args "${@:3}"; echo -------- bla=("${@:3}"); show_args x "${bla[@]}"; echo -------- } #### Boolean flags #### # # Usage: # ifset VAR # ifnset VAR # Return with true if VAR is set / not set. # "Set" means != "" but not "0". # # return true if the variable is set; "set" means != "" but not "0" ifset() { if [ "$1" == "!" ]; then test ! \( -n "${!2}" -a ! 0${!2} -eq 0 \) else test -n "${!1}" -a ! 0${!1} -eq 0 fi } ifnset() { ifset ! $1 } debug_ifset() { local v1=0 v2=1 v3="" v4; unset v4 ifset v1 && echo v1; ifset v2 && echo v2; ifset v3 && echo v3 ifset v4 && echo v4 echo - ifset ! v1 && echo v1; ifset ! v2 && echo v2; ifset ! v3 && echo v3 ifset ! v4 && echo v4 echo - ifnset v1 && echo v1; ifnset v2 && echo v2; ifnset v3 && echo v3 ifnset v4 && echo v4 } #### Files' status #### # file is ((plain or symlink) and readable) test_readablefile() { test \( -f "$1" -o -L "$1" \) -a -r "$1" } checkforexist() { test -e "$1" || exitwith ErrNoExist "$1" } checkforexistreadable() { test -r "$1" || exitwith ErrInput "$1" } checkfordir() { test -d "$1" -a -r "$1" || exitwith ErrNoDir "$1" } checkforfile() { test -f "$1" -a -r "$1" || exitwith ErrNoFile "$1" } # Check if the given programs are in the path, exit with error if one is not. checkforprograms() { while [ $# -gt 0 ]; do checkforprogram "$1" || exitwith ErrNoProg "$1" shift done } # Check if the given program is in the path, set return status. checkforprogram() { type -p "$1" >/dev/null } #### Read user resources #### # Read user resources $1; if $2 = -home also try to read $HOME/$1 # Return msg in $rcmsg readresources() { # Include other resources, can be called form resource file to nest include_resource() { test_readablefile "$1" && source "$1" } if test_readablefile "$1"; then source "$1" rcmsg="Resources read from ${1/#$HOME/~}" elif test "$2" = "-home" && test_readablefile "$HOME/$1"; then source "$HOME/$1" rcmsg="Resources read from ~/$1" else rcmsg="Resources not found (${1/#$HOME/~})" fi #echo $rcmsg } #### Creating directories and symlinks, etc #### # Note: These functions have their own logging, and don't work well # as arguments to Log or DryLog. # Show the creation: _showcreate=1 # Only show creation, but do not create (equivalent to $dryrun): _drycreate=0 makedir() { while [ $# -gt 0 ]; do if [ ! -d "$1" ]; then # cond1 || cond2 && action :=: action = (cond1 OR cond2) ifset _showcreate || ifset _drycreate && echo "mkdir: $1" ifnset _drycreate && { mkdir -p "$1" || return } fi shift done } makelink() { if [ ! -L "$1" ]; then ifset _showcreate || ifset _drycreate && echo "link: $1 -> $2" ifnset _drycreate && ln -s "$2" "$1" fi } makelinkdel() { # as makelink, but deletes link first if it exists ifset _showcreate || ifset _drycreate && echo "link: $1 -> $2" ifnset _drycreate && { if [ -L "$1" ]; then rm "$1"; fi ln -s "$2" "$1" } } deldir() { while [ $# -gt 0 ]; do if [ -d "$1" ]; then ifset _showcreate || ifset _drycreate && echo "rmdir: $1" ifnset _drycreate && rmdir "$1" fi shift done } makemove() { ifset _showcreate || ifset _drycreate && echo "move: ""$@" ifnset _drycreate && mv "$@" # mv does not preserve permissions across filesystems! } #### Strings #### # Strip leading and trailing blanks from the string in the given variable. # Usage: # stripblanks variablename # Don't use illegal characters for variables in variablename! stripblanks() { local s eval s="\"\$$1\"" while [ "${s:0:1}" == " " ]; do s="${s# }"; done while [ "${s:$((${#s}-1)):1}" == " " ]; do s="${s% }"; done eval "$1"='"$s"' } #### Paths #### # replace $HOME-part of path with ~ hometilde() { echo "${1/#$HOME/~}" } # strip trailing "/" from path slashpath() { echo "${1%/}" } # strip all trailing "/" from path slashpaths() { local path="${1%/*}" while [ "${path%/}" != "$path" ]; do path="${path%/}"; done echo "$path" } # path components # tcsh -fc 'set f=path/base.ext; echo $f:r $f:h $f:t $f:r:t $f:e' # => path/base path base.ext base ext # Fixed bug with path_h() (was 1%%/*), 5Apr02. path_r() { echo "${1%.*}"; } # tcsh: root path_h() { echo "${1%/*}"; } # tcsh: head path_t() { echo "${1##*/}"; } # tcsh: tail path_b() { path_r "${1##*/}"; } # tcsh: tail root path_e() { echo "${1##*.}"; } # tcsh: ext path_d() { # directory part (similar to dirname) local dir="`slashpaths "$1"`" if [ "$dir" == "$1" ]; then echo "." elif [ "$dir" == "" ]; then echo "/" else echo "$dir" fi } # Add something like abs_dir() in md5 here debug_path() { echo "pa/h/base.ext root `path_r pa/h/base.ext`" echo "pa/h/base.ext head `path_h pa/h/base.ext`" echo "pa/h/ head `path_h pa/h/`" echo "pa/h/base.ext tail `path_t pa/h/base.ext`" echo "pa/h/base.ext r, t `path_b pa/h/base.ext`" echo "pa/h/base.ext ext `path_e pa/h/base.ext`" echo "pa/h/file dir `path_d pa/h/file`" echo "pa/h/ dir `path_d pa/h/`" echo "path/ dir `path_d path/`" echo "path dir `path_d path`" echo "file dir `path_d file`" echo "/path/file dir `path_d /path/file`" echo "/path////file dir `path_d /path////file`" echo "/path/ dir `path_d /path/` dirname /path/ = `dirname /path/`" echo "/path/// dir `path_d /path///`" echo "/file dir `path_d /file`" echo "////file dir `path_d ////file`" } #### Simple stack for shell options #### # Usage: push_shopt nocaseglob nullglob [..] # pop_shopt nocaseglob nullglob [..] shopt_stack="" push_shopt() { while [ $# -gt 0 ]; do v='u' shopt -q "$1" && v='s' shopt_stack="$shopt_stack$v" shift #echo "push: $shopt_stack" done } pop_shopt() { #echo "pop in: $shopt_stack; " "$@" if [ $# -gt ${#shopt_stack} ]; then echo 1>&2 "pop_shopt(): stack underflow with '${@:${#shopt_stack}+1}'" shopt_stack=""; false; return fi if [ $# -gt 1 ]; then pop_shopt "${@:2}" fi #echo "popping: $1" shopt -${shopt_stack:${#shopt_stack}-1} "$1" shopt_stack="${shopt_stack:0:${#shopt_stack}-1}" #echo "pop : $shopt_stack" } debug_shopt_stack() { shopt -p nocaseglob nullglob promptvars shift_verbose sourcepath extglob push_shopt nocaseglob nullglob promptvars shift_verbose sourcepath shopt -s nocaseglob nullglob shopt -u sourcepath shopt -p nocaseglob nullglob promptvars shift_verbose sourcepath extglob s="$shopt_stack" pop_shopt nocaseglob nullglob promptvars shift_verbose sourcepath extglob shopt_stack="$s" pop_shopt nocaseglob nullglob promptvars shift_verbose sourcepath shopt -p nocaseglob nullglob promptvars shift_verbose sourcepath extglob } #### CDROM, ISO9660, EXT2 (, UDF?) #### ## Obtain block count of iso9660 filesystem (now obsolete!) # Print the number of CD blocks (in 2048 bytes) of the given cdrom device or # iso file to stdout. # Usage: # iso_numblocks DEVICE_OR_ISOFILE [FUNCTION_TO_CALL] # iso_numblocks set FUNCTION_TO_CALL iso_numblocks() { if [ "$1" == "set" ]; then # set method by which to obtain block count case "$2" in isoinfo) _func_iso_getblocks="isoinfo_blocks";; *) _func_iso_getblocks="$2";; esac return fi ${2-$_func_iso_getblocks} "$1" } iso_numblocks set isoinfo # Using program isoinfo (shipped with cdrecord) to obtain block count. isoinfo_blocks() { get_cdimg_fsinfo_iso9660 "$1" test $? -ne 0 && exitwith ErrInput "$1" echo $CDIMG_BLOCKS } ## Obtain file system type, block size + block count of filesystem # Usage: # get_cdimg_fsinfo_iso9660 DEVICE_OR_ISOFILE # get_cdimg_fsinfo_ext2 DEVICE_OR_ISOFILE # get_cdimg_fsinfo_udf DEVICE_OR_ISOFILE -->NYI!! # Variables set: CDIMG_BLOCKS, CDIMG_BLOCKSIZE # # get_cdimg_fsinfo DEVICE_OR_ISOFILE # Variables set: CDIMG_FSTYPE, CDIMG_BLOCKS, CDIMG_BLOCKSIZE # # CDIMG_FSTYPE "unknown", "iso", or "ext2" # CDIMG_BLOCKS size of filesystem in blocks, or -1 # CDIMG_BLOCKSIZE blocksize of filesystem, or -1 # DEVICE_OR_ISOFILE can't be a pipe! # *) var=`exit 1` exits the subshell with code 1, not the parent # *) local var=`return 1` doesn't set $?, but # *) local var; var=`return 1` sets $? # Obtain block size + count of iso9660 filesystem, using isoinfo (shipped with # cdrecord). Using other programs to obtain this info (as with iso_numblocks) # is not yet supported. There was an old isosize which came with cdwrite, which # returned the size in blocks. There is now a new isosize in util-linux, which # has some fancier options. get_cdimg_fsinfo_iso9660() { CDIMG_BLOCKS=-1 CDIMG_BLOCKSIZE=-1 checkforexistreadable "$1" get_cdimg_fsinfo_iso9660_isoinfo "$1" } get_cdimg_fsinfo_iso9660_isoinfo() { local s checkforprograms isoinfo s=`isoinfo -d -i "$1"` # check return code test $? -ne 0 && return 1 eval `echo "$s" | parse_blocknum_fsinfo_iso9660` test $CDIMG_BLOCKS -ge 0 -a $CDIMG_BLOCKSIZE -ge 0 } parse_blocknum_fsinfo_iso9660() { awk ' BEGIN { bs=-1; blocks=-1; IGNORECASE=1 } $0 ~ /^.ogical block size/ { bs=$NF } $0 ~ /^.olume size is/ { blocks=$NF } END { printf "CDIMG_BLOCKS=%d CDIMG_BLOCKSIZE=%d", blocks, bs } ' } get_cdimg_fsinfo_iso9660_isosizeutillinux() { # unused? local s checkforprograms isosize s=`isosize -x "$1"` # check return code test $? -ne 0 && return 1 test "${s##* }" -ge 0 2>/dev/null && CDIMG_BLOCKSIZE="${s##* }" s="${s%,*}"; s="${s##* }" test "$s" -ge 0 2>/dev/null && CDIMG_BLOCKS="$s" } # Obtain block size + count of ext2 filesystem, using tune2fs get_cdimg_fsinfo_ext2() { local s CDIMG_BLOCKS=-1 CDIMG_BLOCKSIZE=-1 checkforexistreadable "$1" checkforprogram tune2fs || return s=`tune2fs -l "$1"` # check return code test $? -ne 0 && return 1 eval `echo "$s" | parse_blocknum_fsinfo_ext2` test $CDIMG_BLOCKS -ge 0 -a $CDIMG_BLOCKSIZE -ge 0 } parse_blocknum_fsinfo_ext2() { awk ' BEGIN { bs=-1; blocks=-1; IGNORECASE=1 } $0 ~ /^.lock size:/ { bs=$NF } $0 ~ /^.lock count:/ { blocks=$NF } END { printf "CDIMG_BLOCKS=%d CDIMG_BLOCKSIZE=%d", blocks, bs } ' } # Obtain block size + count of reiser filesystem # reiserfstune refuses to return parameters of a filesystem which is mounted, or # read-only. # reiserfsck returns very little info, but all we want to know. get_cdimg_fsinfo_reiser() { local s CDIMG_BLOCKS=-1 CDIMG_BLOCKSIZE=-1 checkforexistreadable "$1" checkforprogram reiserfsck || return s=`reiserfsck -p "$1"` # check return code - no don't!! # can give any number of errors, but still have the blocks + size #test $? -ne 0 && return 1 eval `echo "$s" | parse_blocknum_fsinfo_reiser` # the exit code here is crucial! } parse_blocknum_fsinfo_reiser() { while read -r; do case "$REPLY" in Blocks*) CDIMG_BLOCKS="${REPLY#*:}" CDIMG_BLOCKS="${CDIMG_BLOCKS# }" CDIMG_BLOCKS="${CDIMG_BLOCKS%%/*}" CDIMG_BLOCKSIZE="${REPLY%byte*}" CDIMG_BLOCKSIZE="${CDIMG_BLOCKSIZE% }" CDIMG_BLOCKSIZE="${CDIMG_BLOCKSIZE##* }" echo "CDIMG_BLOCKS=$CDIMG_BLOCKS" \ "CDIMG_BLOCKSIZE=$CDIMG_BLOCKSIZE" return 0 ;; esac done false } # Determine file system and obtain block size + count get_cdimg_fsinfo() { checkforprograms isoinfo tune2fs #isosize CDIMG_FSTYPE="unknown" CDIMG_BLOCKS=-1 CDIMG_BLOCKSIZE=-1 # Piping isoinfo directly into /dev/null causes an "inappropriate ioctl" # with isoinfo 2.01 on SuSE 9.2, on isofs which are too short. # Going through cat doesn't make it work with the vanilla isoinfo 2.01a31. #if isoinfo -d -i "$1" >/dev/null 2>&1; then #if isoinfo -d -i "$1" 2>&1 | cat >/dev/null; then if isoinfo -d -i "$1" 2>&1 | cat >/dev/null && test $PIPESTATUS -eq 0; then CDIMG_FSTYPE="iso" get_cdimg_fsinfo_iso9660 "$1" elif tune2fs -l "$1" >/dev/null 2>&1; then CDIMG_FSTYPE="ext2" get_cdimg_fsinfo_ext2 "$1" elif get_cdimg_fsinfo_reiser "$1"; then CDIMG_FSTYPE="reiser" else checkforexistreadable "$1" #return 1 fi } # Write file system info # Usage: # cdimg_store_fsinfo DEVICE_OR_ISOFILE [STORE_PREFIX] # Store info about the disk filesystem in DEVICE_OR_ISOFILE.$CDIMG_FSTYPE.info, # or STORE_PREFIX.$CDIMG_FSTYPE.info if given. If DEVICE_OR_ISOFILE/STORE_PREFIX # already end in .$CDIMG_FSTYPE, this is not appended again. The location of # the file in which the info is stored is returned in $CDIMG_STORE_FSINFO, # without the .info extension. cdimg_store_fsinfo() { CDIMG_STORE_FSINFO="${2:-$1}" get_cdimg_fsinfo "$1" CDIMG_STORE_FSINFO="${CDIMG_STORE_FSINFO%.$CDIMG_FSTYPE}.$CDIMG_FSTYPE" case "$CDIMG_FSTYPE" in iso) isoinfo -d -i "$1";; ext2) tune2fs -l "$1";; reiser) reiserfsck -p "$1" ( # reiserfstune barfs on read-only and mounted filesystems, # and kills itself with ABRT - not useful. trap true ABRT reiserfstune 2>/dev/null 1>&2 "$1" ) 2>/dev/null && { echo "---------" reiserfstune 2>/dev/null "$1" } echo "---------" file - < "$1" | sed 's/^[^ ]* //' ;; unknown) if checkforprogram listall; then for o in "" -L -a "-L -a"; do listall $o "$1"; done else for o in -ln -lLn -l -lL; do ls $o "$1"; done fi ;; esac >"$CDIMG_STORE_FSINFO.info" } ## Read disk image to stdout # # Use cdimg_read, cdimg_read_blocks in preference to iso_blockread, # iso_read_blocks because they handle multiple filesystem types. # iso_blockread_numblocks is filesystem-independent, despite its name. # Read the whole disk image to stdout # Usage: # cdimg_read DEVICE_OR_ISOFILE # Obtains the filesystem type and size, then returns the filesystem on stdout. # DEVICE_OR_ISOFILE can't be a pipe! # A block size or blocks <= 0 is an error (the fs type couldn't be detected), # but if it's a plain file, the contents of the file are returned. cdimg_read() { local CDIMG_FSTYPE CDIMG_BLOCKS CDIMG_BLOCKSIZE get_cdimg_fsinfo "$1" || exitwith ErrNoCDFS "$1" if [ $CDIMG_BLOCKS -le 0 -o $CDIMG_BLOCKSIZE -le 0 ]; then if [ -f "$1" ]; then ( exitwith ErrNoCDBlock "$1" "$CDIMG_BLOCKSIZE, $CDIMG_BLOCKS" ) cat "$1" else exitwith ErrNoCDBlock "$1" "$CDIMG_BLOCKSIZE, $CDIMG_BLOCKS" fi else cdimg_read_blocks "$1" fi } # Read a number of blocks to stdout # Usage: # cdimg_read_blocks DEVICE_OR_ISOFILE [NUM_BLOCKS] # Reads $CDIMG_BLOCKS (or NUM_BLOCKS, if given) blocks of size $CDIMG_BLOCKSIZE # to stdout. # DEVICE_OR_ISOFILE may be a pipe, but must be given as file (e.g. /dev/fd/0). # If $SCRIPTLIBVK (this is part of the user interface) contains the string # DD_IDIRECT, the option "direct" will be used with dd when reading and ISO # image from a block device, if the dd supports the option. cdimg_read_blocks() { local flag="iflag=direct" # Check whether dd understands iflag= (can't use iflag=direct on /dev/null!) dd count=0 if=/dev/null of=/dev/null iflag=nonblock 2>/dev/null || flag= # Apply iflag=direct only to block special files; on plain files it also # bypasses the block buffer, making an on-hard-disk CD/DVD image painfully # slow to read. test -b "$1" || flag= # Even on block devices, iflag=direct significantly reduces the throughput. # For any source (block device or plain file), iflag=direct increases # runtime but decreases the overall load on the system. # Therefore, go direct only when SCRIPTLIBVK contains DD_IDIRECT. case "$SCRIPTLIBVK" in *DD_IDIRECT*) : ;; *) flag= ;; esac # Read disk image: dd bs="$CDIMG_BLOCKSIZE" count="${CDIMG_BLOCKS:-$2}" if="$1" $flag } # Return all blocks of the given cdrom device or iso file to stdout. # Usage: # iso_blockread DEVICE_OR_ISOFILE # DEVICE_OR_ISOFILE can't be a pipe! # If $1 is a plain file, we could run cat instead - downside of that is that we # return the whole file, not the size of the iso9660 of it (the file may be # longer or shorter). iso_blockread() { #iso_read_blocks "$1" "`iso_numblocks "$1"`" local n ret n="`iso_numblocks "$1"`" ret=$? test $ret -ne 0 && exit $ret iso_read_blocks "$1" "$n" } # Return the given number of blocks of the given cdrom device or iso file to # stdout. # Usage: # iso_read_blocks DEVICE_OR_ISOFILE NUMBLOCKS # Uses dd to read, because the Linux kernel drivers for cdroms do not recognise # EOF for something like "cat /dev/cdrom" correctly (some kernels crash(!), some # return too much data, others too little). # 2.4.10-20 kernels can give I/O errors even then, and even if the disk was # written with cdrecord -pad, especially when DMA is on. iso_read_blocks() { dd bs=2048 if="$1" count="$2" # block size does not affect speed: #> sh -c 'time dd if=/dev/hdc of=/dev/null bs=2048 count=25000' #25000+0 records in #25000+0 records out #real 1m3.684s #user 0m0.060s #sys 0m1.000s #> sh -c 'time dd if=/dev/hdc of=/dev/null bs=20480 count=2500' #2500+0 records in #2500+0 records out #real 1m3.627s #user 0m0.010s #sys 0m0.610s #> sh -c 'time dd if=/dev/hdc of=/dev/null bs=10k count=5000' #real 1m2.415s # SuSE 8.2, 2.4.20, 19Aug03: #/usr/bin/time dd bs=512 count=1330240 < /dev/cdrom > /dev/null #1.52user 45.11system 3:39.38elapsed 21%CPU (0avgtext+0avgdata 0maxresident)k #/usr/bin/time dd bs=20k count=33256 < /dev/cdrom > /dev/null #0.06user 43.03system 3:39.95elapsed 19%CPU (0avgtext+0avgdata 0maxresident)k } # Read given cdrom device or iso file with given blocksize, saving to given path # prefix with block number appended. Useful with read errors on parts of the CD. # Usage: # iso_blockread_numblocks DEVICE_OR_ISOFILE BLOCKS_AT_A_TIME \ # START_BLOCK END_BLOCK PATHPREFIX # (START_BLOCK and PATHPREFIX may be empty.) iso_blockread_numblocks() { local iso="$1" chunk=$2 block=${3:-0} size=$4 prefix="${5:-cdblocks}" test -z "$size" && size="`iso_numblocks "$iso"`" test $chunk = 0 && return while [ $block -lt $size ]; do test $(($block + $chunk)) -gt $size && chunk=$(($size - $block)) block0="`iso_printf06i $block`" dd if="$iso" bs=2k count="$chunk" skip="$block" of="$prefix$block0" \ >/dev/null 2>&1 \ || echo "Error: blocks starting $block (chunk=$chunk)" 1>&2 block=$(($block + $chunk)) done } iso_printf06i() { if [ $1 -le 9 ]; then echo "00000$1" elif [ $1 -le 99 ]; then echo "0000$1" elif [ $1 -le 999 ]; then echo "000$1" elif [ $1 -le 9999 ]; then echo "00$1" elif [ $1 -le 99999 ]; then echo "0$1" elif [ $1 -le 999999 ]; then echo "$1" fi } ## MD5 checksums of cd/dvd disk images # # The cdimg_* functions will handle multiple filesystem types. # Read given cdrom device or iso file and write corresponding md5 checksum to # stdout. The filename stored with the checksum is always "-". # Usage: # iso_create_md5 DEVICE_OR_ISOFILE # cdimg_create_md5 DEVICE_OR_ISOFILE iso_create_md5() { checkforprograms md5sum checkforexistreadable "$1" iso_blockread "$1" | md5sum } cdimg_create_md5() { checkforprograms md5sum checkforexistreadable "$1" cdimg_read "$1" | md5sum test ${PIPESTATUS[0]} -eq 0 } # Check the given cdrom device or iso file with the given md5 checksum. # The checksum can be read from stdin if using "-" as filename. # Only the MD5 sum part of the first line of the checksum file is used, the rest # of the checksum file is ignored; this makes it possible to use MD5 sum files # which have any filename stored in them. # A temp file is required for storing the expected checksum while checking. # The temp file name can be given with optional $3. # Usage: # cdimg_check_md5 DEVICE_OR_ISOFILE MD5_FILE [TMPFILE_FOR_MD5] # deprecated: # iso_check_md5 DEVICE_OR_ISOFILE MD5_FILE [TMPFILE_FOR_MD5] iso_check_md5() { cdimg_blockread_cmd=iso_blockread cdimg_check_md5_ "$@" } cdimg_check_md5() { cdimg_blockread_cmd=cdimg_read cdimg_check_md5_ "$@" } cdimg_check_md5_() { checkforprograms md5sum checkforexistreadable "$1" local tmpfile stat sum rest get_tempfile "" "iso_check_md5_" tmpfile="$TEMPFILE" trap 'rm "$tmpfile" 2>/dev/null' EXIT if [ "$2" == "-" ]; then read -r sum rest else read -r sum rest < "$2" fi echo "$sum -" >"$tmpfile" || exitwith ErrCreateTmp "$tmpfile" find_GNU_md5sum $cdimg_blockread_cmd "$1" | gnumd5sum -c "$tmpfile" stat=$? rm "$tmpfile" trap - EXIT return $stat } ## Various cd/dvd functions # Append a number of nullblocks to the file given. # The Linux kernels 2.4.x are very buggy with reading the ends of CDs or DVDs. # 2.4.20 more often than not produces an I/O error before it has even come # close to reading the end of the isofs. cdrecord -pad is nowhere near # sufficient any more. # Workaround: append more nullblocks to ISO file before writing. # Usage: # cdimg_append_nullblocks FILE [NUMBLOCKS] # FILE may be - for stdout. Block size is 2048. # NUMBLOCKS defaults to 1000, i.e. 2MB cdimg_append_nullblocks() { local n="${2:-1000}" if [ "$1" == "-" ]; then cdimg_append_nullblocks_ $n else cdimg_append_nullblocks_ $n >>"$1" fi } cdimg_append_nullblocks_() { dd bs=2k if=/dev/zero count="$1" } # Establish the device of a CD mounted at DIR. DIR can be a symlink. # Usage: # get_cd_device_from_mountpoint DIR # Out: CD_MOUNTPOINT the actual mount point of the CD # CD_DEVICE the corresponding device # Changelog: v 47: Make sure only one path is returned if the device is mounted # more than once. get_cd_device_from_mountpoint() { CD_MOUNTPOINT="" CD_DEVICE="" test -d "$1/." || return pushd "$1/." >/dev/null || return # can't cd to mountpoint CD_MOUNTPOINT="`pwd -P`" popd >/dev/null find_better_prog CD_DEVICE="$(mount | AWK '$3 ~ "^'"$CD_MOUNTPOINT"'$" {print $1; exit}')" test -n "$CD_DEVICE" } # Find out whether it's a CD or DVD (or maybe a disk directory, in future) # from its mount point or its filesystem, by looaking at the filesystem size. # Usage: # obtain_cd_type MOUNT_POINT # obtain_cd_type -img ISO_file_or_device # Return on stdout: "CD", "DVD", or "" obtain_cd_type() { printtype() { # The size of a dvd should be more than the size of a cd. # Size of 700MB cd is 720000 1k blocks, give a bit for overburn. # Both should also have 0 blocks free (that only on iso9660). test -z "$1" && return test "$1" -gt 750000 && echo "DVD" || echo "CD" } case "$1" in -img) size="`iso_numblocks "$2"`" size=$(( $size * 2 )) # ISO blocks are 2k printtype "$size" ;; *) local fsdev fssize fsused fsfree rest # df -P puts everything on 1 line (we should check for GNU df) df -Pk "$1/." | while read fsdev fssize fsused fsfree rest; do #echo "$fsdev, $fssize, $fsused, $fsfree" case "$fsdev" in /*) # inside subshell, can't return variables here printtype "$fssize" break;; *) continue;; esac done ;; esac } # Functions to load and eject the CDROM tray # Possible argument: the device to operate on (if there's more than 1 drive) # PLEASE do not edit this file, redefine these functions after loading this. cdrom_tray_open() { checkforprogram eject && eject "${@:1}" } cdrom_tray_close() { checkforprogram eject && eject -t "${@:1}" # This used to be reliable, but with SUSE 10.0, immediate access to the # just-loaded disk often results in "no disk found". sleep 2 } #### Etc #### # Filter; gunzips hex code to stdout; hex code can be inlined # Create hex code with one of: # gzip -9 | od -w39 -A o -t x1 | sed -e 's/[0-7]*//' -e 's/ //g' -e '/^$/d' # gzip -9 | bin2hex | cut -c -50 | sed -e 's, ,,g' # or simply call createcontainer with desired hex-width as optional argument. # Call like this: # writecontainer < # EOF createcontainer() { gzip -9 | od -w${1-39} -A o -t x1 \ | sed -e 's/[0-7]*//' -e 's/ //g' -e '/^$/d' } writecontainer() { #sed -e 's,..,\\\\x&,g' | while read; do # echo 'echo -ne "'"$REPLY"\" #done | $BASH -s | gunzip sed -e 's,..,\\x&,g' -e 's,^.*$,echo -ne "&",' | $BASH -s | gunzip } # display date + time, GMT + local # Use Date() { Date1; } to default to Date1 for Date. Date() { Date2; } Date2() { date -u +'%a %d %b %Y %T %Z' date +'%a %d %b %Y %T %Z' } Date1() { echo "`date -u +'%a %d %b %Y %T %Z'`" " `date +'%a %d %b %Y %T %Z'`" } Date1b() { echo "`date +'%a %d %b %Y %T %Z'` (`date -u +'%a %d %b %Y %T %Z'`)" } # Display time at start and finish. # show_start_time # save=$_save_seconds # ... # show_finish_time $save TEXT # TEXT is optional and is displayed before the seconds. # Or, if certain that there is no nested calling: # show_start_time # ... # show_finish_time # Never write $SECONDS!!! show_start_time() { Date _save_seconds=$SECONDS } show_finish_time() { local sec=$SECONDS sec=$(($sec - ${1:-$_save_seconds})) Date echo "${2:-Total execution time:} `sec2h $sec`" } # Convert seconds to HH:MM:SS. sec2h() { printf "%2i:%02i:%02i" $(($1 / 3600)) $((($1 / 60) % 60)) $(($1 % 60)) } # Convert seconds to MM:SS. sec2m() { printf "%02i:%02i" $((($1 / 60) % 60)) $(($1 % 60)) } # Interpret and check the given time stamp expression. # Expand days, hours and minutes units. Ignore seconds. # Example: "3d + 10h - 8m - 32" = 294688 (seconds). # $1: time stamp expression # Return: $timemove: time stamp expression in seconds. expandtimeunits() { local stamp="$1" old= newtime # Expand the units. while [ "$stamp" != "$old" ]; do old="$stamp" stamp="${stamp/d/ *24*3600}" stamp="${stamp/h/ *3600}" stamp="${stamp/m/ *60}" stamp="${stamp/s/ }" done # Evaluate expression, show error if necessary. stampexpr() { echo $(($stamp)) # Can't suppress error to stderr without function. } newtime="`stampexpr 2>/dev/null`" test $? -eq 0 || exitwith ErrTimeValue "$stamp" timemove="$newtime" } # ringbell [N] [TEXT] # Ring terminal bell N times (default 1), # displaying TEXT (default "Ring bell:\n"). ringbell() { local rings="${1:-1}" bell="\007${2-Ring bell:\n}" while [ $rings -gt 0 ]; do echo -e -n "$bell" test $rings -gt 1 && sleep 1 rings=$(($rings - 1)) done } # Check given number (e.g. previous exit status) and exit with that number if # not 0 CheckStatus() { test "$1" -ne 0 && exit "$1" } # cd to dir, if given; then print current working dir in any case Pwd() { test -n "$1" && { cd "$1" || exitwith ErrNoDir "$1" } echo "Current dir is now:" `pwd` } # printf %02d (but not using printf) digit02() { test ${#1} = 1 && echo "0$1" || echo "$1" } # Character constants HT=" " NL=" " CR=" " eval `echo -e -n BEL="\007" BS="\010" NP="\014" ESC="\033" DEL="\0177"` # Assemble words into an alternate regular expression assemble_alternate() { local re="$1" mid="$2" end="$3" first=1 shift 3 while [ $# -gt 0 ]; do test -n "$first" && { re="$re$1"; first=; } || re="$re$mid$1" shift done REGEX="$re$end" } assemble_regex() { assemble_alternate "(" "|" ")" "$@" } # Create temporary file safely. # In: $1: Some string, which becomes part of the temp file name. Optional. # $2: A category string, which becomes part of the temp file name. Optional. # $3: Extension of the temp file name. Optional. # RETURN: The name of the created file in $TEMPFILE # Exits program if temp file couldn't be created. # Usage: # get_tempfile myprogram; tmpfile="$TEMPFILE" get_tempfile() { local str="${1:-funktions_vk_bash}" cat="$2" ext="$3" local file tmp TEMPFILE= if checkforprogram mktemp; then tmp="${TMPDIR:-/tmp}/$str.$$.${cat}XXXXXXXXXXXXXX$ext" file="`mktemp "$tmp"`" || exitwith ErrCreateTmp "$tmp" else tmp="$HOME/.funclibtmp" test -d "$tmp" || mkdir "$tmp" || exitwith ErrCreate "$tmp" file="$tmp/$str.$$.$cat`date +%H%M%S-%N`$ext" touch "$file" || exitwith ErrCreateTmp "$file" fi TEMPFILE="$file" } # Create temporary directory safely. # In: $1: Some string, which becomes part of the temp dir name. Optional. # $2: A category string, which becomes part of the temp dir name. # Optional, default "d_". # $3: Extension of the temp dir name. Optional. # RETURN: The name of the created directory in $TEMPDIR # Exits program if temp dir couldn't be created. get_tempdir() { local str="${1:-funktions_vk_bash}" cat="${2:-d_}" ext="$3" local dir tmp TEMPDIR= checkforprograms mktemp tmp="${TMPDIR:-/tmp}/$str.$$.${cat}XXXXXXXXXXXXXX$ext" dir="`mktemp -d "$tmp"`" || exitwith ErrCreateTmp "$tmp" TEMPDIR="$dir" } # 2-dimensional array support # In: $1: name of array variable (no dollar sign!) # $2: number of columns # $3: column number to extract the elements of (default 0) # This is zero-based! # Out: All indices in array $1 which are in column $3. # Usage: # col_indices arrayvar columns column # Example: Extract peoples' names: # people=( Mike mike@somewhere.net blue Jane janem@inter.net red ) # for i in `col_indices people 3`; do echo ${people[$i]}; done # col_indices() { local maxidx columns="$2" col="${3-0}" eval "maxidx=\${#$1[@]}" let maxidx-- seq $col $columns $maxidx } # Pipe stdin through a pager, if output is to a terminal, otherwise through cat. # Default pager is less, or cat with -nodefault. mypager() { if [ -t 1 ]; then if [ "$1" = "-nodefault" ]; then ${PAGER:-cat} else ${PAGER:-less} fi else cat fi } #### Debug the lot #### debug_functions_vk() { # some functions don't have a debug function find_GNU_prog find_better_prog readresources FILE debug_find_GNU_prog debug_ifset debug_show_args -d 2 "3 3" 4 "" 6 "'" debug_path debug_shopt_stack }