# Copyright (c) 2007, Roberto Aguilar # All rights reserved. # # Redistribution and use of this software in source and binary forms, with or # without modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name, "Roberto Aguilar", nor the names of its contributors may # be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # This script provides shell convenience functions. # # To add this to your environment, export the variable ST_SH to the # path to this script and source the file: # # export ST_SH=/path/to/shelltools.sh && . ${ST_SH} # # This script uses the shell return status convention, which means # that a 0 returned suggests a successful operation and a non-zero # status is not successful. # # Run st_help to see a listing of all the help texts in this file # # Changelog: # 2006-07-06 # added st_rm function to remove files if the exist # # 2006-07-28 # added st_linefy - changes space-delimted list to newline-delimited list # added st_status - tries to run a command and returns the status # added st_echo_up - echoes the ../ the requested number of times # added st_up - cds to the directory that is x above the current dir # added st_pup - pushds to the directory that is x above the current dir ECHO="/bin/echo" if [ -z ${ST_SH} ]; then echo "The shelltools script needs to know where it resides. " echo "Therefore, it's important that you set the ST_SH env variable " echo "in your .bashrc file: " echo echo "export ST_SH=/path/to/shelltools.sh" return -1 fi ST_APPDIR_PATHS=(\ 'PATH:bin' 'MANPATH:share/man' 'MANPATH:man' 'LD_LIBRARY_PATH:lib') # st_appdir_set HELP: # st_appdir_set # inspect the given root directory and prepend paths of typical services # to shell's environment, e.g. PATH, MANPATH, PYTHONPATH, etc. # ENDHELP function st_appdir_set { [ -z $1 ] && echo "no directory given" 1>&2 && return -1 root=$1 [ ! -e $root ] && echo "given directory does not exist" 1>&2 && return -1 # convert to a full path in case it's relative pushd ${root} > /dev/null root=$(pwd) popd > /dev/null for i in ${ST_APPDIR_PATHS[@]}; do split=($(echo $i | sed 's,:, ,')) t_path=${root}/${split[1]} existing=$(env | grep -w ${split[0]} | sed 's,.*=\(.*\)$,\1,') if [ -e ${t_path} ]; then if [ "${existing}" != "" ]; then eval "export ${split[0]}=$(st_path ${t_path}:${existing})" else eval "export ${split[0]}=${t_path}" fi; fi; done; # python is special in that its directory is dependent on the app's version. # also, it's good to see if python is in the root's path to use that version # instead of the one before path was set python_version=$(python -V 2>&1 | sed 's,.* \(.\..\).*,\1,') python_path=${root}/lib/python${python_version}/site-packages if [ -e ${python_path} ]; then export PYTHONPATH=$(st_path ${python_path}:${PYTHONPATH}) fi; } # st_appdir_unset HELP: # st_appdir_unset # inspect the given root directory and prepend paths of typical services # to shell's environment, e.g. PATH, MANPATH, PYTHONPATH, etc. # ENDHELP function st_appdir_unset { [ -z $1 ] && echo "no directory given" 1>&2 && return -1 root=$1 [ ! -e $root ] && echo "given directory does not exist" 1>&2 && return -1 # convert to a full path in case it's relative pushd ${root} > /dev/null root=$(pwd) popd > /dev/null # python is special in that its directory is dependent on the app's version. # also, it's good to see if python is in the root's path to use that version # instead of the one before path was set python_version=$(python -V 2>&1 | sed 's,.* \(.\..\).*,\1,') python_path=${root}/lib/python${python_version}/site-packages if [ -e ${python_path} ]; then export PYTHONPATH=$(st_path_pop ${python_path} ${PYTHONPATH}) fi; for i in ${ST_APPDIR_PATHS[@]}; do split=($(echo $i | sed 's,:, ,')) t_path=${root}/${split[1]} existing=$(env | grep -w ${split[0]} | sed 's,.*=\(.*\)$,\1,') if [ -e ${t_path} ]; then eval "export ${split[0]}=$(st_path_pop ${t_path} ${existing})" fi; done; } # st_common HELP: # st_common # echo the common component of the given strings # ENDHELP function st_common { i=0 min_len=${#1} len2=${#2} common="" if [ ${len2} -lt ${min_len} ]; then min_len=${len2}; fi; while [ $i -lt ${min_len} ]; do if [ "${1:$i:1}" != "${2:$i:1}" ]; then break; fi; common="${common}${1:$i:1}" let i="$i + 1" done; echo ${common} } # st_delimit HELP: # st_delimit # take the given string and replace the current delimiter with the new one # ENDHELP function st_delimit { [ $# != 3 ] && st_help st_delimit && return -1; if [ "$1" == "/" ]; then echo $3 | sed -e "s,$1,$2,g" else echo $3 | sed -e "s/$1/$2/g" fi; } # st_echo_up HELP: # st_echo_up # echo out the directory representing the number of directories to go up # ENDHELP function st_echo_up { if [ -z $1 ]; then num=1; else num=$1; fi; dir="" # check to see if the system has either seq or jot if [ $(st_status which seq) -eq 0 ]; then sequence=$(seq $num) else if [ $(st_status which jot) -eq 0 ]; then sequence=$(jot $num) fi; fi; [ "$sequence" == "" ] && \ st_error "Unable to get number sequence" && \ return -1; for i in $sequence; do dir="../$dir"; done; echo $dir; } # st_envset HELP: # st_envset # go through the list of variables to set and export the values # ENDHELP function st_envset { while [ 1 ]; do read input if [ "${input}" == "" ]; then break; fi; parsed=($(echo ${input} | sed 's,=, ,')) t=$(env | grep "^${parsed[0]}" | sed 's,.*=\(.*\),\1,') echo "export ${parsed[0]}=$(st_path ${parsed[1]}:$t)" done; } # st_envunset HELP: # st_envunset # go through the list of variables and unset the given paths # ENDHELP function st_envunset { while [ 1 ]; do read input if [ "${input}" == "" ]; then break; fi; parsed=($(echo ${input} | sed 's,=, ,')) t=$(env | grep "^${parsed[0]}" | sed 's,.*=\(.*\),\1,') echo "export ${parsed[0]}=$(st_path_pop ${parsed[1]} $t)" done; } # st_error HELP: # st_error # Write the given message to stderr # ENDHELP function st_error { echo $1 1>&2 } # st_is_sourced HELP: # st_is_sourced # Simply returns 0 to report shelltools has been sourced # ENDHELP function st_is_sourced { return 0; } # st_in_list HELP: # st_in_list [list] # Check if the given item already exists in the list. If there is # something in the list, a 0 will be returned, otherwise 1 (shell # return status convention). # ENDHELP function st_in_list { found=1; item=$1; shift; until [ -z "$1" ]; do if [ "$1" == "${item}" ]; then found=0 break; fi; shift done echo ${found} return ${found} } # st_help HELP: # st_help [function] [filename] [cmd] # Print out the help documentation for the given function to the screen. # If no function was given, all functions are printed # The filename is the path to the script to extract help from. The # default is this shell script (i.e. $ST_SH). The command is what # is displayed in the help output, default is st_help # ENDHELP function st_help { if [ ${#} -eq 0 ]; then function="all" elif [ ${#} -eq 1 ]; then function=${1} elif [ ${#} -eq 2 ]; then file=${1} function=${2} elif [ ${#} -eq 3 ]; then file=${2} function=${1} cmd=${3} fi; # make sure a file was given if [ "${file}" == "" ]; then file=${ST_SH} fi; exec cat ${file} | _st_print_help ${function} ${cmd} } # st_linefy HELP: # st_linefy # Takes a space delimited list and makes a carriage-return delimited list # # For example, you can use this in conjunction with st_delimit to perform an # action on every directory in $PATH: # # for i in $(st_linefy $(st_delimit : " " $PATH)); do echo "dir: $i"; done; # # Input to this function can also be piped in, e.g.: echo "a b c" | st_linefy # ENDHELP function st_linefy { if [ -z "$1" ]; then input="" read -t 1 input if [ "${input}" != "" ]; then st_linefy ${input} else st_help st_linefy fi; else while [ ! -z "$1" ]; do echo $1; shift; done; fi; } # st_path HELP: # st_path # Clean out duplicate entries in the given colon-delimeted path and # echo the result. # ENDHELP function st_path { path=$1 shift new_path="" keep_dirs=() path_dirs=(`echo $path | sed 's/:/ /g'`) for i in "${path_dirs[@]}"; do if [ -n $1 ] && [ "${i}" == "$1" ]; then continue; fi in_list=`st_in_list ${i} ${keep_dirs[@]}` if [ ${in_list} -eq 1 ]; then keep_dirs=( ${keep_dirs[@]} ${i} ); if [ "${new_path}" == "" ]; then colon="" else colon=":" fi; new_path=${new_path}${colon}${i} fi; done; echo ${new_path}; } # st_path_remove HELP: # st_path_remove # Remove out the given path from the colon-delimited list. # ENDHELP function st_path_remove { path=$1 shift keep_dirs=() path_dirs=($(echo $1 | sed 's/:/ /g')) for i in "${path_dirs[@]}"; do if [ "${path}" != "${i}" ]; then keep_dirs=(${keep_dirs[@]} ${i}) fi; done; echo ${keep_dirs[@]} | sed 's/ /:/g' } # st_path_pop HELP: # st_path_pop # If the given path is found in the colon-delimited list, remove it # ENDHELP function st_path_pop { [ -z $2 ] && \ echo "please specify the path to pop and the path list" 1>&2 && \ return -1 desired_path=$1 shift path_split=$(st_delimit : " " $1) st_delimit " " : "$(st_pop ${desired_path} ${path_split})" } # st_pop HELP: # st_pop [list] # Check to see if the first argument of the function exists in the # remaining arguments. If it does, remove it from the list and return # the modified list. Otherwise, return the list unchanged. If # something is found, this function will return 0, otherwise it will # return 1. # ENDHELP function st_pop { what=$1 shift where="${*}" echo "${where}" | grep -q -- "${what}" status=$? if [ ${status} -eq 0 ]; then SED_EXPR="s#${what}##g" echo "${where}" | sed ${SED_EXPR} else echo ${where} fi; return ${status} } # A helper function that prints out the help information function _st_print_help { in_help=0 if [ "${1}" == "all" ]; then function=""; else function=${1} fi; if [ "${2}x" == "x" ]; then help_cmd="st_help" else help_cmd="${2}" fi; if [ "${function}" == "" ]; then echo "Here is a list of all the available development functions." echo "If you would only like to see a specific function, pass its " echo "name as an argument, e.g. ${help_cmd} ${help_cmd}." fi; while [ 1 ]; do read input if [ $? -ne 0 ]; then break; fi; # not in help block if [ $in_help -eq 0 ]; then echo ${input} | grep -q "^\#.*${function} HELP:" if [ $? -eq 0 ]; then in_help=1; echo; continue; fi; elif [ $in_help -eq 1 ]; then echo ${input} | grep -q "^\#.*ENDHELP" if [ $? -eq 0 ]; then in_help=0; # if we're only looking for one help item, break after # it's found if [ "${function}x" != "x" ]; then break; fi; continue; fi; fi; if [ $in_help -eq 1 ]; then echo ${input} | sed 's/^.//' fi; done; } # st_promote HELP: # st_promote # Take the given executable and promote it to the top of the path. # This does not move the executable's entire directory to the top of the path, # but rather just moves that particular executable. If you use this, in order # to keep the promote directory tidy, add st_promote_cleanup to a logout # script, like ${HOME}/.bash_logout. # ENDHELP function st_promote { [ -z $1 ] && echo "no path given" && return -1 # see if we need to create a promote directory for this shell shelltools_path=$(dirname ${ST_SH}) promote_dir="${shelltools_path}/promote/bin.$$" [ ! -e ${promote_dir} ] && mkdir -p ${promote_dir} while [ ! -z $1 ]; do [ ! -x $1 ] && echo "the given path is not an executable" && return -1 executable=$(basename $1) exec_path=$(which ${executable}) # if the promote path is already taken, remove the old and replace with # the new promote_path=${promote_dir}/${executable} [ -e ${promote_path} ] && rm -f ${promote_path} ln -s $1 ${promote_path} shift; done; # ensure the newly promoted executables make it to the top of the path. # Sometimes when an executable is moved to a path of higher priority it will # not be picked up as the old path is still cached. Running export on the # PATH will fix this, so this is a good idea even if the promote dir is # aleady in the PATH. export PATH=$(st_path ${promote_dir}:${PATH}) return 0; } # st_promote_cleanup HELP: # clean up the temporary promote directories if they exist # ENDHELP function st_promote_cleanup { shelltools_path=$(dirname ${ST_SH}) promote_dir=${shelltools_path}/promote # clean up any promote directory that is not associated with a live process for i in $(ls ${promote_dir}); do # get the directory's pid pid=$(echo $i | awk -F. '{print $2}') # if the pid is not alive, remove the directory ps -ef | grep -v grep | awk '{print $2}' | grep -q ${pid} status=$? if [ ${status} -ne 0 ]; then echo "removing $i" rm -rf ${promote_dir}/$i fi; done; # if an argument was given, treat that as the current process' pid and clean # it up. if [ "${1}" != "" ]; then this_dir=${promote_dir}/bin.$1 [ -e ${this_dir} ] && rm -rf ${this_dir} fi; } # st_pup HELP: # st_pup # pushd up the given number of directories # ENDHELP function st_pup { dir=$(st_echo_up $1) [ $? -ne 0 ] && "Unable to get directory" && return -1; pushd ${dir} } # st_pyswitch HELP: # st_pyswitch # Go through the PYTHONPATH environment variable and switch out all given # python version strings with the given version. Then look for the path of # the given python version and set it to the path specified in # $ST_PYTHON_LINK. Some paths in PYTHONPATH may no longer exist with the new # path, but this is intentional in order to preserve all the paths in the # right order in case st_pyswitch is used to switch back to the original # version. # ENDHELP function st_pyswitch { [ -z "$1" ] && echo "no python path given" && return -1; new_python_path=$1 if [ ! -x "${new_python_path}" ]; then echo "given python path not executable"; return -1; fi; new_python=$(basename $1) new_python_version="python$(${new_python_path} -V 2>&1 | \ sed 's/\(.*\)\..*/\1/' | awk '{print $2}')" curr_python="python$(python -V 2>&1 | \ sed 's/\(.*\)\..*/\1/' | awk '{print $2}')" # go through the current PYTHONPATH and switch out the current python # version with the new python version. if [ "${new_python_version}" != "${curr_python}" ]; then t="" for i in $(echo ${PYTHONPATH} | sed 's/:/ /g'); do new_python_dir=$(echo $i | sed "s/${curr_python}/${new_python}/g") t=$(st_path "$t:${new_python_dir}") done; export PYTHONPATH=${t} fi; # now promote the given python in PATH st_promote ${new_python_path} # if the promoted python is not "python" make the symlink if [ "${new_python}" != "python" ]; then t=$(which ${new_python}) t_dir=$(dirname ${t}) python_path="${t_dir}/python" [ -e ${python_path} ] && rm ${python_path} ln -s ${new_python_path} ${python_path} fi; } # st_repeat HELP: # st_repeat [-d ] [-i] [-c ] [-k] # Repeat the given command. # # By default, this command will exit upon success, unless -i or -c are given. # # -d (default 2) specifies how long to wait between iterations # -i will repeat the command indefinitely # -c will repeat the command the specified number of times # -k will clear the screen before each iteration # ENDHELP function st_repeat { unset OPTIND; delay=2; on_success=1; infinite=0; count=-1; clear=0; while getopts ":d:sikc:" options; do case $options in d ) delay=${OPTARG}; ;; i ) infinite=1; ;; c ) count=${OPTARG}; ;; k ) clear=1; ;; * ) st_help st_repeat; return 1; ;; esac; done; [ ${infinite} -eq 1 ] && [ ${count} -gt -1 ] && \ echo "You can only give a count or specify infinity" && \ return 1 shift $((${OPTIND} - 1)) i=1; while [ 1 ]; do [ ${clear} -eq 1 ] && clear; "$@" status=$? # if on_success was specified and exit status was 0, break [ ${infinite} -ne 1 ] && \ [ ${count} -lt 0 ] && \ [ ${on_success} -eq 1 ] && \ [ ${status} -eq 0 ] && break; let i="${i} + 1"; # if a count was specified, break [ ${infinite} -ne 1 ] && \ [ ${count} -gt -1 ] && \ [ ${i} -gt ${count} ] && break; # sleep sleep ${delay} done; } # st_rm HELP: # st_rm # If the given file is found remove it, otherwise exit quietly # ENDHELP function st_rm { while [ ! -z $1 ]; do [ -e $1 ] && rm -f $1 shift; done; } # st_run HELP: # st_run [args... ] # Run the given command and print out an error message if the command does not # complete successfully # ENDHELP function st_run { # run the command command=$1; shift; ${command} "$@" status=$? if [ ${status} -ne 0 ]; then echo "$1 failed!" 1>&2 fi; return ${status} } # st_run_or_exit HELP: # st_run_or_exit [args... ] # Run the given command and print out an error message if the command does not # complete successfully # ENDHELP function st_run_or_exit { st_run $* status=$? if [ ${status} -ne 0 ]; then exit ${status} fi; } # st_source HELP: # st_source # If the given file[s] exist[s] source. Return 0 if all the given files were # sourced successfully, non-zero othewise. # ENDHELP function st_source { num_sourced=0; count=0 for i in ${@}; do filename=$i; if [ -e ${filename} ]; then . $filename; status=$? [ ${status} -eq 0 ] && let num_sourced="${num_sourced} + 1"; fi; shift; let count="${count} + 1" done; [ ${num_sourced} -eq ${count} ] && return 0 || return 1 } # st_status HELP: # st_status # Run the command and echo the status. # # This is a shortcut around the idiom that goes something like this: # # ps -ef | grep -q foo # status=$? # # Instead you end up with: # # status=$(st_status ps -ef | grep -q foo) # ENDHELP function st_status { command=$1 shift ${command} "$@" 2>&1 > /dev/null status=$?; echo $status; return $status; } # st_up HELP: # st_up # cd up the given number of directories # ENDHELP function st_up { dir=$(st_echo_up $1) [ $? -ne 0 ] && "Unable to get directory" && return -1; cd ${dir} }