Tag: bash

Adding defensive sanity checks

I recently needed to make a set of several favicons, so I went to the web to see if anyone had a script I could borrow steal.

Sure enough, I found one written by Joshua McGee: “Create a favicon with ImageMagick” (not reproduced here for copyright reasons).

It was a simple enough script, just a series of escaped commands. I noticed, however, that it assumed a few things:

  • An image file was specified on the command line,
  • The image existed, and
  • Imagemagik was installed.

In other words, the script was not developed defensively. This makes sense: it was just a bang-out.

The script had no inline documentation, and if a favicon file that already existed in the current directory would be silently overwritten—not good.

I’m clumsy: I delete and overwrite files all the time, so I could use a little help. Maybe I can tidy up the script?

Ensure command-line parameter

Whenever your script requires a command-line parameter, display a help if it is not provided or is the wrong type. Why? Because in 6 months you will forget how to use it.

if [[ -z "$1" ]]; then
		echo -e "\nUsage:\n\t$0 image.png\n\n\tWhere 'image.png' is ..."
		exit 0
fi

Easy! The -z means “if empty”. And $1 contains the first parameter. When we display the error message, we use $0, which contains the script’s filename and path. So if I run my script like this “/foo/bar/somescript”, that is what $0 will display.

Ensure Source Image Exists

# Check to is if source file exists
if [[ ! -e $1 ]]; then
		echo -e "\nFile '$1' not found."
		exit 0
fi

I added the comment because I can never remember what “-e” means. When scanning the source, I can quickly tell what this section does without reading much further into the code.

Ask the user before overwriting

# Warn if favicon.ico already exists in current directory
if [[ -e favicon.ico ]]; then
		echo -e "Warning:\n\tfavicon.ico exists in current directory."
		choice "Overwrite? [y/N]: "
		if [ "$CHOICE" = "y" ]; then
				echo "Overwriting favicon.ico"
				rm favicon.ico
		else
				echo "Exiting"
				exit 0
		fi
fi

Another defensive-coding best-practice. Before doing anything destructive that isn’t the primary purpose of the script, let the user decide if they want to proceed. By “primary purpose” I mean that the only reason someone would run the script was to destroy something.

What’s worse is destroying something silently, which is why I let the user know when we overwrite favicon. Keeping the user informed needs to be balanced with flooding the user with too many messages.

Note that the “choice” statement above isn’t a build-in command, but a very handy one I got from O’Reilly’s “bash Cookbook”.

Is Imagemagick installed?

Actually, I don’t care because I use it so much that I always install it on whatever system I’m using. An approach I might take would be

type mogrify

And compare the result.
A good discussion of self-documenting code vs. inline documentation may be found in chapter 11 of: McConnell, Steve (2004). Code Complete (2nd ed.). Pearson Education. ISBN 9780735636972.

Bulk process RAW image files

Recently I had to convert about 250 RAW image files to PNGs. For neatness, I wanted to convert the upper-case filenames the camera assigned with a lower-case name.

A little bash script-fu is all it took:

clean_pics.sh

#!/bin/bash
# Extract from SD Card
for i in /Volumes/SDCARD/DCIM/100ND40X/DSC_0*; do
 filename=$(basename "$i")
 lower_file="$(echo $filename | tr '[A-Z]' '[a-z]')"
 # verify it doesn't already exist
 newfile="${lower_file%.*}.png"
 echo -e "Processingnt$i tont$newfile"
 if [[ -e $newfile ]]; then
  echo "****SKIPPING"
 else
  convert "$i" "$newfile"
 fi
done

echo -e "Detoxing..."
find . -iname "*.png" -exec detox "{}" ;

echo "Procedure complete."

(“SDCARD”, etc is the path to the source files)

Once the script was up and running, it took about 1/2 hour to process all the files. Meanwhile, I was off doing something else!

Path technique: preventing duplicate directories

Forest PathWhat happens with the Path

The path is set via a myriad of config files. It is very easy to accidentally add the same directory to the path, and there is no built-in mechanism from preventing this situation.

While it has no impact on performance, it does make reading the path more difficult (for example, when trying to see if a particular directory is in the path).

Easy Fix

When you find your path cluttered up with duplicate directories, it is relatively easy to correct this. Simply use pathmunge to add directories instead of the typical

export PATH=/fizz/buzz:$PATH

Procedure

First, edit your /etc/bashrc (or equivalent) and add the following function:

pathmunge () {
  if ! [[ $PATH =~ (^|:)$1($|:) ]] ; then
    if [ "$2" = "after" ] ; then
      PATH=$PATH:$1
    else
      PATH=$1:$PATH
    fi
  fi
}

Now you can call this function in your ~/.bashrc, ~/.bash_profile, or wherever you need to add a directory to the path. There are two ways to do this.

Insert at the beginning of the path

Using

pathmunge /path/to/dir

is the functional equivalent of

export PATH=/path/to/dir:$PATH

Append to the end of the path

pathmunge /path/to/dir after

is the functional equivalent of

export PATH=$PATH:/path/to/dir

In either case, the directory won’t be added if it already is in the path, preventing duplicates.

Technique credit: Sam Halicke and Christopher Cashell at Serverfault.

Enhanced by Zemanta

How to prevent duplicate directories in your path

Forest PathWhat happens with the Path

The path is set via a myriad of config files. It is very easy to accidentally add the same directory to the path, and there is no built-in mechanism from preventing this situation.

While it has no impact on performance, it does make reading the path more difficult (for example, when trying to see if a particular directory is in the path).

Easy Fix

When you find your path cluttered up with duplicate directories, it is relatively easy to correct this. Simply use pathmunge to add directories instead of the typical

export PATH=/fizz/buzz:$PATH

Procedure

First, edit your /etc/bashrc (or equivalent) and add the following function:

pathmunge () {
  if ! [[ $PATH =~ (^|:)$1($|:) ]] ; then
    if [ "$2" = "after" ] ; then
      PATH=$PATH:$1
    else
      PATH=$1:$PATH
    fi
  fi
}

Now you can call this function in your ~/.bashrc, ~/.bash_profile, or wherever you need to add a directory to the path. There are two ways to do this.

Insert at the beginning of the path

Using

pathmunge /path/to/dir

is the functional equivalent of

export PATH=/path/to/dir:$PATH

Append to the end of the path

pathmunge /path/to/dir after

is the functional equivalent of

export PATH=$PATH:/path/to/dir

In either case, the directory won’t be added if it already is in the path, preventing duplicates.

Technique credit: Sam Halicke and Christopher Cashell at Serverfault.

Enhanced by Zemanta