User Tools

Site Tools


docs:tips_n_tricks:shellskripting.html

Shellskripting

Error handling

(this will only work on recent bash versions)

#!/bin/bash
# as this uses bash-isms, better not use #! /bin/sh

on_error()
{
 echo "error encountered in $BASH_COMMAND" >&2
}

trap on_error ERR

# inherit trap on error to functions and subshells
set -o errtrace 

# don't continue after an error: 
set -o errexit

more sofisticated on_error() function:

on_error()
{
  case "$1" in
    "")  mailbody="error encountered in '$BASH_COMMAND' on line ${BASH_LINENO[0]}" ;;
    *)   mailbody="$1"                                                           ;;
  esac
  if tty > /dev/null
  then
    echo "ERROR: $mailbody" >&2
  else
    tty || echo "tty failed with $?"
    mail -s "Probleme mit Notes SIT auf `hostname` in '$0'" $mailto <<EOF
$mailbody
EOF
fi
}

safely delete double files

Assuming, you have a complete copy of directory dir on cdrom, mounted at /cdrom and want to delete them only after checking if the copy on CD is valid:

find dir -type f -print0 \
 | xargs -0 -n 1 -I XXX sh -c "echo 'checking file \"XXX\"' ; diff -- 'XXX' /cdrom/'XXX' && rm -v -- 'XXX' || kill \"$PPID\""

Note: This solution copes even with filenames containg weird characters like $ and linefeeds.

If you want to clean up everything else afterwards without accidently removing left over files (as rm -r would do) use these commands:

find . -type l -print0 | xargs -0 rm -v
find . -type s -print0 | xargs -0 rm -v
find . -type p -print0 | xargs -0 rm -v
find . -type d -empty -print0 |xargs -0 rmdir -p

Yes, it's paranoid. But I prefere being paranoid over loosing data ;-)

And no, this way you won't have a check whether all pipes, sockets and symlinks are the same in both directories. Talking about being paranoid, hmmmm?

Reading config parameters from file

Simple

script:

value=`awk ' $1 == "key" {print $2}' file.conf`

file.conf:

# key may occur in comments without causing problems
key dies_ist_der_wert

will set key to “dies_ist_der_wert”

Safe

script:

value="`sed -ne 's/^[[:space:]]*key[[:space:]]*\([^#][^[:space:]]*\)[[:space:]]*\(#.*\|\)$/\1/gp'  file.conf`"

file.conf:

# key may occur in comments without causing problems
# all text between key and # or end of line without
# leading or trailing whitespace is considered
# the value (i.e. "dies ist der wert")
key dies ist der wert # und dies ist ein Kommentar

Extend PATH and MANPATH in startup scripts

for addpath in ~/bin
do
  if echo "$PATH" | grep -qv "$addpath"
  then
    PATH="$PATH:$addpath"
  fi
done

if man --path >/dev/null 2>&1
then
  for addpath in ~/man
  do
    MANPATH="`man --path`:$addpath"
  done
fi

Retrieve cn from .ldif file

get_cn()
{

 ( cline=`grep -m 1 '^cn:'`
   while IFS="" read -r cont
   do
     case "$cont" in
      " "*) cline="$cline""${cont# }" ;;
      *)    break ;;
     esac
   done
   case "$cline" in
     "cn: "*) echo "${cline#cn: }" ;;
     "cn::"*) echo "${cline#cn:: }" | base64 -id ;;
     *) echo "FAILURE" >&2 ; break ;;
   esac ) < "$1"
}

Get process start time

ps --no-headers -o lstart -p "$pid"

Read Passwords from Terminal

read -r -s -p "Password: " pass
echo

Use "trap" with inline function

trap '(echo goodbye;echo "and farewell";)' EXIT

Create List of existing and possible aliases

mkaliases()
{
 ( ls /opt/local/bin/ \
   | while read item
     do
       if alias "$item" > /dev/null
       then
        alias $item | sed -e 's:^\(.*\)=\(.*\)$':"alias \1='\2':g"
       else
        echo "# alias $item='/opt/local/bin/$item'"
       fi
   done 
   alias | sed -E -e "s:^(.*)='?(.*[^'])'?$:alias \1='\2':g" ) \
 | sort -u
}

Generic Script Template

#! /bin/sh

# Let's see which CVS version this is:
# $Id: shellscript_template.sh,v 1.4 2008/05/15 14:17:55 pjw Exp $
# set some default:

check_arg="foobar"

print_usage()
{
  sed -e "s:^  ::g" << EOF # little trick to have HERE document indented
  This is a lab lubba script to achieve foobar
  It will skip files starting with [xyz].
  
  Usage: ${0##*/} [-a|-b] [-c <arg1>] <file> [<file> ...]

   Options:
     -h        : help    - print this and exit
     -v        : verbose - print more messages
     -q        : quiet   - print less messages
     -a        : add     - some explanation what add means
     -b        : block   - explain what block means
     -c <arg1> : check   - explain what check means and what arg
                           ist to be given (default "${check_arg}")
     <file>    : file(s) to act on

  \$Id: shellscript_template.sh,v 1.4 2008/05/15 14:17:55 pjw Exp $  
EOF
}

# # define functions for easy handling of -q (quiet) and -v (verbose) flags
echo_noquiet()
{
  [ -z "$be_quiet"   ] && echo "$@" ; # "$@" will echo all function args
} 

echo_verbose()
{
  [ -n "$be_verbose" ] && echo "$@" ; # "$@" will echo all function args
} 

# just an example for a function:
# (see end of script for invocation)
multiply()
{
 # take the two parameters of the fuction call and multiply them
 # take the two parameters of the fuction call and multiply them
 let "prod = $1 * $2"
 echo $prod
}

# end of function definition
# --------------------------------
# start parsing command line arguments

# unset vars just in case
unset do_add
unset do_block
unset be_quiet
unset be_verbose
# init empty array
files=()

# check whether there are command line args left
while [ "$#" -gt 0 ]
do
  case "$1" in
    "-h") print_usage  # print to stdout as this is no error
          exit         # normal exit as the user asked for help
          ;; 
    "-a") do_add=yes         ;;
    "-b") do_block=yes       ;;
    "-q") be_quiet=yes       ;;
    "-v") be_verbose=yes     ;;
    "-c") check_arg="$2" # use quotes to cope with whitespace
          shift          # do additional shift for parameter
          ;;
    "-"*) echo "Unknown Option $1" >&2 # print error to stderr
          print_usage >&2              # print to stderr as it was not expected
          exit 42                      # return error as it is one
          ;;
    *)    files=( "${files[@]}" "$1") # build shell arrays of filename to cope with
                                      # multiple files even when containing whitespace
          ;;
  esac
  shift # drop first command line argument and shift other ones
done

# avoid any writes / changes if called without arguments
# so if there is no read-only default action, abord:

if [ -z "$do_add" -a -z "$do_block" ]
then
  echo "Missing run option" >&2 # print errors to stderr
  print_usage               >&2 # print errors to stderr
  exit 42                       # indicate error my exit code != 0
fi


# abord if no files are given (never do any write operation by default)

if [ "${#files[@]}" -eq 0 ] 
then
  echo "Missing filenames" >&2 # print errors to stderr
  print_usage              >&2 # print errors to stderr
  exit 42                      # indicate error my exit code != 0
fi

echo_noquiet "check_arg is $check_arg"

# loop over array elements while respecting spaces in one element
for file in "${files[@]}"
do
 echo " ========= "
 echo "handling $file"
 case "$file" in 
  [xyz]*) echo_noquiet "skipping file '$file' starting with x,y or z"
          continue      # jump to next 
          ;; 
  *)      echo_noquiet -n "do something with file"
          [ -n "$do_block" ] && echo_noquiet -n " while blocking something" 
          [ -n "$do_add"   ] && echo_noquiet -n " and add something" 
          echo_noquiet # finish line
          echo_verbose "tell more about file '$file'"
          ;;
 esac
done

echo
echo "end of parsing files"
echo
echo "this output is generated by a function:"
multiply 23 42

Move by year in name with zsh

mkdir  2015 2016 2017 2018
setopt cshnullglob
for year in 19?? 20??
do     
  mv -iv _"$year"*.JPG IMG_"$year"*.jpg IMG_OC_"$year"*.jpg PANO_"$year"*.jpg PANO_"$year"*.raw VID_"$year"*.mp4 "$year"/ \
  || echo "ERROR on $year" >&2
done

Append to history in script (bash)

addhist.sh
#! /bin/bash
 
bash -i -c "history -s \"echo 'Super langer Befehlt aus Skript' `date`\""
 
exit 0

Variant 1

~ $ date
Sat Aug 24 14:13:57 CEST 2019
~ $ addhist.sh 
~ $ history -r
~ $ date
Sat Aug 24 14:14:11 CEST 2019
~ $
[Key ↑]
~ $ echo 'Super langer Befehlt aus Skript' Sat Aug 24 14:14:01 CEST 2019

Variant 2

~ $ alias addhist='export=HISTFILE=`mktemp` ; /full/path/to/addhist.sh ; history -r ; rm "$HISTFILE" ; export HISTFIL=~/.bash_history'
~ $ date
Sat Aug 24 14:13:57 CEST 2019
~ $ addhist
~ $ date
Sat Aug 24 14:14:11 CEST 2019
~ $
[Key ↑]
~ $ echo 'Super langer Befehlt aus Skript' Sat Aug 24 14:14:01 CEST 2019
docs/tips_n_tricks/shellskripting.html.txt · Last modified: 25.08.2019 12:55 CEST by peter