Scripts: Just a List of Commands (For Starters)

In its most basic form, a Bash script is simply a series of commands stored in a file. This might seem trivial at first, but it saves you the effort of retyping the same commands repeatedly.

Example:

Bash

#!/bin/bash
# Cleanup log files (requires root privileges)

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Log files cleaned up."

This script, when executed, will navigate to the /var/log directory and clear the contents of the messages and wtmp log files.

Why Bother with Scripts?

You might be thinking, “Why not just type these commands directly?” Well, scripts offer several advantages:

  • Efficiency: No more retyping long sequences of commands.
  • Re-usability: Easily reuse and share your scripts.
  • Maintainability: Modify and update your scripts as needed.
  • Automation: Schedule scripts to run automatically (e.g., with cron).
  • Flexibility: Customize scripts for specific tasks.

Adding a Little Intelligence

Let’s enhance our cleanup script a bit:

Bash

#!/bin/bash
# Cleanup, version 2

# Run as root, of course.
# TODO: Add code here to print error message and exit if not root.

LOG_DIR=/var/log
# Variables are better than hard-coded values.
cd "$LOG_DIR" # Quoting variables prevents issues with spaces.

cat /dev/null > messages
cat /dev/null > wtmp

echo "Logs cleaned up."

exit 0 # Explicitly exit with success code.

We’ve added a few things:

  • Shebang: The #!/bin/bash line tells the system to use the Bash interpreter.
  • Comments: Lines starting with # are comments to help explain the code.
  • Variables: We’ve used a variable LOG_DIR to store the directory path.
  • Exit Command: The exit 0 command ensures a clean exit from the script, with a success code.
  • Quoting variables: Added quotes around the $LOG_DIR variable to prevent issues if there are spaces in the path.
  • TODO Comment: added a comment to remind the reader to add root checking code.

Making it More Robust

Let’s take it a step further:

Bash

#!/bin/bash
# Cleanup, version 3

LOG_DIR=/var/log
ROOT_UID=0     # Only users with $UID 0 have root privileges.
LINES=50       # Default number of lines saved.
E_XCD=86       # Can't change directory?
E_NOTROOT=87   # Non-root exit error.

# Run as root, of course.
if [ "$UID" -ne "$ROOT_UID" ]; then
  echo "Must be root to run this script."
  exit "$E_NOTROOT"
fi

if [ -n "$1" ]; then
  LINES="$1"
fi

cd "$LOG_DIR" || {
  echo "Can't change to $LOG_DIR." >&2
  exit "$E_XCD"
}

tail -n "$LINES" messages > mesg.temp # Save last section of message log file.
mv mesg.temp messages               # Rename it as system log file.

cat /dev/null > wtmp
echo "Log files cleaned up."

exit 0

In this version:

  • We’ve added error checking to ensure the script runs as the root user.
  • We’ve introduced the concept of command-line arguments ($1).
  • We’re now saving the last 50 lines of the messages log file instead of wiping it completely.
  • We replaced the pwd check with the more efficient cd || {} construct.
  • We’ve added quotes around all variables within the conditional statements.

The Shebang (#!)

The #! at the beginning of a script is crucial. It tells the system which interpreter to use to execute the script. Common shebangs include:

  • #!/bin/bash (Bash shell)
  • #!/bin/sh (Default Bourne shell, often a link to Bash)
  • #!/usr/bin/perl (Perl interpreter)
  • #!/usr/bin/python3 (Python interpreter)

Make sure the path to the interpreter is correct!

Building a Scripting Toolkit

As you write more scripts, you’ll start to accumulate reusable code snippets. These can be functions, error-handling routines, or other useful bits of logic. Organize these into a personal “scripting toolkit” to save time and effort in future projects.

For example, here’s a script prolog to check for the correct number of command-line arguments:

Bash

#!/bin/bash

E_WRONG_ARGS=85
script_parameters="-a -h -m -z"
#                  -a = all, -h = help, etc.

if [ "$#" -ne "$Number_of_expected_args" ]; then
  echo "Usage: $(basename "$0") $script_parameters"
  # $(basename "$0") is the script's filename.
  exit "$E_WRONG_ARGS"
fi

Invoking the script

Having written the script, you can invoke it by bash scriptname. Much more convenient is to make the script itself directly executable with a chmod. Either:

  • chmod +x scriptname (gives everyone execute permission) or
  • chmod u+x scriptname (gives only the script owner execute permission)

Having made the script executable, you may now test it by ./scriptname. If it begins with a “shebang” line, invoking the script calls the correct command interpreter to run it.

As a final step, after testing and debugging, you would likely want to move it to /usr/local/bin (as root, of course), to make the script available to yourself and all other users as a systemwide executable. The script could then be invoked by simply typing scriptname [ENTER] from the command-line.

Notes

  • Why not simply invoke the script with scriptname? If the directory you are in ($PWD) is where scriptname is located, why doesn’t this work? This fails because, for security reasons, the current directory (./) is not by default included in a user’s $PATH. It is therefore necessary to explicitly invoke the script in the current directory with ./scriptname.

From Specific to General

Many times, you’ll write a script for a particular task. Later, you might want to generalize it. Replacing hardcoded values with variables and using functions can make your scripts more flexible and reusable.

This post has given you a taste of basic Bash scripting. In the coming chapters, we’ll delve deeper into variables, control flow, loops, functions, and more. Get ready to level up your scripting skills!

No responses yet

Leave a Reply