Writing simple shell scripts
Writing a script that runs: the shebang, exit status, arguments and variables, tests and conditionals, loops, and the two commands that turn a list into work.
Lesson 2 of 2 in objective 105. Shells and shell scripting, part of LPIC-1 Exam 102-500.
Making a file a program
The first line is #!/bin/bash — the shebang — and it tells the kernel which interpreter to run the file with. Without it, the file is run by whatever happens to be interpreting, which is usually right until it is not. The file also needs execute permission (chmod +x), and it has to be found: a script in the current directory is run as ./script, because . is not in PATH.
Every command sets an exit status: 0 means success and anything else is failure, which is backwards from how truth is normally spelled and is the single thing to internalise here. $? holds the last status, exit N ends the script with a chosen one. && runs the next command only if the previous succeeded, || only if it failed.
That is what makes the one-line conditional in a cron entry work, and it is worth reading one against its neighbours. backup.sh || echo "backup failed" | mail -s "backup" root sends the mail only when backup.sh exited non-zero. With && instead, the mail goes out exactly when the backup SUCCEEDED, which is the inverted answer a question offers next to the right one. With a semicolon it goes out every night regardless. With a pipe it also goes out every night, because a pipe wires standard output to standard input and takes no interest in the exit status at all. Note also that the pipeline binds tighter than the list operators, so the echo and the mail together are the right-hand side of the ||.
Arguments, variables and tests
Positional parameters are $1 to $9 and beyond with ${10}; $0 is the script name, $# is how many arguments there were, $@ is all of them as separate words and $* as one. $$ is the script's own PID. Command substitution — $(command), or the older backticks — captures output into a variable. read NAME takes a line from standard input into a variable, which is how a script asks a question.
test EXPRESSION, spelled [ EXPRESSION ] with mandatory spaces inside the brackets, is how conditions are written. For files: -f is a regular file, -d a directory, -e exists at all, -r/-w/-x readable, writable, executable, -s non-empty. For strings: = and != compare, -z is empty and -n is non-empty. For numbers the operators are worded: -eq, -ne, -lt, -le, -gt, -ge, because < and > would be redirection. Quoting a variable in a test — [ "$x" = "y" ] — matters, since an unset variable would otherwise leave the bracket malformed.
Branching and looping
if CONDITION; then … elif … else … fi. case VALUE in pattern) … ;; esac matches against glob patterns and is the readable choice when there are several options. Both close with their own name reversed, which is a habit rather than a rule to memorise.
for VAR in LIST; do … done iterates over words — often a glob, a command substitution, or seq output. while CONDITION; do … done repeats while a command succeeds, and until is the same with the sense reversed. break leaves a loop and continue starts the next iteration.
The pieces that connect scripts to the rest of the system: xargs turns lines into arguments, and a script called by cron gets a minimal environment — no PATH you set in ~/.bashrc, no aliases — so scripts intended for cron use full paths and set what they need.
exec is the other one, and it does something a loop or a conditional cannot. Given a command, it does NOT fork: the shell overlays itself with the new program, which keeps the same PID and the same open file descriptors and inherits whatever the script had set up. So a wrapper that ends exec /usr/bin/myapp "$@" leaves no idle shell hanging around as a parent, myapp is what a service manager tracks, and signals reach it directly — at the cost that anything written after that line never runs, because there is no shell left to return to. Without exec the shell would fork, wait for the child and carry on to the cleanup. Given no command at all, exec is a different instruction again: exec >> /var/log/run.log 2>&1 applies those redirections to the current shell and execution continues normally.
Worth carrying in
- #!/bin/bash
- The shebang: which interpreter runs this file.
- $?
- Exit status of the last command. 0 is success.
- $1 $# $@ $0
- First argument, argument count, all arguments, script name.
- $(command)
- Command substitution: capture output. Backticks are the older spelling.
- [ -f file ]
- Test a regular file exists. -d directory, -e any, -s non-empty.
- -eq -lt -gt
- Numeric comparison. = and != are for strings.
- for x in list; do … done
- Iterate over words. while/until loop on a condition.
- case $x in a) … ;; esac
- Match against glob patterns.
- read NAME
- Read one line of standard input into a variable.
- cmd || fallback
- Run the fallback only on failure. && is the other way round;
;and a pipe ignore the status. - exec command
- Replace the shell with the command, same PID. Nothing after that line runs.
What the exam does with this
- Exit status 0 means SUCCESS. Every conditional in a shell is built on that inversion.
- Numeric tests use -eq and -lt; string tests use = and !=. Swapping them is a favourite distractor.
- A cron job does not inherit your interactive environment. Scripts for cron use absolute paths.
- Objective
- 105. Shells and shell scripting
- Share of the exam
- 15% (the whole objective)
- Questions in this lesson
- 23
- Signed for by a person
- 0
Partly checked. None of the 23 questions here has been read against the cited source by a person. 23 questions have been checked against their cited clause by an automated pass — which is not the same thing, and is not a signature.
Only questions a person has signed for are used in mock exams here. That is the whole difference between the two kinds of checking above.
How these questions are written — where each question comes from, what the verification ledger records, and what happens when one is found wrong.
Drill this lesson
A lesson is one sitting: the trainer draws a short run from these questions alone and spaces the ones you get wrong.
Practise Writing simple shell scripts
Questions in this lesson
- A command finishes and the shell reports an exit status of 0. What does that mean, and which parameter reports it? machine-checked
- Which statement correctly distinguishes bash's `[[ ... ]]` from `[ ... ]`? machine-checked
- Inside a shell script, which special parameter expands to the number of positional parameters the script was given? machine-checked
- A script contains the line `mkdir /srv/data && cp report.txt /srv/data/`. Under what condition does the cp run? machine-checked
- Which keyword closes a `case` construct in a bash script? machine-checked
- A script is called with two arguments: `./run.sh "annual report" 2026`. How does `"$@"` differ from `"$*"` when the script passes them on to another command? machine-checked
- Which three loop headers cause a bash script to iterate exactly over the numbers 1 to 5? Assume default bash options and an empty current directory. (Choose three.) machine-checked
- Type the first line a script must contain so that the kernel runs it with the Bourne Again shell installed at /bin/bash. machine-checked
- Type the special parameter, including its leading dollar sign, that expands to the exit status of the most recently completed foreground command. machine-checked
- Using symbolic mode and changing no other permission bits, type the command that adds the execute permission for all three permission classes (user, group and other) to the file backup.sh in the current directory. machine-checked
- Type the bash builtin command, with its argument, that reads one line from standard input and stores it in the shell variable named ANSWER. machine-checked
- A script must continue only when /etc/app.conf exists and is a regular file, and must stop if that name is a directory or absent. Which test does exactly that? machine-checked
- A nightly job must run /usr/local/sbin/backup.sh and send mail to root only when the backup fails. Which line does that? machine-checked
- A wrapper script sets a few variables and ends with the line exec /usr/bin/myapp "$@", after which two cleanup commands are written. What happens when the script runs? machine-checked
- A script must process /etc/hosts one whole line at a time, including lines that contain spaces. Which loop does that correctly? machine-checked
- A colleague sets root ownership and the set-UID bit on a bash script so that ordinary users can run it with root privileges. Users run it and still get permission denied on the privileged step. Why? machine-checked
- The variable COUNT holds a decimal integer. Which THREE conditions are true exactly when COUNT is greater than 10? (Choose three.) machine-checked
- /srv/link is a symbolic link pointing at /srv/data/report.txt, which exists and is an ordinary file. Which THREE test expressions return a true exit status? (Choose three.) machine-checked
- Type the single bash keyword that closes the body of a for, while or until loop. machine-checked
- A configuration check fails, and the script must stop at once and report failure to whatever called it using the conventional general-error status. Type the complete command line, builtin and argument, that ends the script this way. machine-checked
- A script takes an optional directory as its first argument and must fall back to /var/log when it is called with no arguments at all. The positional parameters themselves must not be changed. Which line does that? machine-checked
- A script must store the output of the command date +%F in the shell variable TODAY. Which TWO lines do that? (Choose two.) machine-checked
- A backup script runs the pipeline tar cf - /srv | gzip > /backup/srv.tar.gz and then examines $? on the next line. tar exits non-zero because one file could not be read, while gzip compresses what it was given and exits 0. Assuming default shell options, what does $? hold? machine-checked
Practise Writing simple shell scripts
The rest of objective 105
- Customizing the shell environment
- Writing simple shell scripts — you are here