# #!/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) 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 # chr0(){ echo @;};chr1(){ echo .;} VERSION_FUNCLIB_NUM="59" # this integer will be incremented with each change VERSION_FUNCLIB="VK 1.9.22, 07 Jan 2007" AUTHOR_FUNCLIB="Volker Kuhlmann " COPYRIGHT_FUNCLIB="Copyright (C) 1999-2007" #### 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.dnsalias.net/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 "" } Version() { show_version; exitwith ErrOK; } # obsolete, for compatibility only: version() { show_version; } show_usage__2() { show_usage show_usage_short } show_usage_short() { test -n "$CALL_WITH_HELP_TEXT" && echo "$CALL_WITH_HELP_TEXT " } Usage() { test "$1" && exitwith ErrUsage show_usage__2 show_usage__2 exitwith ErrOK } Help() { test "$1" && exitwith ErrHelp show_help show_help 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;; ErrUsage|ErrHelp|ErrUnspecified) # Output generated by function/program $2, if given. test -n "$2" && "${@:2}" 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${2+ '$2'}." exit 3;; ErrLock) echo "Could not obtain lock '$2'. Giving up." exit 3;; ErrIllValue) echo "Bad value: '$2'." 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;; ErrNoFunctionArgs) echo "Function $1() called without arguments." exit 18;; ErrInternal) echo "Internal error. Sorry, giving up." exit 18;; esac } exitwith_illegal() { echo "Internal error: exitwith() was called with unknown error code '$1'" exit 19 } 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 a program, 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" } 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...] # -e causes eval to be used with CMD. # Log logs and executes the command. # DryLog will only log but not execute the command if dryrun is set. # RunLog will either log the command if dryrun is set, or execute it. # $CMD_LOG_PREFIX may be set by caller, the string printed at start of each # logged line. # # 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; } echo "$CMD_LOG_PREFIX""$@" $_e "$@" } # Log given command, and run it unless dry-run is on DryLog() { local _e= test "$1" = "-e" && { shift; _e=eval; } echo "$CMD_LOG_PREFIX""$@" ifset dryrun && return $_e "$@" } # Either log (if dry-run is on) or run given command RunLog() { if ifset dryrun; then echo "$CMD_LOG_PREFIX""$@" else local _e= test "$1" = "-e" && { shift; _e=eval; } $_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 #### # 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" 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="${3-$HOME/.functions_vk.bash.iso_check_md5.$$}" stat local sum rest if [ "$2" == "-" ]; then read -r sum rest else read -r sum rest < "$2" fi trap 'rm "$tmpfile" 2>/dev/null' EXIT 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'`" } # 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)) } # 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 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"` # 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 temp files # In: $1: Some string, becomes part of temp file name # 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 file TEMPFILE= if checkforprogram mktemp; then file="${TMPDIR:-/tmp}/.${1:-funktions_vk.bash}.$$.XXXXXX" file="`mktemp "$file"`" || exitwith ErrCreateTmp "$file" else file="$HOME/.funclibtmp" test -d "$file" || mkdir "$file" || exitwith ErrCreate "$file" file="$file/${1:-funktions_vk.bash}.$$.`date +%H%M%S-%N`" touch "$file" || exitwith ErrCreate "$file" fi TEMPFILE="$file" } #### 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 }