7.3 Process Script Inputs ($1, $2, and Beyond)

Key Takeaways

  • Positional parameters $1, $2, $3… hold command-line arguments; $0 is the script name as invoked.
  • $# is the argument count; "$@" is all arguments as separate words; "$*" joins arguments into one word (IFS first character)—prefer "$@" for safe iteration.
  • Validate inputs with [ $# -eq N ], -z tests, and usage messages on stderr before changing the system.
  • shift discards $1 and renumbers remaining parameters—useful for option parsing in simple scripts.
  • Exam scripts must behave correctly for missing, extra, or wrong-type arguments: check, message, non-zero exit.
Last updated: August 2026

7.3 Process Script Inputs ($1, $2, and Beyond)

Quick Answer: Arguments appear as $1, $2, …; $0 is the script name; $# is how many arguments were passed; iterate safely with for arg in "$@"; do ...; done. Check counts and values with if/[ ] before acting. Prefer "$@" over unquoted $*.

Why inputs matter on the exam

A script that only hard-codes /tmp/foo is easy to write and often wrong for the task. Graders pass arguments or expect the script to read parameters the task describes:

  • ./mkuser.sh jdoe
  • ./backup.sh /data /mnt/backup
  • ./check.sh file1 file2 file3

You must process those inputs, not ignore them.

Positional parameters map

ParameterMeaning
$0Script name as invoked (./tool.sh or full path)
$1First argument
$2Second argument
$3$9Third through ninth
${10}, ${11}, …Tenth and beyond—use braces
$#Number of arguments (not counting $0)
$*All arguments (joining rules below)
$@All arguments (joining rules below)
$$PID of the shell (not an input; useful in temp names)
$?Exit status of last command (not an input)
#!/bin/bash
echo "script=$0"
echo "count=$#"
echo "first=$1"
echo "second=$2"

Run:

chmod +x demo.sh
./demo.sh alpha beta
# script=./demo.sh
# count=2
# first=alpha
# second=beta

Braces for clarity and double digits

echo "${1}"
echo "${10}"    # not $10 which is $1 followed by 0

"$@" vs "$*" vs unquoted forms

This distinction is a classic gotcha.

FormBehavior (typical)
"$@"Each argument is a separate word, even with spaces
"$*"All arguments joined into one word using the first IFS character (usually space)
$@ / $* unquotedSubject to word-splitting and globbing—unsafe
#!/bin/bash
# Safe: preserves "My File.txt" as one argument
for f in "$@"; do
  echo "arg=[$f]"
done
# Dangerous if an argument has spaces:
for f in $*; do
  echo "split badly: $f"
done

Exam rule of thumb: loop with for x in "$@"; do and expand individuals as "$1", "$2".

Validating argument count

#!/bin/bash
if [ $# -ne 2 ]; then
  echo "Usage: $0 source_dir dest_dir" >&2
  exit 2
fi

SRC="$1"
DST="$2"

Common checks:

TestMeaning
[ $# -eq 0 ]No arguments
[ $# -ne 1 ]Not exactly one
[ $# -lt 2 ]Fewer than two
[ $# -gt 5 ]Too many

Exit code 2 is a conventional “usage error”; 1 is general failure. Be consistent within a script.

Validating argument meaning

Count alone is not enough:

#!/bin/bash
if [ $# -ne 1 ]; then
  echo "Usage: $0 existing-directory" >&2
  exit 2
fi

if [ ! -d "$1" ]; then
  echo "Not a directory: $1" >&2
  exit 1
fi

if [ ! -w "$1" ]; then
  echo "Directory not writable: $1" >&2
  exit 1
fi

echo "OK to use $1"

Other frequent validations:

# Non-empty string
[ -z "$1" ] && { echo "empty" >&2; exit 2; }

# User exists
id "$1" &>/dev/null || { echo "no such user" >&2; exit 1; }

# Absolute path required by task
case "$1" in
  /*) ;;
  *) echo "need absolute path" >&2; exit 2 ;;
esac

case is optional sugar; nested if is fine on EX200.

Assigning to named variables

Immediately copy positionals into readable names after validation:

USER_NAME="$1"
COMMENT="$2"
HOME_BASE="${3:-/home}"   # default if $3 unset/null

${VAR:-default} supplies a default—handy when an argument is optional.

ExpansionMeaning
${1:-default}Use default if $1 unset or null
${1:=default}Assign default to $1 if unset/null (positional assignment is special—prefer named vars)
${1:?message}Exit if unset/null, print message
${#1}Length of $1 string

Prefer named variables after the first few lines so later logic does not confuse $1 after shift.

shift — walk arguments

shift removes $1 and renumbers: old $2 becomes $1, and $# decreases by one.

#!/bin/bash
# sum.sh — add numeric arguments
if [ $# -eq 0 ]; then
  echo "Usage: $0 numbers..." >&2
  exit 2
fi

sum=0
while [ $# -gt 0 ]; do
  sum=$((sum + $1))
  shift
done
echo "$sum"

Option-style parsing (simple):

#!/bin/bash
VERBOSE=0
while [ $# -gt 0 ]; do
  case "$1" in
    -v) VERBOSE=1; shift ;;
    -h|--help) echo "Usage: $0 [-v] file"; exit 0 ;;
    --) shift; break ;;
    -*) echo "Unknown option: $1" >&2; exit 2 ;;
    *) break ;;
  esac
done

# remaining "$@" are files
for f in "$@"; do
  ...
done

Full getopts is optional knowledge; simple case + shift covers many exam scripts.

Interactive input: read (when the task asks)

Most EX200 scripts take CLI args, not prompts. If a task says “ask the user,” use read:

#!/bin/bash
read -r -p "Username: " USER_NAME
if [ -z "$USER_NAME" ]; then
  echo "empty name" >&2
  exit 2
fi

Do not mix interactive read into scripts the grader runs non-interactively unless the task requires it—your script would hang.

Environment variables as inputs

Sometimes configuration comes from the environment:

#!/bin/bash
MODE="${MODE:-enforce}"
if [ "$MODE" != "enforce" ] && [ "$MODE" != "permissive" ]; then
  echo "MODE must be enforce or permissive" >&2
  exit 2
fi

Run as MODE=permissive ./script.sh. Prefer explicit $1 when the task lists command-line parameters.

End-to-end exam script

Task: Create /usr/local/bin/hostcheck.sh that takes one hostname argument, pings once, prints OK or FAIL, exits 0/1, and rejects wrong usage.

#!/bin/bash
if [ $# -ne 1 ]; then
  echo "Usage: $0 hostname" >&2
  exit 2
fi

HOST="$1"
if ping -c1 -W2 "$HOST" &>/dev/null; then
  echo "OK $HOST"
  exit 0
else
  echo "FAIL $HOST"
  exit 1
fi
sudo tee /usr/local/bin/hostcheck.sh >/dev/null <<'EOF'
...paste script...
EOF
sudo chmod +x /usr/local/bin/hostcheck.sh
hostcheck.sh server1.example.com
echo $?

Task: Script accepts multiple files; print line counts; skip missing files with a message.

#!/bin/bash
if [ $# -eq 0 ]; then
  echo "Usage: $0 file..." >&2
  exit 2
fi

for f in "$@"; do
  if [ -f "$f" ]; then
    lines=$(wc -l < "$f")
    echo "$f $lines"
  else
    echo "missing: $f" >&2
  fi
done

Special: $0 in usage messages

Always print usage with "$0" so users see the real invocation name:

echo "Usage: $0 USER DIR" >&2

Hard-coding ./script.sh confuses when the script is installed in /usr/local/bin.

Debugging inputs

bash -x ./script.sh arg1 arg2
# or inside script temporarily:
printf 'DEBUG: count=%s a1=%q a2=%q\n' "$#" "$1" "$2" >&2

%q shell-escapes values for visibility of spaces/newlines.

Common traps

  1. Using $1 after shift without re-checking — it is a different value now.
  2. $# vs ${#1} — count of args vs length of first arg string.
  3. Unquoted $@ — breaks on spaces.
  4. Forgetting ${10} braces$10 ≠ tenth argument.
  5. No usage message / always exit 0 — grader cannot distinguish success from silent misuse.
  6. Reading $1 but documenting two required args — validate $# honestly.
  7. Assuming interactive read when automation supplies args only.

Relationship to other sections

Inputs feed conditionals (empty? exists?) and loops (for arg in "$@"). The next section shows how to capture command output into variables that may also become inputs to later decisions.

Section checkpoint

You should read $1/$2/…, report $#, iterate with "$@", understand "$*" joining, shift through a list, validate counts and types, set defaults with ${var:-default}, print usage via $0, and exit non-zero on bad input—core EX200 script input processing.

Test Your Knowledge

A script is run as ./deploy.sh web /var/www. What are $0, $1, $2, and $# inside the script?

A
B
C
D
Test Your Knowledge

Why should you iterate arguments with for f in "$@"; do rather than for f in $*; do when paths may contain spaces?

A
B
C
D
Test Your Knowledge

What does the shift builtin do?

A
B
C
D
Test Your Knowledge

Which snippet best rejects wrong usage when a script requires exactly one argument?

A
B
C
D