Shell snippets - learn-it/scripting GitHub Wiki
Snippets
Code is mostly BASH-style, compatibility with plain Bourne Shell is not guaranteed!
Detect if Bash script is sourced:
if [ "$_" = /bin/bash ]; then
echo "Script is exec-ed"
elif [ "$0" = "$BASH_SOURCE" ]; then
echo "Script is subshell"
else
echo "Script is sourced"
fi
Replace sourced script with subshell:
if [ $_ != $0 ](/learn-it/scripting/wiki/-$_-!=-$0-)
then
echo "Executing instead of source!" >&2
"${BASH_SOURCE[0]}" "$@"
return
fi
Die from subshell:
die()
{
[ -n "$1" ] && echo "$1" >&2;
exit 1
}
Die from subshell or from sourced script:
die()
{
[ -n "$1" ] && echo "$1" >&2;
if [ $_ != $0 ](/learn-it/scripting/wiki/-$_-!=-$0-)
then
while true; do kill -SIGINT -$$; sleep 700d; done
else
exit 1
fi
}
Turn off word splitting in variables:
IFS=""
shopt -s expand_aliases
alias read='IFS=" " read'
Useful for avoiding every-time protection of variables with double quotes (i.e vars as single arguments):
$ IFS=""
$ f() { echo $1; }; x="a b c"; f $x
a b c
To return to default behavior unset IFS:
$ unset IFS
$ f() { echo $1; }; x="a b c"; f $x
a
Get command-line options with getopt:
Check foo.sh for usage ready bash script starter with inline documentation of options and some utility functions.
#!/bin/bash
optstring_long="foo,bar:,baz::,verbose,dry-run,help"
optstring_short="fb:z::vnh"
opts=$(getopt -o "${optstring_short}" --long "${optstring_long}" --name "$0" -- "$@") ||
exit $?
eval set -- "$opts"
unset foo
unset bar
unset baz
unset verbose_on
unset verbose_off
unset verbose
unset dryrun
while true
do
case "$1" in
-f|--foo)
foo=true
echo "Foo!"
shift;;
-b|--bar)
bar=$2
echo "Bar: ${bar}"
shift 2;;
-z|--baz)
baz=${2:-default!}
echo "Baz: ${baz}"
shift 2;;
-v|--verbose)
verbose_on="set -x"
verbose_off="{ set +x; } 2>/dev/null"
verbose=verbose_run
shift;;
-n|--dry-run)
dryrun=echo
shift;;
-h|--help)
cat <<EOF
This is help!
EOF
exit;;
--) shift; break;;
esac
done
verbose_run() { set -x; eval "$@"; { set +x; } 2>/dev/null; }
Required parameters for getopt:
require_param()
{
local param
local missed=""
for param in "$@"
do
eval local val=\$$param
[ -z "$val" ] && {
missed="$missed --$param"
}
done
[ -n "$missed" ] &&
die "Required parameter(s) missed:${missed}"
}
Optional parameters for getopt:
optional_arg()
{
eval local next_token=\$$((OPTIND + 1))
if [ -n $next_token && $next_token != -* ](/learn-it/scripting/wiki/--n-$next_token-&&-$next_token-!=--*-); then
OPTIND=$((OPTIND + 1))
OPTARG=$next_token
[ -n "$1" ](/learn-it/scripting/wiki/--n-"$1"-) &&
eval $1=\$next_token
else
OPTARG=""
fi
}
Convert $optstring_short into case condition (useful for writing wrappers):
$ sed -r -e 's/(.)/-\1|/g;s/^(.*)\|$/+(\1)/' <<< abcde
+(-a|-b|-c|-d|-e)
Use in combination with shopt -s extglob.
Check that PATH contains specific directory $dir:
[ ":$PATH:" == *":${dir}:"* ](/learn-it/scripting/wiki/-":$PATH:"-==-*":${dir}:"*-) &&
echo 'Yes!'
Remove directory $dir from PATH:
tmp=":${PATH}:"
tmp=${tmp/:${dir}:/:}
tmp=${tmp%:};
PATH=${tmp#:}
Add $dir to path variable:
add_path()
{
local v=$1
shift
local back=false
if [ "$v" = _back ]; then
v=$1
shift
back=true
fi
while [ "$1" ]; do
if eval [ ":\$$v:" == *\"":${1}:"\"* ](/learn-it/scripting/wiki/-":\$$v:"-==-*\"":${1}:"\"*-); then
shift
continue
fi
if $back; then
eval export $v=\$$v:\$1
else
eval export $v=\$1:\$$v
fi
shift
done
}
Example:
add_path PATH "${opt}/bin" "${bush_dir}" "${bush_dir}/bin"
add_path _back CDPATH "${src[0]}" "${src[1]}"
Push back into indexed array:
push_back()
{
arr=$1; shift
for val in "$@"
do
eval $arr[\${#$arr[@]}]=\$val
done
}
# Usage:
# your_array=()
# push_back your_array value1 [value2] ...
# echo "${your_array[@]}"
Check if directory is empty
check_empty()
{
shopt -s nullglob # to not croak on empty expansion
shopt -s dotglob # to include hidden files
local -a files=(${1:-.}/*)
shopt -u nullglob dotglob
if [ "$files" ]
then
return 1
else
return 0
fi
}
Ask yes/no question (reads single char without Enter):
ask_yes_no()
{
local REPLY
while true; do
read -n1 ${1:+-p $@}
echo
case $REPLY in
[Yy]) return 0;;
[Nn] ) return 1;;
esac
done
}
Canonicalize script location:
script=$(readlink -ne "$0")
Show backtrace:
backtrace ()
{
echo "Backtrace is:"
i=0
while caller $i
do
i=$((i+1))
done
}
Test DEBUG trap:
#!/bin/bash
run()
{
echo executing: $BASH_COMMAND
}
trap run DEBUG
a=1
while true
do
echo h
exit
done
Result:
executing: a=1
executing: true
executing: echo h
h
executing: exit
Print timestamped message:
$ date '+%b %e %H:%M:%S Hello world!'
Jul 2 13:54:41 Hello world!
$ date '+%F %H:%M:%S Bye hell!'
2015-03-05 13:28:55 Bye hell!
$ date '+timestamped_%Y%m%d_%H%M%S'
timestamped_20171128_114901
Show progress with dots:
Redirect output to it, each 100 lines a dot will be printed. Supply it initial message as first argument.
show_progress()
{
t=$1
case "$option_verbose" in
y*) cat;;
*)
i=0
step=100
while read
do
if ((i > step))
then
i=0
echo -n ${t}.
unset t
else
((i++))
fi
done
[ -z "$t" ] && echo done!
;;
esac
}
Kill all children:
kill $(jobs -p)
Reap children at exit:
at_exit()
{
kill $(jobs -p)
}
trap at_exit EXIT
Convert word to parameter
word=\'${word//\'/\'\"\'\"\'}\'
Find byte sequence in binary file (broken?)
grep -obaUP '\xfc\x4e' /dev/sda1
66649758:üN
67538791:üN
...
Broken? Not confirmed by hexdump. xxd is broken (shows all 00 instead of data).
Read binary sequence from file (limited)
LC_ALL=C
bin2hex()
{
# FIXME: this cannot read zero-byte, it is treated as EOF
while read -n 1 byte
do
printf "%x" "'$byte"
done
printf "\n"
}
Hints
- Using
localwhen declaring/assigning vars inside functions will make them local <<<for expressions works like<<for filesdeclare -Amakes associative arrays (no another way)- Always remember that when you create pipeline, you create sub-process:
$ what=no && echo yes| read what && echo $what
no
Don't believe that read works?
$ what=no && echo yes| (read what && echo $what)
yes
It is possible to use read in current process:
$ what=no && read what <<< yes && echo $what
yes
Or even with catching sub-process output:
$ what=no && read what <<< "$(echo yes)" && echo $what
yes
- [ expr1 -a expr2 ] is faster (~15% CPU) equivalent of [ expr1 ] && [ expr2 ]
- Don't forget about trailing
/in path intersection checks:
dir_a=/abc/def
dir_b=/abc
[ $dir_a/ == $dir_b/* ](/learn-it/scripting/wiki/-$dir_a/-==-$dir_b/*-) &&
echo yes
[ $dir_a == $dir_b/* ](/learn-it/scripting/wiki/-$dir_a-==-$dir_b/*-) ||
echo no
[ $dir_a// == $dir_b/* ](/learn-it/scripting/wiki/-$dir_a//-==-$dir_b/*-) &&
echo yes
Result:
yes
no
yes
Note, that count of / does not matter. So, you should not worry if / termination in variable.
command_not_found_handle()sub will catch command not found errors in Bash 4.