Voor het beheren van UNIX machines worden er bij AT Computing regelmatig
scripts geschreven. Deze scripts beginnen altijd eenvoudig, maar dat
blijft meestal niet zo… Dit blog artikel laat zien hoe een simpel
script uitgebreid kan worden.
N.B. Dit is een manier om je script leesbaar te houden. Er zijn
andere manieren die ook door consultants van AT Computing gebruikt
worden.
Alvorens ik inhoudelijk op het script in ga, wil ik eerst een kleine
inleiding geven:
Het scriptje zorgt ervoor dat je een file met vi kunt editen en dat het
daarna in een git repository wordt gezet zodat er versie beheer is. Ook
wordt de speciale string $Hash$ geëxpandeerd a la de $Id$ string die
bekend is van subversion en CVS.
Het origineel
Het oer script is klein genoeg (net geen 50 regels) om hier in zijn volle
glorie te tonen, zoals je ziet is het ook nog in zsh geschreven
(aangezien dat mijn favoriete shell is).
#!/bin/zsh
# a wrapper around git and vi
# expands $Hash$ to $Hash: file short_hash date committer $
[[ ! -x =git ]] && exit 1
who=${SUDO_USER:-$LOGNAME}
full=$(getent passwd $who | awk -F: '{ gsub(/,*/, ""); print $5 }')
author="$full <$who@atoom.net>"
function search_git_dir {
gpath="$1"
[[ -d "$gpath/.git" ]] && echo "$gpath" && return
[[ -z "$gpath" ]] && echo "" && return
# strip that last path component and try again
search_git_dir "${gpath%/*}"
}
for file in "$@"; do
dir=$(dirname "$file")
cd "$dir"
base=$(basename "$file")
if [[ -z $(search_git_dir "$PWD") ]]; then
# make a new one in $PWD
git init || exit 1
else
#echo FOUND ONE
fi
chmod +w "$base" 2> /dev/null
if ${EDITOR:-/usr/bin/vi} "$base"; then
[[ ! -e $base ]] && exit 0
git add $base
# collapse $Hash: id $ line
sed -i -e 's/\$Hash:.*\$/$Hash$/' "$base"
git commit --author "$author" "$base"
fi
id=$(git-show -s --pretty=format:$base\ %h\ %ci\ $who%n -- "$base")
[[ -z $id ]] && exit 1
# re-add $Hash: sha1hash$ line
sed -i -e 's/\$Hash\$'/\$Hash:\ $id\ \$/ "$base"
chmod a-w $base 2> /dev/null
cd - >/dev/null
done
Zoals je ziet weinig commentaar, maar voor de gevorderde shell
programmeur nog redelijk te behappen.
Het script is nu af, maar binnen grotere organisaties is het vaak
gewenst om een script van meer commentaar te voorzien. Als er
veel scripts in inloop zijn kunnen deze het beste centraal beheert
worden. Ook een versie beheer systeem mag niet ontbreken, bijvoorbeeld
(hoe kan het ook anders) git.
Een file header toevoegen
Aangezien we op onze interne systemen meestal de bash shell gebruiken is het
script als eerste hiernaartoe omgezet. Ook is een programma header toegevoegd.
Het commentaar in dit script is in het Engels. Dit is geen bedrijfs standaard,
maar veel van onze klanten prefereren scripts in het Engels, vandaar.
Dit ziet er als volgt uit:
#!/bin/bash
#------------------------------------------------------------------------------#
# vi: set sw=4 ts=4 ai: ("set modeline" in ~/.exrc) #
#------------------------------------------------------------------------------#
# Program : vigit #
# #
# Author : Ton Kersten Ton.Kersten@ATComputing.nl #
# AT Computing Toernooiveld 104 #
# 6525 EC Nijmegen The Netherlands #
# Fax: +31-24 3527292 Tel: +31-24 3527282 #
# #
# Date : 23-01-2009 Time : 12:18 #
# #
# Description : Program to edit files and commit them to git #
# #
# Parameters : The files #
# #
# Pre reqs : Git should be installed #
# #
# Remarks : Stolen from and based on an idea of Miek Gieben #
# #
# Exit codes : 0 -> OK #
# <> 0 -> !OK #
# #
# Updates : None (yet) #
#------------------------------------------------------------------------------#
# (c) Copyright 2009 by AT Computing, The Netherlands #
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
# V e r s i o n i n f o r m a t i o n #
#------------------------------------------------------------------------------#
# $Id:: vigit 3 2009-01-26 09:36:01Z tonk $: #
# $Revision:: 3 $: #
# $Author:: Ton Kersten <Ton.Kersten@ATComputing.nl> $: #
# $Date:: 2009-01-26 10:36:13 +0100 (Mon, 26 Jan 2009) $: #
#------------------------------------------------------------------------------#
# E n d o f v e r s i o n i n f o r m a t i o n #
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
# Determine the program name and the 'running directory' #
#------------------------------------------------------------------------------#
IAM="${0##*/}"
CRD="$( [[ "$(printf "${0}" | cut -c 1 )" = "." ]] &&
{ printf "${PWD}/${0}"
} || {
printf "${0}"
})"
CRD="${CRD%/*}"
CUR="${PWD}"
#------------------------------------------------------------------------------#
# Save the shell settings #
#------------------------------------------------------------------------------#
SETA=0; [[ ${-} = *a* ]] && SETA=1
SETE=0; [[ ${-} = *e* ]] && SETE=1
SETU=0; [[ ${-} = *u* ]] && SETU=1
SETX=0; [[ ${-} = *x* ]] && SETX=1
#------------------------------------------------------------------------------#
# Set and unset the needed shell settings #
#------------------------------------------------------------------------------#
set +o noclobber # Overwrite existing files, if needed #
set -o nounset # Don't allow uninitialized variables #
set +o errexit # No returncode checking #
Hierbij wordt een aantal zaken altijd op dezelfde manier opgezet, zoals bijvoorbeeld
het set -o nounset waardoor een script geen onbekende variabelen toestaat.
Deze complete header kan worden gegenereerd met een, speciaal voor dit doel geschreven,
header script.
Ook wordt het script voorzien van duidelijk commentaar, hier en daar doorspekt
met humor. Het commentaar dient ervoor te zorgen dat het script voor iedereen beter
leesbaar wordt.
Een paar voorbeelden uit de uiteindelijke code:
#------------------------------------------------------------------------------#
# No git, no glory #
#------------------------------------------------------------------------------#
[[ x"$(which git 2>/dev/null)" = x"" ]] &&
{ echo "No 'git' found. Please use plain vi(m)"
exit 1
}
of
#----------------------------------------------------------------------#
# Check if the file is already in git (get the current hash) #
#----------------------------------------------------------------------#
initial=0
hash=$(git-show -s --pretty=format:"${base} %h %ci ${who}" -- "${base}" 2>/dev/null)
[[ x"${hash}" = x"" ]] &&
{ git add "${base}"
git commit --author "${author}" -m "Automatic initial checkin by '${IAM}'" "${base}"
initial=1
}
of
#--------------------------------------------------------------------------#
# Re-add the $Hash$ line #
#--------------------------------------------------------------------------#
if [[ ${havehash} != 0 ]]
then
if [[ ${longhash} != 0 ]]
then
id="${id}${spc}"
sed -i.bck -e 's!\([[:space:]]*\$Hash::\).*\$:!\1 '"${id:0:66}"'\$:!' "${base}"
else
sed -i.bck "s/\\\$Hash\\\$/\$Hash: ${id} \$/" "${base}"
fi
rm -f "${base}.bck"
fi
Conclusie
Gedurende dit proces zijn ook talloze bugjes gefixt en is het hele
script geaudit. Het eind resultaat is dus dat we hier een “Enterprise
Ready” (TM) script hebben gekregen, terwijl het oer script ook heeft
geprofiteerd van dit herschrijf proces.
Hierbij is echter wel opvallend dat het oer script slechts uit 50 regels bestaat
en de uiteindelijke versie 254 regels groot is. Ook loopt het nu op
FreeBSD (en waarschijnlijk andere Unixen) in plaats van alleen op Linux.
Het uiteindelijke script is
hier te vinden.