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 local when declaring/assigning vars inside functions will make them local
  • <<< for expressions works like << for files
  • declare -A makes 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.

Links