RED HAT ENTERPRISE LINUX
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
-
Access positional parameters —
Use
$1,$2,$0,$#,$@, and$*to read arguments passed to a script - Validate arguments with guard clauses — Check that required arguments are present and have valid values before the script does any work
-
Process multiple arguments with loops and shift —
Iterate over all arguments with
for "$@"and consume them one at a time with theshiftcommand - 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
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
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
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
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
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
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
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 |
|---|---|---|
$0 | Script name as invoked | ./script.sh |
$1 | First positional argument | alpha |
$2 | Second positional argument | beta |
${10} | Tenth argument (braces required) | (depends on call) |
$# | Count of arguments (not including $0) | 2 |
$@ | All arguments as separate quoted words | alpha beta |
$* | All arguments joined as one string | alpha beta |
$? | Exit code of the last command | 0 or non-zero |
$$ | PID of the current script | 12345 |
$! | PID of the last background command | 12346 |
$_ | Last argument of the previous command | beta |
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.
- A script is called as
./setup.sh alice developers. What are the values of$0,$1,$2, and$#? - Write the guard clause that exits with a usage message if the script receives fewer than two arguments.
- What is the difference between
"$@"and"$*"? Which should you use in a for loop and why? - A script currently has
$1=servera,$2=serverb,$3=serverc. After runningshift, what are the values of$1and$#? - Write the parameter expansion that uses
/tmpas the value ofDESTif$1was not provided. - Write a script section that prints the basename of the file path stored
in
$1without using thebasenamecommand.
Knowledge Check — Answers
$0=./setup.sh(the script name)
$1=alice
$2=developers
$#=2(count of arguments, not including $0)-
if [ "$#" -lt 2 ]; then echo "Usage: $0 USERNAME GROUPNAME" >&2 exit 1 fi "$@"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.- After
shift:$1=serverb(old $2),$#=2(decremented by 1 from 3). DEST="${1:-/tmp}"— if$1is unset or empty,DESTis assigned/tmp; otherwise it takes the value of $1.-
BASENAME="${1##*/}" echo "$BASENAME"${1##*/}removes the longest match of*/from the front of$1, leaving only the filename component.
Key Takeaways
-
Positional parameters:
$0is the script name;$1–$9(and${10}+) are the arguments;$#is the count. Assign$1,$2to named variables at the top of the script for readability. Always quote:"$1". -
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>&2andexit 1. -
Use
"$@"— not"$*"— when passing or iterating arguments."$@"preserves each argument as a separate word with spaces intact. Useshiftto consume arguments one at a time in a while loop. -
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
usersusing 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
whileloop andshiftto process each argument and print it with its position number (1: alpha, 2: beta, etc.). - Write a script that accepts a file path as
$1and uses parameter expansion to print the basename, the directory, and the filename without its extension — without usingbasename,dirname, or any external command. - Run
bash -n scriptname.shon each script before testing it.
"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.