Skip to content
Laviyx Learn

Try

Linux and Bash course menu

Bash Script Arguments and Exit Codes Explained

Read script arguments with $1, $@ and $#, parse options with getopts, write usage messages, set exit codes, and check them with $? in Bash.

Intermediate8 min readUpdated
On this page

Arguments#

Values typed after a script's name arrive as $1, $2 and so on:

Terminal
cat > args.sh <<'EOF'
#!/bin/bash
echo "Script: $0"
echo "First:  $1"
echo "Second: $2"
echo "Count:  $#"
echo "All:    $*"
EOF
bash args.sh apple "green mango"
Output
Script: args.sh
First:  apple
Second: green mango
Count:  2
All:    apple green mango
VariableMeaning
$0Script name
$1 to $9, ${10}Arguments
$#How many
"$@"All, as separate words; the one to use
"$*"All, as one word

Check the arguments#

A good script explains itself if it is used wrongly:

Terminal
cat > backup.sh <<'EOF'
#!/bin/bash
if [[ $# -ne 2 ]]; then
  echo "Usage: $0 <source> <destination>" >&2
  exit 1
fi
echo "Copying $1 to $2"
EOF
bash backup.sh only-one
echo "Exit code: $?"
bash backup.sh docs /backup
Output
Usage: backup.sh <source> <destination>
Exit code: 1
Copying docs to /backup

Exit codes#

Every command finishes with a number. 0 means success, and anything else means failure. $? holds the last one:

Terminal
ls /etc/hostname > /dev/null
echo "$?"
ls /nothing 2> /dev/null
echo "$?"
Output
0
2

Scripts set theirs with exit. Without it, a script returns the status of its last command.

CodeUsual meaning
0Success
1General error
2Wrong usage
126Found, but cannot be executed
127Command not found
130Stopped by Ctrl+C

CI systems, cron and other scripts all use exit codes to tell whether something worked, so always exit with non-zero on failure.

Shift through arguments#

shift drops $1 and moves the rest down:

Terminal
sum() {
  local total=0
  while (( $# > 0 )); do
    ((total += $1))
    shift
  done
  echo "$total"
}
sum 5 10 20
Output
35

Options with getopts#

getopts handles flags such as -v and -o file. A colon after a letter means that it takes a value:

Terminal
cat > report.sh <<'EOF'
#!/bin/bash
verbose=false
output="report.txt"

while getopts "vo:h" opt; do
  case "$opt" in
    v) verbose=true ;;
    o) output="$OPTARG" ;;
    h) echo "Usage: $0 [-v] [-o file] name"; exit 0 ;;
    *) echo "Try -h for help" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

echo "verbose=$verbose output=$output name=$1"
EOF
bash report.sh -v -o june.txt sales
bash report.sh sales
bash report.sh -x sales
echo "Exit code: $?"
Output
verbose=true output=june.txt name=sales
verbose=false output=report.txt name=sales
report.sh: illegal option -- x
Try -h for help
Exit code: 2

shift $((OPTIND - 1)) removes the options, leaving the normal arguments.

Long options#

getopts handles only single letters. For --verbose, use a case in a while loop:

Terminal
cat > long.sh <<'EOF'
#!/bin/bash
while (( $# > 0 )); do
  case "$1" in
    --name) name="$2"; shift 2 ;;
    --force) force=yes; shift ;;
    *) echo "Unknown option: $1" >&2; exit 2 ;;
  esac
done
echo "name=$name force=${force:-no}"
EOF
bash long.sh --name asha --force
Output
name=asha force=yes

Common mistakes#

Practice#

Practice exercise

Write count.sh, which takes a file name, prints the number of lines, and exits with code 1 and a message on stderr if the file does not exist. Test both cases.

Show solution
Terminal
cat > count.sh <<'EOF'
#!/bin/bash
file="$1"
if [[ ! -f "$file" ]]; then
  echo "No such file: $file" >&2
  exit 1
fi
wc -l < "$file"
EOF
bash count.sh /etc/hostname
bash count.sh /nothing
echo "Exit code: $?"
Output
1
No such file: /nothing
Exit code: 1

Key takeaway#

Next: Arrays and String Handling.

Finished this lesson?