[Tutorial] Shell scripting by way of an illustrated practical example

Difficulty: ★★☆☆☆


Notes:

  • The script referenced in this post works under any recent version of GNU Bash, but special care was taken not to use any so-called “bashisms” ─ i.e. the use of certain functions, commands and expressions which only exist in GNU Bash but may not exist or may require a different syntax in other types of shells. Therefore, the script should work in any POSIX- and Bourne-compatible shell.

  • This post is meant as a quick beginner’s tutorial in shell scripting. The shell script in this post may still contain bugs, and while it is perfectly possible to extend the functionality of this script so that it does more than simply list movies, that was not my intent when I wrote it. I mainly wrote this script for educational purposes.

  • The script is intended to be self-explanatory because of its inline comments. However, should you have any questions with regard to a certain section of the script, then by all means, feel free to post your question here on the thread. (Please don’t send me any PMs about it, because that would completely sidestep the intent of this tutorial.)


As the introduction says, this script is primarily aimed at beginners and people with a mild knowledge of shell scripting. As for whether the functionality of the script is useful in and of itself, this is entirely up to you. :wink:

Now, in order to better understand what this script does or how it works, I should put up front that I approach GNU/Linux as a multiuser operating system, and that therefore, certain data on my system is located in directories which are readable by every user account. Specifically, I am talking of the multimedia content on my computer, which I keep under the world-readable but root-owned and only root-writable /srv/mmedia directory.

The script below focuses on only one category of multimedia, namely motion pictures, although one of the files in that category is actually a complete miniseries in a single video. In good UNIX tradition, these video files don’t have any spaces in their names, but instead they have underscores. However, as the script is intended to list movie titles instead of simple filenames, the script will translate the underscores into spaces for display, and will also omit the filename suffix that tells you what video format the file is in.

As I said higher up already, this script was primarily written for educational purposes ─ I have posted it in a couple of GNU/Linux-specific Usenet discussion groups in the past ─ so please don’t take its limited functionality too seriously. :wink:

One last tip: If you’re going to save this script on your own computer and you want to be able to readily invoke it as a command in a terminal window, then make sure that the file has execute permission, and that it resides in ~/.local/bin, which in Manjaro is part of your ${PATH}.


THE SCRIPT

#!/bin/sh
#
# 
# This script will list the videos in /srv/mmedia/video/movies in a
# human-readable form, i.e. with the underscores in the filenames 
# replaced by spaces, and with the filename suffix removed.
#
# I intend to later on expand the usability of this script.
#
# Author    : Aragorn (xxxxxxxxx@xxxxxxx.xx)
#
# Release   : 3.1.1
#
# Changelog : 
#
#  1.0    (2014.02.23) (crude version, works on .flv only)
#  1.1    (2014.05.18) (crude version, works on .flv and .avi)
#  2.0    (2014.12.28) (suffix-independent and adds a counter)
#  2.1    (2014.12.29) (improved screen output and performance)
#  3.0    (2014.12.29) (added keyword search option and help)
#  3.0.1  (2014.12.30) (added and improved inline documentation)
#  3.0.2  (2014.12.30) (omit separator when no matching files found)
#  3.1    (2014.12.30) (colorize search string in results)
#  3.1.1  (2015.12.07) (minor exit code bugfix)
#
# License   : GNU General Public License, version 3.0 or later
#
####################################################################
#
#
# Where are the movies?  (Change this value according to where you
# store the movies on your own system.)

moviedir="/srv/mmedia/video/movies"

# Let's store the current working directory in a variable. Where
# are we?

oldpwd=${PWD}

# Create a synopsis page in case we got called incorrectly.

synopsis()
{
    echo
    echo '   SYNOPSIS'
    echo 
    echo '    ' $(basename $0) '[OPTION [KEYWORD]]'
    echo
    echo '     Lists the movies available to every user on this'
    echo '     machine.'
    echo
    echo 
    echo '   OPTIONS'
    echo
    echo '     -h, --help'
    echo '          Displays this syntax message.'
    echo 
    echo '     -w KEYWORD, --word KEYWORD'
    echo '          Displays the number of movies with KEYWORD in'
    echo '          their title.  The KEYWORD search is not case-'
    echo '          sensitive.'
    echo
    echo '     If no options were provided, the default behavior is'
    echo '     to list all available movies.'
    echo
    echo
    echo '   EXAMPLES'
    echo
    echo '    ' $(basename $0)
    echo '    ' $(basename $0) '-w predator'
    echo '    ' $(basename $0) '--word predator'
    echo
    echo
    echo '   LICENSE'
    echo
    echo '     This program is a POSIX-compatible shell script and is'
    echo '     licensed under the GNU General Public License, version'
    echo '     3.0 or later.'
    echo
}

# Create a basic syntax error message.

error_syntax()
{
    echo
    echo '   Incorrect usage.'
    synopsis
    exit "${E_SYNTAX}"
}

# Create a nice separator line to separate the movie list from
# the counter.

line='   ───────────────────────────────────────────────────────────'

# Define the exit codes

SUCCESS=0
E_SYNTAX=2

# Change the working directory to where the movies are.

cd ${moviedir}

# Now let's create a function to list all of the movie titles
# available on this system.

listmovies()
{
  {

    # First we'll create a counter and set it to zero.

    fullcount=0

    # Make a loop to strip the filename suffixes and increment 
    # the counter.

    for video in *
    do
      movie=$(basename "${video%.*}")
      echo '  ' ${movie}
      fullcount=$((${fullcount}+1))
    done

    # Now that we've displayed the list of available movies,
    # let's draw a line and display the counter.

    echo
    echo "${line}"
    echo
    echo '  ' ${fullcount} 'movie(s) found'
    echo

    # And now we take that output and pipe it through to "tr"
    # in order to translate the underscores into spaces.  After
    # all, we are not listing files, we are listing movie titles. :-)

  } | tr '_' ' '
}

# Check whether we were invoked correctly.  Did the user supply
# a command line option?  If not, we'll run the function to list
# all movies, and we exit the script.  If on the other hand, there
# was a command line option passed to the script, we'll parse that
# option and act accordingly.

if [ -z $1 ]                 # If there are no command line
then                         # parameters, then...
  echo                       # ... print a blank line;
  listmovies                 # list all movies, and
  exit ${SUCCESS}            # exit the script.
else
  case "$1" in               # If the first parameter...

    "-h" | "--help" )        # ... matches the help function, then
      synopsis               # run the synopsis function; and
      exit ${SUCCESS}        # exit the script.
      ;;

    "-w" | "--word" )        # ... matches the search function, but...
      if [ -z $2 ]           # the user omitted the keyword, then
      then                   # we run the syntax error function, which
        error_syntax         # will exit the script with error code 2.
      fi
      ;;

    *               )        # ... is an unsupported option, then
      error_syntax           # we run the syntax error function and
      ;;                     # exit the script.

  esac
fi

# If we've come this far, then that means that the user did not 
# want the full list of movies, and has correctly passed a keyword
# search to the script.

# Now we create a counter for the number of movies matching the search
# pattern, and we set its value to zero.

searchcount="0"

# Print an empty line.

echo

# Let's do the case-insensitive pattern search with "grep"...

listmovies | grep -i --color "$2"

# ... and then count the resulting matches.

searchcount=$(ls -1 | grep -c -i "$2")

# And now we print the line and the counter for the matching movies,
# unless the search yields no results, in which case we skip printing
# the separator line.

if [ ${searchcount} -ge 1 ]
then
  echo
  echo "${line}"
  echo
fi
echo '  ' ${searchcount} 'matching movie(s) found'
echo


# Now we return to where we were when we invoked this script, just in
# case this script were to be converted into a shell function for 
# inclusion in one's ~/.bashrc, ~/.bash_profile, ~/.profile, or any
# other POSIX-compatible shell initialization file.  It is however not
# necessary to return to the original working directory if this script
# is run as a standalone executable, because then it'll be run in a 
# subshell.

cd ${oldpwd}

# And we exit cleanly. :-)

exit ${SUCCESS}

# That's all folks!

USAGE EXAMPLES

[nx-74205:/dev/pts/3][/home/aragorn]
[06:29:31][aragorn] > movies --help

   SYNOPSIS

     movies [OPTION [KEYWORD]]

     Lists the movies available to every user on this
     machine.


   OPTIONS

     -h, --help
          Displays this syntax message.

     -w KEYWORD, --word KEYWORD
          Displays the number of movies with KEYWORD in
          their title.  The KEYWORD search is not case-
          sensitive.

     If no options were provided, the default behavior is
     to list all available movies.


   EXAMPLES

     movies
     movies -w predator
     movies --word predator


   LICENSE

     This program is a POSIX-compatible shell script and is
     licensed under the GNU General Public License, version
     3.0 or later.

[nx-74205:/dev/pts/3][/home/aragorn]
[06:29:38][aragorn] > movies --word Predator

   Aliens vs. Predator: Requiem
   AVP: Alien vs. Predator
   Predator 2
   Predator
   Predators

   ───────────────────────────────────────────────────────────

   5 matching movie(s) found


[nx-74205:/dev/pts/3][/home/aragorn]
[06:30:27][aragorn] > movies -w Ring

   The Lord Of The Rings (1): The Fellowship Of The Ring
   The Lord Of The Rings (2): The Two Towers
   The Lord Of The Rings (3): The Return Of The King

   ───────────────────────────────────────────────────────────

   3 matching movie(s) found


[nx-74205:/dev/pts/3][/home/aragorn]
[06:30:55][aragorn] >

Have fun! :slight_smile:

17 Likes