Lesson 9: Input, Arguments & Options
Goal: Build scripts with proper argument parsing, option flags, and interactive input.
Table of Contents
- Positional Arguments
- Argument Validation
- shift — Processing Arguments
- getopts — Short Options
- Long Options with case
- Interactive Input — read
- Usage Messages & Help
- Exercises
Positional Arguments
#!/bin/bash
# save as greet.sh
echo "Script: $0"
echo "First: ${1:-none}"
echo "Second: ${2:-none}"
echo "All: $@"
echo "Count: $#"
./greet.sh hello world
# Script: ./greet.sh
# First: hello
# Second: world
# All: hello world
# Count: 2
Argument Validation
#!/bin/bash
set -euo pipefail
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <source> <destination>" >&2
exit 1
fi
SOURCE="$1"
DEST="$2"
[[ -f "$SOURCE" ]] || { echo "Source not found: $SOURCE" >&2; exit 1; }
[[ -d "$(dirname "$DEST")" ]] || { echo "Dest dir doesn't exist" >&2; exit 1; }
cp "$SOURCE" "$DEST"
echo "Copied $SOURCE → $DEST"
shift — Processing Arguments
shift removes the first argument and shifts everything left.
#!/bin/bash
set -euo pipefail
COMMAND="${1:-}"
shift || true # shift, but don't fail if no args
case "$COMMAND" in
start)
echo "Starting with args: $*"
;;
stop)
echo "Stopping..."
;;
*)
echo "Usage: $0 {start|stop} [args...]"
exit 1
;;
esac
Process all arguments
#!/bin/bash
while [[ $# -gt 0 ]]; do
echo "Processing: $1"
shift
done
getopts — Short Options
Parse standard Unix flags like -v, -f file, -n 5.
#!/bin/bash
set -euo pipefail
VERBOSE=false
OUTPUT=""
COUNT=1
usage() {
echo "Usage: $0 [-v] [-o output] [-n count] file..."
echo " -v Verbose mode"
echo " -o FILE Output file"
echo " -n NUM Number of iterations"
echo " -h Show help"
exit 0
}
while getopts "vo:n:h" opt; do
case "$opt" in
v) VERBOSE=true ;;
o) OUTPUT="$OPTARG" ;;
n) COUNT="$OPTARG" ;;
h) usage ;;
?) echo "Invalid option. Use -h for help." >&2; exit 1 ;;
esac
done
# Remove parsed options, leaving remaining arguments
shift $((OPTIND - 1))
# Remaining arguments are files
echo "Verbose: $VERBOSE"
echo "Output: ${OUTPUT:-stdout}"
echo "Count: $COUNT"
echo "Files: $*"
./script.sh -v -o result.txt -n 3 file1.txt file2.txt
# Verbose: true
# Output: result.txt
# Count: 3
# Files: file1.txt file2.txt
getopts rules
v— flag (no argument)o:— option with required argument (colon after letter)- Leading
:in optstring — silent error handling
Long Options with case
For --verbose, --output=file style options:
#!/bin/bash
set -euo pipefail
VERBOSE=false
DRY_RUN=false
OUTPUT=""
FILES=()
usage() {
cat << EOF
Usage: $0 [options] <files...>
Options:
-v, --verbose Enable verbose output
-d, --dry-run Show what would be done
-o, --output FILE Write output to FILE
-h, --help Show this help
Examples:
$0 --verbose -o result.txt file1.txt file2.txt
$0 --dry-run *.log
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose)
VERBOSE=true
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-o|--output)
OUTPUT="${2:?Error: --output requires an argument}"
shift 2
;;
-h|--help)
usage
;;
--)
shift
FILES+=("$@")
break
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
FILES+=("$1")
shift
;;
esac
done
[[ ${#FILES[@]} -eq 0 ]] && { echo "Error: No files specified" >&2; exit 1; }
echo "Verbose: $VERBOSE"
echo "Dry run: $DRY_RUN"
echo "Output: ${OUTPUT:-stdout}"
echo "Files: ${FILES[*]}"
Interactive Input — read
# Basic input
read -rp "Enter your name: " NAME
echo "Hello, $NAME!"
# With default
read -rp "Port [3000]: " PORT
PORT="${PORT:-3000}"
# Password (hidden)
read -rsp "Password: " PASS
echo ""
echo "Password length: ${#PASS}"
# Timeout
if read -rt 5 -p "Quick! Enter something (5s): " ANSWER; then
echo "You entered: $ANSWER"
else
echo "Too slow!"
fi
# Yes/No confirmation
read -rp "Continue? [y/N] " CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
echo "Proceeding..."
else
echo "Cancelled."
exit 0
fi
Select menu
PS3="Choose an option: "
select opt in "Start" "Stop" "Restart" "Status" "Quit"; do
case "$opt" in
Start) echo "Starting..."; break ;;
Stop) echo "Stopping..."; break ;;
Restart) echo "Restarting..."; break ;;
Status) echo "Status: running" ;;
Quit) echo "Bye!"; exit 0 ;;
*) echo "Invalid option" ;;
esac
done
Usage Messages & Help
Standard pattern
#!/bin/bash
set -euo pipefail
readonly VERSION="1.2.0"
readonly SCRIPT_NAME="$(basename "$0")"
usage() {
cat << EOF
$SCRIPT_NAME v$VERSION — Deploy application to server
USAGE:
$SCRIPT_NAME [OPTIONS] <environment>
ARGUMENTS:
environment Target environment (dev, staging, prod)
OPTIONS:
-b, --branch BRANCH Git branch to deploy (default: main)
-t, --tag TAG Specific tag to deploy
-d, --dry-run Show what would be done
-f, --force Skip confirmation
-v, --verbose Enable verbose output
-h, --help Show this help message
--version Show version
EXAMPLES:
$SCRIPT_NAME staging
$SCRIPT_NAME -b feature/auth --dry-run prod
$SCRIPT_NAME --tag v1.2.0 prod
EOF
exit 0
}
version() {
echo "$SCRIPT_NAME v$VERSION"
exit 0
}
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && usage
[[ "${1:-}" == "--version" ]] && version
Exercises
Exercise 1: File processor with options
cat > process.sh << 'EOF'
#!/bin/bash
set -euo pipefail
UPPERCASE=false
NUMBERED=false
REVERSE=false
while getopts "unrh" opt; do
case "$opt" in
u) UPPERCASE=true ;;
n) NUMBERED=true ;;
r) REVERSE=true ;;
h) echo "Usage: $0 [-u] [-n] [-r] <file>"; exit 0 ;;
?) exit 1 ;;
esac
done
shift $((OPTIND - 1))
FILE="${1:?Error: provide a file}"
[[ -f "$FILE" ]] || { echo "Not found: $FILE" >&2; exit 1; }
CMD="cat"
$REVERSE && CMD="tac"
OUTPUT=$($CMD "$FILE")
$UPPERCASE && OUTPUT=$(echo "$OUTPUT" | tr 'a-z' 'A-Z')
if $NUMBERED; then
echo "$OUTPUT" | nl -ba
else
echo "$OUTPUT"
fi
EOF
echo -e "hello\nworld\nfoo\nbar" > /tmp/test.txt
chmod +x process.sh
./process.sh -u -n /tmp/test.txt
./process.sh -r /tmp/test.txt
rm process.sh /tmp/test.txt
Exercise 2: Interactive setup wizard
cat > setup.sh << 'EOF'
#!/bin/bash
set -euo pipefail
echo "=== Application Setup ==="
echo ""
read -rp "App name [myapp]: " APP_NAME
APP_NAME="${APP_NAME:-myapp}"
read -rp "Port [3000]: " PORT
PORT="${PORT:-3000}"
PS3="Select database: "
select DB in "PostgreSQL" "MySQL" "SQLite"; do
[[ -n "$DB" ]] && break
done
read -rp "Enable debug mode? [y/N] " DEBUG
[[ "$DEBUG" =~ ^[Yy]$ ]] && DEBUG=true || DEBUG=false
echo ""
echo "=== Configuration ==="
echo "App: $APP_NAME"
echo "Port: $PORT"
echo "Database: $DB"
echo "Debug: $DEBUG"
read -rp "Save this configuration? [Y/n] " SAVE
if [[ ! "$SAVE" =~ ^[Nn]$ ]]; then
cat > "${APP_NAME}.conf" << CONF
app_name=$APP_NAME
port=$PORT
database=$DB
debug=$DEBUG
CONF
echo "Saved to ${APP_NAME}.conf"
else
echo "Cancelled"
fi
EOF
chmod +x setup.sh && ./setup.sh
rm -f setup.sh myapp.conf
Key Takeaways
$1,$2,$@,$#— positional arguments${1:-default}— defaults for missing argumentsshift— consume arguments one by onegetopts "vf:n:" opt— parse short options (-v,-f file)while/caseloop — parse long options (--verbose,--file)read -rp "prompt" VAR— interactive input (always use-r)select— interactive menu- Always provide
-h/--helpwith usage examples
Next Lesson: Lesson 10: Docker Entrypoints & Init Scripts →