The Coding Mant.is

Smashing Through Code

Basics of Shell Scripting — 30-September-2014

Basics of Shell Scripting

A quick overview: What is shell scripting?

Essentially, a shell script is a series of Unix commands. The main benefit of a shell script is that, if you find yourself frequently executing the same commands repeatedly, you can write a shell script and run the commands that way.

All shell scripts should begin with the following line:
#!/bin/bash

This line tells the environment how to interpret the script. Here we have used bash, which indicates the bash shell. Other scripting languages such as awk, perl, and python use this declaration syntax as well.

To run the script, use ./ before the file name – this runs the script if it’s in your current working directory. For example, if your script is called myScript.sh, then you would execute it by running:
./myScript.sh

Getting started

To get started, I’m going to build a BASH script that will copy a file from it’s current directory to another and append the date to its file name.

First, open a new file and add the BASH line indicated above:
#!/bin/bash

Before proceeding, I’m going to make sure my script has execute permissions:
chmod +x myScript.sh

The easiest way for me to proceed is to break up the goal of my program into smaller steps (the “exercises” below) and then use those to earmark my progress.

Exercise 1

Use the shell script to print the current date.

If I am in terminal, I can use date and have it output the date, but if I use echo date then echo will simply print the word “date” and not execute the command. In order to execute the command, I need use the following syntax:
$(<command>)

For example:
> echo $(date)

So I need to update my script to:

#!/bin/bash
echo $(date)

Exercise 2

Update script to print a formatted version of the current date.

Using man date I can see that I need to supply a string argument to the date command to control how I want the date formatted. Since I want to use the numeric representation of the year, month, and day and I want to use 24 hour time, I will need to specify %Y, %m, %d, %H, %M, and %S in the appropriate sections of the string. In this case I want the date to be formatted as YYYY-mm-dd_HHMMSS. At the prompt, I test:
date "+%Y-%m-%d_%H%M%S"

The updated shell script is now:

#!/bin/bash
echo $(date "+%Y-%m-%d_%H%M%S")

Exercise 3

Print an argument (string) to screen.

In order to copy a file from one place to another, I will need to specify the file. In a shell script, the arguments can be referenced using the order they are specified. $0 is used to reference the script being executed, $1 is the first argument, $2 is the second, etc.

So if I update the script to just echo the arguments:

#!/bin/bash
#echo $(date "+%Y-%m-%d_%H%M%S")
echo $0
echo $1
echo $2

And then run it, it will print:

> ./myScript.sh hi.png
./myScript.sh
hi.png

The date line no longer prints because I’ve commented it out, it echoes the script execution for $0, and “hi.png” as $1. A null line is printed for $2 since there was no second argument specified (note that it does not fail).

Since I’m only going to be using the file name and the date, I will update the script to:

#!/bin/bash
echo $(date "+%Y-%m-%d_%H%M%S")
echo $1

Exercise 4

Concat argument string and formatted date

In terminal, if I want to concat two strings and print them, I can do something like:
echo $USER::$PATH

This will print my user name (stored in the $USER environmental variable), two colons, and the contents of my $PATH environmental variable. (Note: exactly what these are is outside of the scope of this entry, but feel free to Google environmental variables in Unix.)

I want to tell BASH to execute the date command and append its output to what will be the file name, $1, after an underscore:

#!/bin/bash
echo $1_$(date "+%Y-%m-%d_%H%M%S")

Which looks like this:

> ./myScript.sh hi
hi_2014-09-30_153741

Exercise 5

Input string must now be a file name. Concat file name, date, and file extension.

This script will encounter a little hiccup, in my opinion, if I use a file name instead of a regular string:

> ./myScript.sh hi.png
hi.png_2014-09-30_153741

Ideally, I would want this to look something like hi_2014-09-30_153741.png: i.e. I want the timestamp appended to the file name specifically, not just the end of the string. To do this I’ll need to separate the file name and extension. While I was searching for Unix commands to do this, I found basename, which returns the whole file name (including extension) after extracting it from a path:

> basename /some/path/hi.png
hi.png

With some more searching, I found information about Shell Parameter Expansion. So if I use a variable, I can do this:

> FILE="example.tar.gz"

> echo "${FILE%%.*}"
example

> echo "${FILE#*.}"
tar.gz

So now all I need to do is update my script:

#!/bin/bash
fileWithoutPath=$(basename $1)
echo ${fileWithoutPath%%.*}\_$(date "+%Y-%m-%d_%H%M%S")\.${fileWithoutPath#*.}

(The “extra” backslashes are to escape the underscore and period.) Although accurate, let’s try to make that a *little* more readable:

#!/bin/bash
formattedDate=$(date "+%Y-%m-%d_%H%M%S")
inputFile=$1
fileWithoutPath=$(basename $inputFile)
filename=${fileWithoutPath%%.*}
extension=${fileWithoutPath#*.}
updatedFilename=$filename\_$formattedDate\.$extension

echo $updatedFilename

Why the “additional” variables? Well, I happen to know that I intend to use flags as a learning exercise below, so it is easy to change $inputFile and point it to the appropriate flag value instead of changing all the instances of $1 later. Similarly, naming the other items allows me to very clearly read what $updatedFilename is. Not a great concern in as short a script as this one, but could come in with something more complicated.

Exercise 6

Copy file with updated name in current directory

For this we just need to use the cp command:

#!/bin/bash
formattedDate=$(date "+%Y-%m-%d_%H%M%S")
inputFile=$1
fileWithoutPath=$(basename $inputFile)
filename=${fileWithoutPath%%.*}
extension=${fileWithoutPath#*.}
updatedFilename=$filename\_$formattedDate\.$extension

echo $updatedFilename

cp $inputFile $updatedFilename

Exercise 7

Copy file to specified directory

The behavior I’ve decided on for this script is to copy the file to the current working directory if none is specified or to use a specified directory. To accomplish this, I’m going to use a simple switch statement (and remove the echo lines):

#!/bin/bash
formattedDate=$(date "+%Y-%m-%d_%H%M%S")
inputFile=$1
outputDir=$2
fileWithoutPath=$(basename $inputFile)
filename=${fileWithoutPath%%.*}
extension=${fileWithoutPath#*.}
updatedFilename=$filename\_$formattedDate\.$extension

if [ -z "$outputDir" ]; then
  cp $inputFile $updatedFilename
else
  cp $inputFile $outputDir/$updatedFilename
fi

Again, since I know I will be using flags I set $2 to the variable $outputDir. The -z flag in the if statement checks if the specified variable has a zero length string.

Exercise 8

Use flags instead of $1, $2, etc.

To keep it simple, I’m going to use single letter flags which will allow me to use getops. I am going to use -f for the input file, -d for the output directory, and -h for “help” (but I’m not going to write the help info yet).

In order to tell OPTARG that -f and -d need arguments I trail them with a colon. The loop goes through the arguments provided and matches them in the case statement. Since I haven’t written any help information, I just have it printing that the option was called:

#!/bin/bash

while getopts "f:d:h" opt; do
  case $opt in
    f) inputFile="$OPTARG"
      ;;
    d) outputDir="$OPTARG"
      ;;
    h)
      echo "User used h!" 
      ;;
  esac

done

formattedDate=$(date "+%Y-%m-%d_%H%M%S")
#inputFile=$1
#outputDir=$2
fileWithoutPath=$(basename $inputFile)
filename=${fileWithoutPath%%.*}
extension=${fileWithoutPath#*.}
updatedFilename=$filename\_$formattedDate\.$extension

if [ -z "$outputDir" ]; then
  cp $inputFile $updatedFilename
else
  cp $inputFile $outputDir/$updatedFilename
fi

Note that I had minimal changes to the logic I already wrote – I commented out (and will delete) the where I set $inputFile and $outputDir to $1 and $2, respectively, and set the variables in the while loop/case statement. I didn’t need to hunt down multiple uses of $1 and $2 and replace them because I had set them to variables. Handy :)

Exercise 9

Create help output

To create the help output, I looked around to see if there is any “canon” way to do this. Looks like one preferred method is to just store the help output to a string and then print that string when the help flag is used. When working out this bit of code, I noticed that the order of the flags matters – so if I have -h last, then the program will not execute in the way that I intend (it will look for an argument for -f, etc.). I also added exit 0 to the -h block. exit 0 means that the program exited without errors.

#!/bin/bash

# Help output
usage="$(basename "$0") [-h] [-f <string> -d <string>] -- script copies file and appends timestamp to file name using YYYYmmdd-HHMMSS format.

where:
        -h show this help text
        -f set the input file
        -d set the output directory, if unset will copy to current working directory"

###

while getopts "f:d:h" opt; do
  case $opt in
    h)
      echo "$usage"
      exit 0
    ;;
    f) inputFile="$OPTARG"
      ;;
    d) outputDir="$OPTARG"
      ;;
  esac

done

formattedDate=$(date "+%Y-%m-%d_%H%M%S")
fileWithoutPath=$(basename $inputFile)
filename=${fileWithoutPath%%.*}
extension=${fileWithoutPath#*.}
updatedFilename=$filename\_$formattedDate\.$extension

if [ -z "$outputDir" ]; then
  cp $inputFile $updatedFilename
else
  cp $inputFile $outputDir/$updatedFilename
fi

So now when I use the -h flag, I see:

> ./myScript.sh -h
myScript.sh [-h] [-f <string> -d <string>] -- script copies file and appends timestamp to file name using YYYYmmdd-HHMMSS format.

where:
	-h show this help text
	-f set the input file
	-d set the output directory, if unset will copy to current working directory

Exercise 10

Some basic error handling

The main ways this script will encounter a problem are if:

  • There is no input file specified
  • An invalid flag is provided
  • A specified input file does not exist
  • A specified output directory does not exist

Since getops goes through each of the options in sequence, before the rest of the program runs I am going to have it check that at least one argument has been provided and exit if there are none:

if [[ $# == 0 ]]; then
  echo "An input file is required."
  echo ''
  echo "$usage"
  exit 1
fi

Note: $# counts the number of arguments provided.

Next, to check that the flags provided are correct, I will add the following to the getops while loop:

    \?)
      echo "Invalid option: please reference help below" >&2
      echo ''
      echo "$usage"
      exit 2

In order to check the input file and output directories, I’m going to use a similar if statement as I did when I used -z to see if the value provided was a non-empty string. To check if the file exists, I will use -f. For example, with the input file:

if [ ! -f "$inputFile" ]; then
  echo "Input file not found!"
  echo ''
  echo "$usage"
  exit 2
fi

Now to put it all together:

#!/bin/bash

# Help output
usage="$(basename "$0") [-h] [-f <string> -d <string>] -- script copies file and appends timestamp to file name using YYYYmmdd-HHMMSS format.

Options:
        -h show this help text
        -f set the input file
        -d set the output directory, if unset will copy to current working directory"

###

if [[ $# == 0 ]]; then
  echo "An input file is required."
  echo ''
  echo "$usage"
  exit 1
fi

while getopts "f:d:h" opt; do
  case $opt in
    h)
      echo "$usage"
      exit 0
      ;;
    f) inputFile="$OPTARG"
      ;;
    d) outputDir="$OPTARG"
      ;;
    \?)
      echo "Invalid option: please reference help below" >&2
      echo ''
      echo "$usage"
      exit 2
      ;;
  esac

done


if [ ! -f "$inputFile" ]; then
  echo "Input file not found!"
  echo ''
  echo "$usage"
  exit 2
fi

formattedDate=$(date "+%Y-%m-%d_%H%M%S")
fileWithoutPath=$(basename $inputFile)
filename=${fileWithoutPath%%.*}
extension=${fileWithoutPath#*.}
updatedFilename=$filename\_$formattedDate\.$extension


if [ -z "$outputDir" ]; then
  cp $inputFile $updatedFilename
else
  if [ ! -f "$ouputDir" ]; then
    echo "Output directory not found!"
    echo ''
    echo "$usage"
    exit 2
  fi
  cp $inputFile $outputDir/$updatedFilename
fi

Success! I now have a shell script that will copy a file to another directory, append the timestamp, and do some basic error checking.

Markdown for quasi-Beginners — 6-August-2014

Markdown for quasi-Beginners

Recently, we wrote all of our slides in Markdown and used mdpress at my new job (see post here), which was my official dive into Markdown. Prior to that, my exposure had been pretty limited. Since Markdown is commonly used, I’m going to do a quick run through.

The What and Why

Markdown is both a syntax and a tool. A Markdown file, which has the extension .md, is written in Markdown syntax so the Markdown tool can convert it to HTML. The idea behind Markdown is that it’s easier and faster than writing HTML and the source file (*.md) is more human-readable than an HTML file. Personally, I tend to only run into Markdown when looking through code on GitHub – specifically the Readme and related documentation files that programmers include with their applications.

Originally written in 2004, Markdown has been gaining a smattering of followers who use it for more than just writing plain text documents. As mentioned, I recently also encountered it as a tool for writing presentation slides, but a quick search on the net will yield other applictions as well, including various blog platforms, simple to-do lists, full fledged websites, or even resumés. (Note: I haven’t used most/all of these tools, I am simply using them as examples after a quick Google search.)

Why should I care?

You may be looking at all that and thinking “HTML isn’t that hard to read… I don’t know if I really care about all this”. As with many “on the job” tools, your initial exposure (and, in fact, perhaps the only reason you’re reading this post) may only be because you found yourself in need of a quick understanding and how-to for a project at work or school. Don’t feel left out! That’s how I got here :)

The “Meat and Potatoes”

Markdown syntax is very, very easy. In fact so easy that you might not even feel comfortable thinking of it as “syntax”. To help myself out, I only needed to remind myself of three rules:

  • All roads lead to Rome
  • There are multiple flavors of Markdown
  • If all else fails, use HTML

All roads lead to Rome

There is always more than one way to do something and the same is true of Markdown. To get you used to this idea, let’s take a look at how to make a header in Markdown (HTML tag <h1>). You can use either of the following:

#H1 Heading

H1 Heading
==========

There are also multiple ways to make bold, italic, and bold+italic text:

*Italics*
_Also Italics_

**Bold**
__Still Bold__

***Bold and Italics***
**_You get the idea..._**

Since there are a plethora of tutorials available on the web, rather than rehash everything here I’m providing some links at the bottom of the post. You may notice that not all tutorials cover all variations of the syntax. Don’t be confused! Part of this is brevity, but another part brings me to my second point:

There are multiple flavors of Markdown

Although “the basics” of Markdown are standard, there are some variations that provide additional functionality. One example I make frequent use of is syntax highlighting. In Markdown, you can indicate code using either of the following:

`This` word is code formatted in line.

```
This would be a block of code,
two lines.
```

Now let’s say you want to document a bit of Javascript, Ruby, or even bash in your Markdown file. You can use the above, but for added readability you can also make use of the syntax highlighting feature if your flavor of Markdown supports it. When available, you would specify the language of interest after the first set of backticks:

```bash
$ mkdir -p workspace/apps
$ cd workspace/apps
$ git clone https://github.com/cloudfoundry-community/cf-env.git
Cloning into 'cf-env'...
remote: Reusing existing pack: 71, done.
remote: Total 71 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (71/71), done.
```

This feature is especially helpful when posting long output. Again for brevity, I’m not going to rehash all the possible specialized syntax here, but the Wikipedia article does list the most common flavors. If you are using a specific flavor of Markdown, make sure to check what additional features they offer (if any). If syntax highlighting is available, make sure you also check which languages the flavor supports.

If all else fails, use HTML

I was so relieved/happy when I discovered this. You may have something that you aren’t quickly finding the syntax for or that isn’t explicitly available in Markdown. Never fear! Since Markdown converts to HTML, HTML code is left as-is. As a quick example, let me show you how to make a section of blockquoted text. Now, there is valid Markdown syntax for that:

>This is a blockquote.

But let’s say you can’t find the syntax for some reason and would just like to put in the text and move on. You can still use:

<blockquote>This is a blockquote.</blockquote>

You can also mix HTML tags and Markdown syntax. For example, if you wanted to force where the line break occurred in your blockquote, rather than let it wrap around, you could do this:

>This is a blockquote, <br />
with a second line.

One final note

Although I usually prefer command line text editors, when I am working on Markdown files I usually use Atom (available for Mac and Windows). The main reason is because it has a built-in preview pane that allows you to see how your file will render while you edit it. As an added bonus, there are also many Markdown packages that can be added to Atom to make the job even easier.

Resources you may find helpful

What and why of Markdown:

Some Markdown Tutorials

This article has also been posted on my work blog, which uses one of the blog platforms that utilizes Markdown. ツ

How to Make a (Simple) Frequency Histogram in Google Spreadsheets — 28-July-2014

How to Make a (Simple) Frequency Histogram in Google Spreadsheets

Hello everyone! I was on a brief hiatus last week traveling for work in the Chicago area. It was a lot of fun! But now, back to work.

I thought it would be nice to do a quick “How To” for making a frequency histogram with Google Spreadsheets, since I made one recently.

First, a quick look at the data:
HistogramData

In the first column I have the respondents’ ID numbers and in the subsequent columns the first row is the question number and the value for each response.

In order to make a frequency histogram, I first need to tabulate the frequency data like so:
HistogramFrequencies

In order to obtain the frequency of each value, I use Google Spreadsheet’s “countif” function. The syntax looks like this:

=countif(<range>, <condition>)

For cell B20, I use the following:

=countif(B$2:B$16, "="&$A$20)

B$2:B$16 is the data range for the results for Q1 and “=”&$A$20 is interpreted as “=1” (the value in cell A20 in concatenated with the string “=”). I initially input this as $A20 so that when I clicked-and-dragged down, each result was matched with the appropriate value (i.e. “=1”, “=2”, etc.) without needing any additional edits. I then changed $A20 to $A$20 (and $A21 to $A$21, etc.) so I could click-and-drag across, populating the rest of the table. A couple examples:
HistogramFormula1

HistogramFormula2

HistogramFormula3

Note that all columns need to reference column A for the compare value, as seen in the third image.

To make the actual chart/graph, I go to Insert -> Chart:
HistogramChart1

I click the table–esque looking icon to select the range(s):
HistogramChart2

HistogramChart3

Then, I select the chart type (highlighted in blue) and select “use row 19 as headers”, otherwise no identifying information will display in the legend for each item. (Google Drive will automatically populate this with the first row number in the range of data you have selected.)

HistogramChart4

My final chart, after playing around with the names/etc. in the Customize tab, looks like this:
HistogramChart

Note: You can re-open these options at any time by right-clicking on the chart and choosing “Advanced Edit”. There are also a few “quick edit” options available in the menu:
HIstogramRightClick

Happy chart-making!

Design a site like this with WordPress.com
Get started