LearnRHCSA (EX200)Shell Scripts

Script Inputs and Exit Codes

bash - a hands-on Linux lab on a real virtual machine.

Write a real runnable script that takes command-line input and reports success or failure. The shebang, chmod +x, running with ./, the positional parameters $1 $2 $3, the count $ and all-args $@, the usage-then-exit-1 guard for the no-argument case, and reading the exit status with $?. The core EX200 scripting skill behind the produce-a-script and inspect-a-script tasks.

You have written a script that runs. It does exactly one thing, the same way, every time. Then a task on the exam asks for a script that greets whatever name you hand it, or backs up whatever directory you point it at. Your fixed script cannot do that. It has no way to hear you.

Every serious script takes input from the command line and reports back whether it succeeded or failed. Those two skills, reading arguments and setting an exit status, are the spine of the EX200 scripting tasks. One task says produce a script that takes an argument. Another, the classic inspect-this-script task, hands you someone else's $1 and $? and asks what they do. By the end of this lesson you will write a real greet.sh, feed it a name, and read the code it hands back.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The outputs shown are a real capture from a RHEL 10 machine (AlmaLinux 10.2). The lines a script prints are its own output, so they are stable for the inputs shown here; on the exam you write the script and the same logic produces the same shape.

When you run a script and type words after its name, bash hands those words to the script as positional parameters. Run ./greet.sh world and inside the script the word world is waiting in a variable called $1. The first word after the script name is $1, the second is $2, the third is $3, and so on. They are numbered by position, which is where the name comes from.

Two more give you the shape of the whole batch. $# is the count of arguments, how many words were passed. $@ is all of them as separate words, handy for looping over every argument. And $0 holds the script's own name. Learn those five, $1, $2, $#, $@, $0, and you can read any exam script's inputs.

A script with no input is a fixed recipe. A script with positional parameters is a tool: one script, many jobs, decided at the moment you run it. That flexibility is exactly what the exam grades, and it is why nearly every scripting task begins the same way, the script reads $1, checks it is really there, and only then does its work.

There is a second half the grader cares about just as much: the exit status. Every command and every script ends with a number, 0 for success and anything from 1 to 255 for failure. Automation reads that number to decide what to do next. A script that prints the right words but exits with the wrong code can still lose points, so you will set it on purpose with exit.

Write the real thing. You will create greet.sh, make it executable, and run it. The script starts with #!/bin/bash, the shebang line, hash then bang then the path to the interpreter, which tells the system to run the file with bash. chmod +x flips on the execute permission so the file can run as a program. Then ./greet.sh world runs it, the leading ./ meaning run the script right here in this directory.

This one greets the name you pass and reports how many arguments it saw. Build it and run it with one argument:

printf '%s\n' '#!/bin/bash' 'echo "Hello, $1. You passed $# argument(s)."' > greet.sh && chmod +x greet.sh && ./greet.sh world

prompt: student@servera:~$ answer: printf '%s\n' '#!/bin/bash' 'echo "Hello, $1. You passed $# argument(s)."' > greet.sh && chmod +x greet.sh && ./greet.sh world output: Hello, world. You passed 1 argument(s). hint: Write the two lines into greet.sh with printf, chmod +x it, then run ./greet.sh world. Chain the three with &&.

The script heard you. $1 became world, so the greeting filled in your name, and $# was 1, so it reported one argument. That is the whole mechanism: the word you typed after the script name arrived inside the script as $1, ready to use. The #!/bin/bash shebang told the system which interpreter to run, chmod +x made the file runnable, and ./ said run the one in this folder. On the exam this exact sequence, write, make executable, run, is how every script task begins.

A script that ran is not the same as a script that succeeded. The proof is the exit status: the number the last thing to run left behind. Bash keeps it in a special variable spelled $?, the question mark. Read it the instant a command finishes, because the very next command overwrites it.

Your greet.sh ran cleanly and never called exit, so it inherited the exit status of its last command, echo, which is 0 for success. Run the script again, then immediately read $? to see the code it handed back:

./greet.sh world; echo "exit: $?"

prompt: student@servera:~$ answer: ./greet.sh world; echo "exit: $?" output: Hello, world. You passed 1 argument(s). exit: 0 hint: Run the script, then on the same line after a semicolon echo the special variable $?.

The script printed its greeting, then $? reported 0: success. Zero always means it worked. The semicolon let you run two commands on one line so you could grab $? before anything else clobbered it, and that timing matters, $? only ever holds the result of the command that just finished. This is the exact move the inspect-a-script task wants: run it, read $?, and know whether it passed or failed.

Here is the pattern the exam rewards. If the script is run with no name at all, $1 is empty and the greeting is meaningless. A good script refuses to run half-blind. It checks the argument count first, prints a usage line explaining how to call it, and exits with code 1 to signal failure. Only if an argument is present does it do its real work.

The check is if [ $# -eq 0 ], read as if the argument count equals zero. The [ ] is the portable test command, the form the exam expects; bash also offers [[ ]] as its own richer extension, but [ ] is the one that works everywhere. Here is greet.sh rebuilt with the guard, then run with NO argument so the guard fires:

printf '%s\n' '#!/bin/bash' 'if [ $# -eq 0 ]; then echo "usage: greet.sh NAME"; exit 1; fi' 'echo "Hello, $1."' > greet.sh && chmod +x greet.sh && ./greet.sh; echo "exit: $?"

prompt: student@servera:~$ answer: printf '%s\n' '#!/bin/bash' 'if [ $# -eq 0 ]; then echo "usage: greet.sh NAME"; exit 1; fi' 'echo "Hello, $1."' > greet.sh && chmod +x greet.sh && ./greet.sh; echo "exit: $?" output: usage: greet.sh NAME exit: 1 hint: Add an if [ $# -eq 0 ] guard that echoes a usage line and runs exit 1, then run the script with no argument and read $?.

Because no argument was passed, $# was 0, the guard caught it, printed the usage line, and ran exit 1. That exit 1 did two things at once: it stopped the script before it reached the greeting, and it set the exit status to 1, which $? then reported. Failure, announced honestly. This three-line guard, count the args, print usage, exit 1, is the single most reused shape in exam scripts. Put it at the top of everything you write.

The usage text and the exit code here are the script's own logic, so they are the same on any machine that runs this greet.sh. What varies between tasks is the message and the required count. The move never changes: test $#, print how to call the script, exit 1.

Positional parameters are not only $1. You can pull any argument by its number, and $# always tells you how many arrived. That matters when a task hands the script several words and asks you to use, say, the first and the third. This probe reads $1 and $3 directly, reports the count from $#, and prints every argument with $@.

Run it with three colours so you can watch each parameter land in its slot:

printf '%s\n' '#!/bin/bash' 'echo "first=$1 third=$3 count=$#"' 'echo "all: $@"' > probe.sh && chmod +x probe.sh && ./probe.sh red green blue

prompt: student@servera:~$ answer: printf '%s\n' '#!/bin/bash' 'echo "first=$1 third=$3 count=$#"' 'echo "all: $@"' > probe.sh && chmod +x probe.sh && ./probe.sh red green blue output: first=red third=blue count=3 all: red green blue hint: Have the script echo $1, $3, and $#, then echo $@. Run it with the three words red green blue.

Each word dropped into its numbered slot: red into $1, green into $2, blue into $3. The script asked for $1 and $3, so it printed red and blue and skipped the middle. $# counted 3, and $@ spilled all three back out in order. This is the reach-by-position skill the inspect task tests: given a script full of $1, $2, $3, you can now say precisely which typed word each one holds.

Scaffolding off. No command is printed this time. You have every piece you need.

Write and run a script named hello.sh in one line. It must greet the first argument, printing Hello, followed by that argument and a period. But first it has to guard the empty case: if no argument is given, it prints usage: hello.sh NAME and exits with code 1. Build it, make it executable, then run it with the single word world. Think about the shebang, the if [ $# -eq 0 ] guard with its exit 1, and the ./ to run it.

prompt: student@servera:~$ answer: printf '%s\n' '#!/bin/bash' 'if [ $# -eq 0 ]; then echo "usage: hello.sh NAME"; exit 1; fi' 'echo "Hello, $1."' > hello.sh && chmod +x hello.sh && ./hello.sh world output: Hello, world. hint: Same shape as greet.sh: shebang, an if [ $# -eq 0 ] guard that echoes usage and runs exit 1, then echo "Hello, $1.". Write it with printf, chmod +x, run ./hello.sh world.

You built a complete exam-shaped script from memory. The guard sat at the top ready to catch a missing name, but because you passed world the argument count was 1, the guard stayed quiet, and $1 filled the greeting. Run the same script with no argument and it would print the usage line and exit 1 instead. Validate input, do the work, set the code: that is the whole discipline the scripting tasks grade, and you just wrote it without a template.

You earned this cheat sheet. Every row is a piece you just wrote or ran:

The one shape to burn in for the exam: shebang at the top, a [ $# -eq 0 ] guard that prints usage and runs exit 1, the real work using $1, and exit 0 (or a clean last command) at the end. [ ] is the portable test form the exam expects; [[ ]] is bash's richer extension when you need it.

When you are handed an unfamiliar script to read, trace the inputs first: find every $1, $2, and $#, then find every exit and $?. Those tell you what the script accepts and how it reports success or failure, which is exactly what the inspect-a-script task asks you to explain.

The practice terminal has shown you the shape of a real script: the #!/bin/bash shebang, chmod +x, running it with ./, the positional parameters $1, $3, $#, and $@, the usage-then-exit 1 guard, and reading the status back with $?. Every one of those you typed yourself.

The shell-scripts module mission is where you run these against a real RHEL 10 machine. A full VM boots for you, with a real shell and real files. The mission hands you objectives that use exactly what you practiced here: write a script that takes an argument, guard the empty case, and exit with the right code so the grader is satisfied. One difference from this lesson: the mission shows no commands. You read the objective, recall the pattern, and type it. That recall is what makes it stick on exam day.

Finish the other shell-scripts lessons, then go write a real script for yourself.

Practice Script Inputs and Exit Codes in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.