Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

#!/bin/bash without loosing your sleep


Published on

Operators are used to scripting, but learning bash can be confusing for developers. It starts out by putting a few commands in sequence and running it. Immediately you experience the power of the shell, and you want to do more. That's when you may experience that your scripts become a nightmare, and I am sure operators have experienced that to. When developers get more experienced in scripting they miss the set of tools and techniques they are used to from the developers world, and they start finding ways to incorporate them in bash scripting. And when they do, their scripts become a lot more manageable and less error-prone. Things like functions, unit tests, organising of code files, version control and logging frameworks enhances the quality, maintainability, readability, reusability, cleanliness, changeability, testability, security and ease of use. In this talk I will explain problems you may encounter when scripting bash and how to cope with them. I will show how techniques and tools from the programming world may be interesting to people coming from the operators world, and what developers need to learn from the operators world to become effective bash programmers.

  • Be the first to like this

#!/bin/bash without loosing your sleep

  1. 1. #!/bin/bash without loosing your sleep
  2. 2. #!/bin/bash# Usage: <artifact> <version>artifact=$1version=$2wget${artifact}/${version}/${artifact}-${version}.zipunzip ${artifact}.zip/etc/init.d/${artifact} stoprm ${artifact} # softlinkln -s ${artifact}-${version} ${artifact}/etc/init.d/${artifact} start
  3. 3. #!/bin/bash# Usage: <artifact> <version>artifact=$1version=$2/etc/init.d/${artifact} stoprm ${artifact} # softlinkln -s ${artifact}-${version} ${artifact}/etc/init.d/${artifact} start
  4. 4. includes=( "../include/" "../include/" )for include in ${includes[@]}do if [ -f ${include} ]; then . ${include} else _fatal "File ${include} not found. Quitting! :-(" fidone
  5. 5. _is_snapshot() { [[ "${1}" =~ ^[0-9]+(.[0-9]+)+-SNAPSHOT$ ]] && return 0 || return 1}_run() { local cmd=${1} _info "Running: ${cmd}" if [ $? -ne 0 ]; then _fatal "${cmd} failed! Please retry the command manually." fi}_mwget() { local wget="wget -nd -nH -r -l1 --no-parent -A "${1}" ${2}" _info "Running: ${wget}" eval $wget return $?}
  6. 6. _ms() { local S=${1} ((m=S%3600/60)) ((s=S%60)) printf "%dm:%ds" $m $s}expected=10m:15sval=$(_ms 615) && retval=$? || retval=$? && [[ "${val}" == "${expected}" ]] && _info "test passed! (retval=${retval})" || _fatal "test failed! Expected "${expected}" but was "$val" (retval=${retval})"exit 0
  7. 7. _assertEquals() { local arguments=( "$@" ) local expected="${1}" local actual="${2}" local msg="${arguments[@]:2}" [[ "${expected}" == "${actual}" ]] && _info "test passed! (value=${actual}) ${msg}" || _fatal "test failed! Expected value ${expected} but was ${actual} ${msg}" [[ $1 =~ "^[0-9]+$" ]] && return ${actual} || return 0}HOSTNAME="node1"expected="8080"actual=$( _getWebServerPort )_assertEquals 0 $?_assertEquals ${expected} ${actual} "Test: _getWebServerPort when server is ${HOSTNAME}"HOSTNAME="no_such_server"expected="UNDEFINED"actual=$( _getWebServerPort )_assertEquals 1 $?_assertEquals ${expected} ${actual} "Test: _getWebServerPort when server is nonexistent"
  8. 8. # Syntax checkecho "Running syntax check ..."scripts=$(ls -1 *.sh ../include/*.sh ../scripts/*.sh)for script in ${scripts[@]}do echo "checking ${script}" bash -n ${script} || exit 1doneecho "Syntax check finished ..."# Testsecho "Running tests ..."tests=$(ls -1 ../tests/test*.sh )for test in ${tests[@]}do echo "running ${test}" ${test} || exit 1doneecho "Tests finished ..."exit 0
  9. 9. ...13 if [ "${version}" != "rollback" ] && [ ${#artifactsAndVersions[@]} -ne 0 ]; then14 if ( _contains ${targets[@]} ${host} ) || ( _is_snapshot ${deployVersion} ) ;15 then16 deployFile="${M2_REPO}/no/posten/dpost/deploy/${deployVersion}/deploy-${deployVersion}.zip"17 if [ ! -s "${deployFile}" ]; then18 echo "mkdir -p ${M2_REPO}/no/posten/dpost/deploy/${deployVersion}/"19 if ( _is_snapshot ${deployVersion} ); then20 if [ -s ~/deploy-${deployVersion}.zip ]; then21 _debug "Found deploy-${deployVersion}.zip in homedir. Moving it to ${deployFile}."22 _run "echo ~/deploy-${deployVersion}.zip ${deployFile}"23 if [ ! -s ~/deploy-${deployVersion}.zip ]; then24 _fatal "You are trying to deploy a SNAPSHOT version, but it is not installed"25 fi26 else27 _fatal "Could not find deploy-${deployVersion}.zip at ${deployFile}."28 fi29 [[ -e ${deployFile} ]] || _fatal "${deployFile} not found locally or in Nexus! Exiting!"30 fi31 artifactFilesToUpload+=( "${deployFile}" )32 else33 installDeployCmds=( "wget -O deploy-${deployVersion}.zip ${NEXUS_URL}deploy-${deployVersion}.zip" )34 fi35 fi/Users/stein/ line 35: syntax error: unexpected end of file
  10. 10. debug="true"_run_ssh() { local -a servers=( "${!1}" ) local cmd=${2} for server in ${servers[@]} do local remote_cmd="ssh -tt ${server} "${cmd}"" _info "Running: ${remote_cmd}" test "${debug}" == "true" && debug_cmds+=( "${remote_cmd}" ) || eval ${remote_cmd} if [ $? -ne 0 ]; then _fatal "${remote_cmd} failed! Please retry the command(s) on the remote server(s)." fi done test "${debug}" == "true" && echo "${debug_cmds[@]}"}
  11. 11. _info() { echo -e 1>&2 "033[32m-->" $@ "033[0m" # green}_debug() { test "${debug}" == "true" && echo -e 1>&2 "033[34m-->" $@ "033[0m" # purple}_error() { echo -e 1>&2 "033[31m-->" $@ "033[0m" # red}_fatal() { echo -e 1>&2 "033[31m-->" $@ "033[0m" # red exit 2}_happyQuit() { echo -e 1>&2 "033[36m-->" $@ "033[0m" # blue exit 0}
  12. 12. _init_log() { local timestamp=`date +%Y%m%d` logs="./logs" log_filename="${1}" if [ ! -d ${logs} ]; then mkdir $logs fi find $logs -name ${log_filename} -type f -size +512k | while read logfile do echo $logfile local newlogfile=$logfile.$timestamp cp $logfile $newlogfile cat /dev/null > $logfile gzip -f -9 $newlogfile done find $logs -name "${log_filename}*.gz" -type f -mtime 30 |xargs rm -f}
  13. 13. _run_with_rollback_if_fail() { _info "Running ${1} ..." ${1} response=$? if [ $response -ne 0 ]; then _rollback "${previous_version_directory}" "${home}/${artifact}-${version}" fi}
  14. 14. _rollback() { local rollback_to="${1}" local rollback_from="${2}" cd ${home} _info "Rolling back from ${rollback_from} to ${rollback_to}" if [ -d "${rollback_to}" ]; then _stop if [ -h "${artifact}" ]; then _delete ${artifact} fi if [ -d "${rollback_from}" ]; then _delete ${rollback_from} fi mv ${rollback_to} ${home} ln -s ${artifact}-${version} ${artifact} if ( _start ); then _info "Rollback to ${rollback_to} successful." else _fatal "Could not start ${artifact}. Rollback to ${rollback_to} failed!" fi else _error "File ${rollback_to} not found. Rollback failed!" if [ -d "${rollback_from}" ] && [ -h "${artifact}" ]; then _start else _fatal "No executable versions of ${artifact} exists!" fi fi}
  15. 15. THANK YOU! Stein Inge MorisbakPractice lead Continuous Delivery and DevOps @ BEKK