Learn by reading through in order

Shell Script - Arguments and Exit Codes

Receive ./a.sh foo bar values with $1 $2 $@ $# $0, supply a default with ${2:--}, check $? and return success or failure with exit 0 / exit 1 — write and run a practical script, illustrated in a browser terminal.

Receiving arguments — $1 / $2 / $0

Scripts can take values when you run them.

Values you list after the command, like ./a.sh foo bar, are called arguments, and inside the script $1 is the first and $2 is the second.

$0 is the script's own name, $# is the number of arguments passed, and $@ stands for all of them together.

With these you can run the script with different input every time instead of with fixed values.

SyntaxMeaningExample
$1 $2First and second argumentsecho "$1 then $2"
$0The script's own nameecho "running $0"
$#Number of arguments passedecho "argc=$#"
$@All arguments togetherecho "all: $@"
How arguments map
./a.sh foo bar$1 = foo$2 = bar$# = 2$@ = foo bar1st2ndcountall
The values you pass go into $1 $2; $# gives the count and $@ the whole set.
set -- foo bar                   # set positional parameters for demo
echo "1st: $1"                   # foo
echo "2nd: $2"                   # bar
echo "argc: $#"                  # 2
echo "all: $@"                   # foo bar

Write a script that receives values as arguments and prints them as they are.

① Open args.sh with vi args.sh, press i to enter insert mode, and put #!/bin/sh on the first line.

② Write code that prints the first argument ($1), the second ($2), the count ($#), all arguments ($@), and the script name ($0).

③ Press Esc, save with :wq, then add execute permission.

④ Run it as ./args.sh foo bar with two arguments and check which value went into which variable. If you get stuck, copy the body from the answer panel and paste it into vi's insert mode. (Run it correctly and the explanation will appear.)

Linux console
0 / 3 completed
Loading Linux Terminal...

Supplying a default — ${1:-def}

Sometimes no argument is passed.

If you write ${1:-default}, default is used when $1 is unset or empty, and the given value is used when one is provided.

This lets you write a script that follows the argument when given, and runs on a safe default when not.

Checking $# also tells you whether the expected number of arguments arrived.

SyntaxMeaningExample
${1:-def}Use def when $1 is unsetname=${1:-guest}
${2:-def}Use def when $2 is unsetsep=${2:--}
How the default is supplied
sep=${2:--}$2 = barsep = bar$2 is emptysep = - (default)if givenif omittedAs givenUses default
With ${2:--}, passing a second value uses it, and omitting it uses the default -.
set --                           # reproduce running with no arguments
echo "${1:-guest}"               # unset, so guest
set -- alice                     # now the first argument is set
echo "${1:-guest}"               # has a value, so alice

Write a script that receives a separator as an argument and uses a default when it is omitted.

① Open format.sh with vi format.sh, press i to enter insert mode, and put #!/bin/sh on the first line.

② Receive the first argument as a target name and the second as a separator, but write it with ${2:-...} so a default (for example -) is used when the second is missing, then print the target and the separator.

③ Press Esc, save with :wq, then add execute permission.

④ First run it as ./format.sh foo, omitting the second argument, and check that the separator falls back to the default. If you get stuck, copy the body from the answer panel and paste it into vi's insert mode.

Linux console
0 / 3 completed
Loading Linux Terminal...

Returning success or failure — $? and exit N

When a command finishes it returns an exit code, a number from 0 to 255.

By convention 0 means success and anything other than 0 means failure.

You can read the previous command's exit code with $?.

For grep it is 0 when found and 1 when not, so the value changes with the result.

From inside a script you set the exit code explicitly with exit N.

Returning exit 0 for success and exit 1 for failure is the common convention.

The caller receives this value through $? or && || and can use it for control such as proceeding only on success.

If you omit exit, the exit code of the last command run is returned as it is.

SyntaxMeaningExample
$?Exit code of the previous commandgrep x f; echo $?
exit NEnd the script with exit code Nexit 1
How exit codes flow
Process succeedsexit 0$? = 0Process failsexit 1$? = 1returnsreadsreturnsreadsSuccessFailure
A script returns exit 0 on success and exit 1 on failure, and the caller reads the result with $?.
printf 'apple\nbanana\n' > fruits.txt   # create the material
grep apple fruits.txt > /dev/null
echo "found? $?"                          # found, so 0
grep mango fruits.txt > /dev/null
echo "found? $?"                          # not found, so 1

Write a script that searches a file for a word given as an argument and returns an exit code based on the result.

① Open search.sh with vi search.sh, press i to enter insert mode, and put #!/bin/sh on the first line.

② In the script, create a material file and search it for the word received as the first argument. Write code that prints a success message and returns exit 0 when found, or prints a failure message and returns exit 1 when not.

③ Press Esc, save with :wq, then add execute permission.

④ Run it as ./search.sh foo bar, then check the exit code with echo $?. If you get stuck, copy the body from the answer panel and paste it into vi's insert mode.

Linux console
0 / 3 completed
Loading Linux Terminal...

Processing every argument in turn — $@ and $#

When the number of arguments isn't fixed, instead of writing $1 $2 one by one, loop over $@ with for to process as many as were passed.

The form for x in "$@"; do … done takes them one at a time, and showing the count with $# lets the same script handle any number of arguments.

This is a common pattern in tools that process several files together.

SyntaxMeaningExample
"$@"Pass every argument one word at a time (for for; needs double quotes)for x in "$@"; do
$#Number of arguments passedecho "count=$#"
Looping over all arguments
./a.sh foo bar baz$# = 3$@ = foo bar bazfor x in "$@"- foo- bar- bazcountallone by oneprintloop with for
Get the count with $#, then take each argument one at a time with $@ in a for loop to process them.
set -- foo bar baz               # set positional parameters for demo
echo "argc: $#"                  # 3
for x in "$@"; do
  echo "- $x"                    # - foo / - bar / - baz
done

Write a script that processes every argument passed one at a time and also shows the count.

① Open list.sh with vi list.sh, press i to enter insert mode, and put #!/bin/sh on the first line.

② Print the number of arguments with $#, then take each one in turn with $@ in a for loop and print each argument on its own line.

③ Press Esc, save with :wq, then add execute permission.

④ Run it as ./list.sh foo bar baz with three arguments and check that the count and each argument print in order. If you get stuck, copy the body from the answer panel and paste it into vi's insert mode.

Linux console
0 / 3 completed
Loading Linux Terminal...
QUIZ

Knowledge Check

Answer each question one by one.

Q1What goes into $2 when you run ./a.sh foo bar?

Q2Which is the correct meaning of ${1:-guest}?

Q3Which reads the exit code that shows whether the previous command succeeded?