Skip to content

Instantly share code, notes, and snippets.

@rca
Created Aug 21, 2012
Embed
What would you like to do?
Semi-useful Shell functions
# Copyright (c) 2004-2008, Roberto Aguilar <berto at chamaco dot org>
# 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, source shelltools.sh
#
# souce /path/to/shelltools.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"
export ST_SH=$BASH_SOURCE
ST_APPDIR_PATHS=(\
'PATH:bin' 'MANPATH:share/man' 'MANPATH:man' 'LD_LIBRARY_PATH:lib')
# st_appdir_set HELP:
# st_appdir_set <root>
# 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
root=$(cd "$1"; pwd)
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 <root>
# 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
root=$(cd "$1"; pwd)
# 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_check_pidfile HELP:
# st_check_pidfile <pidfile>
# Check the given pid file. If no pidfile is given, return -1. If
# the file does not exist, return -2. If the file exists and a pid is
# found on the first line. If no pid is found in the file return -3.
# If a pid is found, check to see if the process is running. If the
# process is running, return 0; 1 otherwise
# ENDHELP
function st_check_pidfile
{
[ -z "$1" ] && echo "No pidfile given" && return -1
pidfile=$1
[ ! -e ${pidfile} ] && echo "${pidfile} not found" && return -2
pid=$(head -n 1 ${pidfile} | awk '{print $1}')
[ "${pid}" == "" ] && echo "No pid found in ${pidfile}" && return -3
ps -eo pid | grep -q ${pid}
}
# st_common HELP:
# st_common <string1> <string2>
# 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 <current_delimiter> <new_delimiter> <string>
# 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 <number>
# 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 <list>
# 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 <list>
# 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 <message>
# 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 <item> [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 <list>
# 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 <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 <path>
# 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 <path to remove> <path list>
# 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_path_replace HELP:
# st_path_replace <path to remove> <path to add> <path list>
# If the given path is found in the colon-delimited list, remove it
# ENDHELP
function st_path_replace
{
[ -z $3 ] && \
echo "please specify the path to pop, the path to push and the path list" 1>&2 && \
return -1
remove_path=$1
shift
add_path=$1
shift
path_split=$(st_delimit : " " $1)
st_delimit " " : "$(st_replace ${remove_path} ${add_path} ${path_split})"
}
# st_pop HELP:
# st_pop <item> [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}
}
# st_replace HELP:
# st_replace <old> <new> [list]
# Check to see if the first argument of the function exists in the
# third and subsequent arguments. If it does, replace it with <new>
# 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_replace
{
old=$1
shift
new=$1
shift
where="${*}"
echo "${where}" | grep -q -- "${old}"
status=$?
if [ ${status} -eq 0 ]; then
SED_EXPR="s#${old}#${new}#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 <path>
# 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_prompt HELP:
# st_prompt <message> [default=y]
# Prompt the user for a response and return 0 for 'yes' and 1 for 'no'.
# This function uses the typical shell convention where status 0 is good.
# ENDHELP
function st_prompt
{
[ -z "$1" ] && echo "no message given" && return -1;
message=$1;
default=$(echo ${2:-"y"} | tr [a-z] [A-Z]);
if [ "${default}" == "Y" ]; then
answers="[Y|n]";
else
answers="[y|N]";
fi;
/bin/echo -n "${message} ${answers} ";
read input;
input=$(echo ${input} | tr [a-z] [A-Z])
if [ "${input}" == "Y" ] || \
([ "${default}" == "Y" ] && [ "${input}" == "" ]); then
status=0;
else
status=1;
fi;
return ${status}
}
# st_pup HELP:
# st_pup <number>
# 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_pypath HELP:
# st_pypath <python version>
# Add the given path to the PYTHONPATH environment variable
# ENDHELP
function st_pypath
{
[ -z "$1" ] && echo "nopath given" && return -1;
path=$1
if [ "${path:0:1}" != "/" ]; then
cd $path
path=$(pwd)
cd - > /dev/null
fi;
export PYTHONPATH=$(st_path ${path}:${PYTHONPATH})
}
# st_pyswitch HELP:
# st_pyswitch <python version>
# 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 <delay>] [-i] [-c <count>] [-k] <command>
# 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 <filename>
# 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 <command> [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 <command> [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 <filename>
# 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 <command>
# 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 <number>
# 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}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment