Processing Script
Inputs

Process script inputs ($1, $2, etc.)

CIS126RH | RHEL System Administration 1
Mesa Community College

A script that works only on hardcoded values is limited — it must be edited every time the target changes. Scripts that accept arguments from the command line are reusable: the same script creates any user, backs up any directory, or restarts any service. This module covers how bash passes arguments to scripts, how to access and validate them, how to shift through them, and how to handle variable numbers of inputs. These skills are tested on the RHCSA exam.

Learning Objectives

  1. Access positional parameters — Use $1, $2, $0, $#, $@, and $* to read arguments passed to a script
  2. Validate arguments with guard clauses — Check that required arguments are present and have valid values before the script does any work
  3. Process multiple arguments with loops and shift — Iterate over all arguments with for "$@" and consume them one at a time with the shift command
  4. Use default values and parameter expansion — Apply bash parameter expansion to provide defaults, require values, and handle missing or empty arguments safely

How Arguments Reach a Script

When you run a script, bash assigns each word on the command line to a numbered positional parameter.

# Running the script
$ ./create-user.sh maria developers sudo

# Inside the script, the arguments are available as:
$0  = ./create-user.sh   # the script name itself
$1  = maria              # first argument
$2  = developers        # second argument
$3  = sudo              # third argument
$#  = 3                  # total argument count
$@  = "maria" "developers" "sudo"  # all args, each quoted
$*  = "maria developers sudo"       # all args, one string
Arguments beyond $9

Parameters beyond position 9 require curly braces: ${10}, ${11}, and so on. Without braces, $10 is interpreted as ${1}0 — the value of $1 with a literal 0 appended.

Using Positional Parameters

#!/bin/bash
# Usage: ./greet.sh FIRSTNAME LASTNAME

echo "Script name: $0"
echo "Arguments received: $#"
echo "First name:  $1"
echo "Last name:   $2"
echo "Full name:   $1 $2"
echo "All args:    $@"
# Running the script
$ ./greet.sh Alice Smith
Script name: ./greet.sh
Arguments received: 2
First name:  Alice
Last name:   Smith
Full name:   Alice Smith
All args:    Alice Smith
Always assign $1, $2 to named variables

At the top of a script, copy positional parameters into named variables: USERNAME="$1", GROUPNAME="$2". This makes the rest of the script self-documenting and reduces errors when positional references shift due to argument processing.

Argument Validation: Guard Clauses

Check that the required arguments are present before the script does any work. Fail fast with a clear message rather than failing partway through.

#!/bin/bash
# Usage: ./setup-user.sh USERNAME GROUPNAME

# Guard: require exactly two arguments
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 USERNAME GROUPNAME" >&2
    exit 1
fi

USERNAME="$1"
GROUPNAME="$2"

# Guard: require at least one argument
if [ "$#" -lt 1 ]; then
    echo "Error: at least one argument required" >&2
    exit 1
fi

# Guard: check a specific argument is non-empty
if [ -z "$1" ]; then
    echo "Error: USERNAME cannot be empty" >&2
    exit 1
fi
Redirect error messages to stderr

Usage and error messages should go to stderr with >&2 so they appear on screen even when stdout is redirected to a file or another command.

$@ vs $*

Both expand to all positional parameters, but they behave differently when quoted.

Variable Unquoted Quoted
$@ Expands to all parameters, split on whitespace "$@" — each parameter is a separate quoted word, spaces preserved
$* Expands to all parameters, split on whitespace "$*" — all parameters joined into one string with IFS separator
#!/bin/bash
# Called as: ./demo.sh "Alice Smith" Bob

# "$@" — two separate arguments preserved
for ARG in "$@"; do
    echo "Arg: $ARG"
done
Arg: Alice Smith
Arg: Bob

# "$*" — all joined into one string
for ARG in "$*"; do
    echo "Arg: $ARG"
done
Arg: Alice Smith Bob
Always use "$@"

Use "$@" whenever you need to pass or iterate over all arguments. It is the only form that correctly preserves arguments containing spaces. "$*" is almost never the right choice.

The shift Command

shift moves all positional parameters down by one position. $2 becomes $1, $3 becomes $2, and the original $1 is discarded. $# decrements by one.

#!/bin/bash
# Process arguments one at a time with shift
echo "Arguments: $#"
while [ "$#" -gt 0 ]; do
    echo "Processing: $1"
    shift
done
echo "Arguments remaining: $#"
# Running: ./process.sh alpha beta gamma
Arguments: 3
Processing: alpha
Processing: beta
Processing: gamma
Arguments remaining: 0

# shift N shifts multiple positions at once
FIRST="$1"
shift 2   # skip the first two args — $3 is now $1
shift is essential for option parsing

When a script processes flags like -u username -g group, shift advances past the flag and its value together, letting the loop always examine $1 for the next flag.

Parsing Options with shift

The shift and case combination is the standard manual option-parsing pattern for bash scripts.

#!/bin/bash
# Usage: ./adduser.sh -u USERNAME -g GROUP [-s SHELL]

USERNAME=""
GROUP=""
SHELL="/bin/bash"   # default value

while [ "$#" -gt 0 ]; do
    case "$1" in
        -u) USERNAME="$2"; shift 2 ;;
        -g) GROUP="$2";    shift 2 ;;
        -s) SHELL="$2";    shift 2 ;;
        *)  echo "Unknown option: $1" >&2; exit 1 ;;
    esac
done

if [ -z "$USERNAME" ]; then
    echo "Error: -u USERNAME is required" >&2; exit 1
fi

echo "Creating $USERNAME in $GROUP with shell $SHELL"

The case Statement

case matches a value against multiple patterns and executes the first matching block — cleaner than a chain of elif comparisons.

# Syntax
case "$VARIABLE" in
    pattern1) commands ;;
    pattern2) commands ;;
    pattern3|pattern4) commands ;;  # OR with |
    *)        commands ;;           # default/fallback
esac

# Example: validate the first argument
case "$1" in
    start)   systemctl start   httpd ;;
    stop)    systemctl stop    httpd ;;
    restart) systemctl restart httpd ;;
    status)  systemctl status  httpd ;;
    *)
        echo "Usage: $0 start|stop|restart|status" >&2
        exit 1
        ;;
esac
case pattern matching

Patterns in case use shell glob syntax: * matches anything, ? matches one character, [abc] matches a character class. Use | to match multiple patterns in one arm.

Parameter Expansion: Default Values

Bash parameter expansion lets you provide default values and handle missing arguments inline — without writing a full if/then/else block.

Syntax Meaning
${VAR:-default}Use default if VAR is unset or empty
${VAR:=default}Use default AND assign it to VAR if unset or empty
${VAR:?message}Print message and exit if VAR is unset or empty
${VAR:+value}Use value only if VAR is set and non-empty
# Use /tmp as default if $1 was not provided
DEST="${1:-/tmp}"
echo "Destination: $DEST"

# Require $1 — exit with message if missing
USERNAME="${1:?Usage: $0 USERNAME}"

# Assign default to the variable itself
: "${LOGDIR:=/var/log/myscript}"
echo "Logging to: $LOGDIR"

Parameter Expansion: String Operations

Parameter expansion can also modify string values — extract substrings, trim prefixes and suffixes, get the length, and more.

Syntax Meaning Example
${#VAR}Length of the string${#USERNAME}5 for "maria"
${VAR#pattern}Remove shortest match from front${FILE#*/} strips leading path
${VAR##pattern}Remove longest match from front${FILE##*/} → basename only
${VAR%pattern}Remove shortest match from back${FILE%.conf} strips .conf
${VAR%%pattern}Remove longest match from back${FILE%%.*} strips all extensions
${VAR/old/new}Replace first occurrence${MSG/error/ERROR}
${VAR//old/new}Replace all occurrences${MSG// /_} replaces spaces with underscores
# Get the basename without dirname
FILE="/etc/ssh/sshd_config"
echo "${FILE##*/}"     # sshd_config
echo "${FILE%/*}"      # /etc/ssh

getopts: Standard Option Parsing

getopts is a bash built-in that parses POSIX-style short options (-u, -v, etc.) correctly, handling combined flags and error reporting automatically.

#!/bin/bash
# Usage: ./adduser.sh [-u USER] [-g GROUP] [-v]

VERBOSE=0
USERNAME=""
GROUP=""

while getopts ":u:g:v" OPT; do
    case "$OPT" in
        u) USERNAME="$OPTARG" ;;
        g) GROUP="$OPTARG"    ;;
        v) VERBOSE=1          ;;
        :) echo "Option -$OPTARG requires an argument" >&2; exit 1 ;;
        ?) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
    esac
done
shift $(( OPTIND - 1 ))   # move past parsed options to remaining args
getopts optstring syntax

In the optstring ":u:g:v": a leading : enables silent error handling; a : after a letter means that option requires an argument; a letter alone means the option is a flag with no argument. $OPTARG holds the value for options that require an argument.

The read Command: Interactive Input

read pauses the script and waits for the user to type input. It is used when a script needs information that is not known at invocation time — a password, a confirmation, or a choice.

# Prompt for input and store in a variable
read -p "Enter username: " USERNAME
echo "Creating user: $USERNAME"

# Read a password without echoing it to the screen
read -sp "Enter password: " PASSWORD
echo   # print a newline after hidden input

# Time out after 10 seconds if no input
read -t 10 -p "Continue? [y/N] " ANSWER
if [[ "$ANSWER" != [yY] ]]; then
    echo "Aborted"; exit 0
fi

# Read with a default value
read -p "Destination [/tmp]: " DEST
DEST="${DEST:-/tmp}"

A Complete Script Example

#!/bin/bash
# backup-dir.sh — back up a directory to an archive
# Usage: ./backup-dir.sh SOURCE [DESTINATION]

# Guard: require at least one argument
if [ "$#" -lt 1 ]; then
    echo "Usage: $0 SOURCE [DESTINATION]" >&2
    exit 1
fi

SOURCE="$1"
DEST="${2:-/tmp}"                     # default destination is /tmp
BASENAME="${SOURCE##*/}"              # strip path, keep dir name
TIMESTAMP=$(date +%F)               # YYYY-MM-DD
ARCHIVE="${DEST}/${BASENAME}-${TIMESTAMP}.tar.gz"

# Guard: check the source exists
if [ ! -d "$SOURCE" ]; then
    echo "Error: $SOURCE is not a directory" >&2
    exit 1
fi

tar -czvf "$ARCHIVE" "$SOURCE" && echo "Archived to: $ARCHIVE"

Special Variable Quick Reference

Variable Meaning Example value for: ./script.sh alpha beta
$0Script name as invoked./script.sh
$1First positional argumentalpha
$2Second positional argumentbeta
${10}Tenth argument (braces required)(depends on call)
$#Count of arguments (not including $0)2
$@All arguments as separate quoted wordsalpha beta
$*All arguments joined as one stringalpha beta
$?Exit code of the last command0 or non-zero
$$PID of the current script12345
$!PID of the last background command12346
$_Last argument of the previous commandbeta

Common Mistakes

Mistake What goes wrong Correct form
Accessing $10 without braces Interpreted as ${1}0 — wrong value Use ${10} for arguments beyond $9
Using "$*" in a for loop Arguments with spaces are merged — loop processes too few items Use "$@" to preserve each argument as a separate word
Not quoting "$1" in tests or commands Arguments with spaces cause word splitting or test errors Always quote: [ -f "$1" ], cp "$1" /tmp/
No argument count check Script silently uses empty variables and produces confusing errors Add a [ "$#" -lt N ] guard at the top
Writing error messages to stdout Error messages pollute the output when stdout is redirected Redirect errors to stderr: echo "Error" >&2
Modifying $1 directly Cannot reassign positional parameters with $1=value Copy to a named variable first: NAME="$1", then modify $NAME

Knowledge Check

Answer these before moving to the next slide.

  1. A script is called as ./setup.sh alice developers. What are the values of $0, $1, $2, and $#?
  2. Write the guard clause that exits with a usage message if the script receives fewer than two arguments.
  3. What is the difference between "$@" and "$*"? Which should you use in a for loop and why?
  4. A script currently has $1=servera, $2=serverb, $3=serverc. After running shift, what are the values of $1 and $#?
  5. Write the parameter expansion that uses /tmp as the value of DEST if $1 was not provided.
  6. Write a script section that prints the basename of the file path stored in $1 without using the basename command.

Knowledge Check — Answers

  1. $0 = ./setup.sh (the script name)
    $1 = alice
    $2 = developers
    $# = 2 (count of arguments, not including $0)
  2. if [ "$#" -lt 2 ]; then
        echo "Usage: $0 USERNAME GROUPNAME" >&2
        exit 1
    fi
  3. "$@" expands each argument as a separate quoted word, preserving spaces within arguments. "$*" joins all arguments into one string. Always use "$@" in a for loop so that arguments containing spaces are each treated as one item rather than being split.
  4. After shift: $1 = serverb (old $2), $# = 2 (decremented by 1 from 3).
  5. DEST="${1:-/tmp}" — if $1 is unset or empty, DEST is assigned /tmp; otherwise it takes the value of $1.
  6. BASENAME="${1##*/}"
    echo "$BASENAME"
    ${1##*/} removes the longest match of */ from the front of $1, leaving only the filename component.

Key Takeaways

  1. Positional parameters: $0 is the script name; $1$9 (and ${10}+) are the arguments; $# is the count. Assign $1, $2 to named variables at the top of the script for readability. Always quote: "$1".
  2. Validate arguments before doing any work. Check $# against the required count. Use -z "$1" to check for empty values. Print error messages to stderr with >&2 and exit 1.
  3. Use "$@" — not "$*" — when passing or iterating arguments. "$@" preserves each argument as a separate word with spaces intact. Use shift to consume arguments one at a time in a while loop.
  4. Parameter expansion provides defaults and string operations without extra commands. ${VAR:-default} supplies a default; ${VAR##*/} extracts a basename; ${VAR%.conf} strips an extension — all in pure bash with no subprocess.

Graded Lab

  • Write a script that requires exactly one argument (a username), exits with a usage message if missing, and prints the message "User: $USERNAME" using a named variable.
  • Extend the script to accept an optional second argument (a group name) with a default of users using parameter expansion.
  • Write a script that accepts any number of service names as arguments and prints whether each service is active or inactive. Test with ./check-services.sh sshd httpd firewalld.
  • Write a script that uses a while loop and shift to process each argument and print it with its position number (1: alpha, 2: beta, etc.).
  • Write a script that accepts a file path as $1 and uses parameter expansion to print the basename, the directory, and the filename without its extension — without using basename, dirname, or any external command.
  • Run bash -n scriptname.sh on each script before testing it.
RHCSA Objective

"Process script inputs ($1, $2, etc.)" Every script task on the exam accepts arguments. Argument validation, named variable assignment, and "$@" in loops are the three patterns that appear most frequently.