Weight: 4
Goal: Customize existing scripts, or write simple new Bash scripts.
A shell script is a plain text file containing a sequence of shell commands. Instead of typing the commands one by one, you save them in a file and run that file. The shell reads the file from top to bottom and runs each line.
A minimal script:
#!/bin/bash
echo "Hello, world"To run it:
$ chmod +x hello.sh # make it executable
$ ./hello.sh # run it
Hello, world#!)The very first line of a script can begin with #!
followed by the path to an interpreter. This is called the
shebang (or hashbang). It tells the kernel which
program should run the script.
#!/bin/bash # use Bash
#!/bin/sh # use the system's POSIX shell
#!/usr/bin/python3 # use Python 3Key points for the exam:
#!).#! must be the absolute
path to the interpreter.To run a script, the kernel needs:
$ chmod +x myscript.sh # add execute permission for all
$ chmod 755 myscript.sh # rwxr-xr-x — owner can edit/run, others can run
$ chmod 700 myscript.sh # rwx------ — only the owner can do anything~/bin/ — personal scripts for one
user. Many distributions add this to PATH automatically if
it exists./usr/local/bin/ — scripts intended for
all users on the system./usr/local/sbin/ — system
administration scripts for root.If a directory is in PATH, you can run the script by
name (myscript.sh). Otherwise, you must give the full or
relative path (./myscript.sh).
The SUID bit (chmod u+s) normally makes
a program run with the privileges of its owner. Linux
deliberately ignores SUID on shell scripts for security reasons
— setting it has no effect. The exam expects you to know this: do
not rely on SUID for scripts; use sudo
instead.
NAME="Alice"
echo "Hello, $NAME" # → Hello, Alice
echo "Hello, ${NAME}!" # braces let you put text directly afterNo spaces around the = sign —
NAME = "Alice" is a syntax error.
| Variable | Meaning |
|---|---|
$0 |
The script’s own name. |
$1, $2, …, $9 |
The first, second, … positional arguments. |
$# |
Number of arguments passed. |
$@ |
All arguments as separate words. |
$* |
All arguments as a single word. |
$? |
Exit status of the last command (see section 6). |
$$ |
PID (process ID) of the current shell. |
$! |
PID of the last background job. |
"…" — variables and
command substitution are expanded.'…' — everything is
literal, no expansion.\ — escape one
character.$ NAME=Alice
$ echo "Hello, $NAME" # Hello, Alice
$ echo 'Hello, $NAME' # Hello, $NAME
$ echo "Cost: \$5" # Cost: $5Command substitution runs a command and inserts its output into another command.
TODAY=$(date) # preferred form
TODAY=`date` # older form, same meaning
echo "Today is $TODAY"You can use it anywhere a string is expected:
echo "There are $(ls | wc -l) files here"The exam may show either form ($(…) or backticks
`…`). Both work.
Every command returns an exit status (also called a return code) when it finishes:
The exit status of the last command is in $?:
$ ls /etc
... output ...
$ echo $?
0
$ ls /nope
ls: cannot access '/nope': No such file or directory
$ echo $?
2A script can return its own exit status using exit:
exit 0 # success
exit 1 # general errorIf exit is omitted, the script returns the status of its
last command.
&&,
||, ;These connect commands and act on the exit status of the previous one.
| Operator | Meaning |
|---|---|
cmd1 ; cmd2 |
Run cmd1, then run cmd2 regardless. |
cmd1 && cmd2 |
Run cmd2 only if cmd1
succeeded (exit 0). |
cmd1 || cmd2 |
Run cmd2 only if cmd1
failed (non-zero exit). |
Examples:
mkdir /tmp/work && cd /tmp/work # cd only if mkdir worked
ping -c1 host || echo "host is down"
make && make install || echo "build failed"test Command and
[ … ]test evaluates a condition and returns exit status 0
(true) or 1 (false). It is the heart of if statements.
These two lines are identical:
test -f /etc/passwd
[ -f /etc/passwd ]Note the spaces inside the brackets —
[-f file] is wrong.
| Test | True if … |
|---|---|
-e FILE |
FILE exists. |
-f FILE |
FILE exists and is a regular file. |
-d FILE |
FILE exists and is a directory. |
-r FILE |
FILE is readable. |
-w FILE |
FILE is writable. |
-x FILE |
FILE is executable. |
-s FILE |
FILE exists and is not empty. |
-L FILE |
FILE is a symbolic link. |
| Test | True if … |
|---|---|
-z STRING |
STRING is empty (zero length). |
-n STRING |
STRING is not empty. |
STR1 = STR2 |
strings are equal. |
STR1 != STR2 |
strings differ. |
| Test | Meaning |
|---|---|
N1 -eq N2 |
equal |
N1 -ne N2 |
not equal |
N1 -lt N2 |
less than |
N1 -le N2 |
less than or equal |
N1 -gt N2 |
greater than |
N1 -ge N2 |
greater than or equal |
Pitfall: use -eq for numbers and
= for strings. Mixing them up is a common exam trap.
! — NOT-a — AND (inside [ ])-o — OR (inside [ ])&& / || between two separate
[ ] blocks (preferred):[ -f "$f" ] && [ -r "$f" ]if Statementif [ -f /etc/passwd ]; then
echo "passwd exists"
elif [ -f /etc/master.passwd ]; then
echo "BSD-style passwd file"
else
echo "no passwd file found"
fiKey points:
if must be closed by fi.then can be on the same line if separated by
;, otherwise on the next line.elif and else are optional.for and
whilefor — iterate over a
listfor name in alice bob carol; do
echo "Hello, $name"
doneLoop over files:
for f in *.txt; do
echo "Found: $f"
doneLoop over numbers using seq:
for i in $(seq 1 5); do
echo "Number $i"
doneseq generates a sequence of numbers:
$ seq 3 # 1 2 3
$ seq 2 5 # 2 3 4 5
$ seq 0 2 10 # 0 2 4 6 8 10 (start, step, end)C-style for (Bash only):
for ((i=1; i<=5; i++)); do
echo $i
donewhile — loop
while a condition is truecount=1
while [ $count -le 5 ]; do
echo "Count is $count"
count=$((count + 1))
doneuntil is the opposite — loops while the condition is
false:
until [ $count -gt 5 ]; do
...
doneread — Get Input
from the Userecho -n "Enter your name: "
read name
echo "Hello, $name"Shortcuts:
read -p "Enter your name: " name # built-in prompt
read -s password # silent input (no echo)
read first second third # split input into multiple varsread is also useful for reading a file line by line:
while read line; do
echo "Got: $line"
done < /etc/passwdexec — Replace
the Current Shellexec replaces the current shell process with the given
command. The shell does not return after exec finishes —
the running script ends.
exec /usr/bin/python3 myscript.py # the bash script is replaced by pythonA common use is redirecting all output of a script to a file:
exec > /var/log/myscript.log 2>&1
echo "this goes into the log file"After that exec line, all subsequent
output of the script is sent to the log.
A common scripting pattern: when something goes wrong, email root.
The mail command (also called mailx on some
systems) sends mail from the command line:
echo "Disk is almost full" | mail -s "ALERT" rootIn a script:
if [ $(df / | awk 'NR==2 {print $5}' | tr -d '%') -gt 90 ]; then
echo "Root filesystem is over 90% full" | mail -s "Disk Alert" root
fi-s sets the subject.#!/bin/bash
# backup.sh — back up /home if the destination directory exists
DEST="/backup"
if [ ! -d "$DEST" ]; then
echo "Destination $DEST does not exist" | mail -s "Backup failed" root
exit 1
fi
for user in $(ls /home); do
tar -czf "$DEST/${user}-$(date +%F).tar.gz" "/home/$user" \
&& echo "Backed up $user" \
|| echo "Failed: $user" | mail -s "Backup error" root
done
exit 0This single script touches almost every concept in this objective:
shebang, variables, if, for, command
substitution, &&/||, conditional mail,
and exit.
Keywords and commands:
forwhiletestifreadseqexec||&&Other things you must know:
#! shebang line$?) and exit N$(…) and backticks[ … ] syntax (with required spaces)What does the first line #!/bin/bash
do? It tells the kernel to use /bin/bash to
interpret the script.
A script needs to run only if the previous command
succeeded. Which operator do you use? &&.
For example: cmd1 && cmd2.
What is the exit status of a successful command?
0.
How do you check if a file exists and is
readable? [ -r filename ] (or
test -r filename).
What is the difference between = and
-eq in a test? = compares strings;
-eq compares integers.
What does $? contain? The exit
status of the most recently executed command.
You set SUID on a Bash script. Will it run with the
owner’s privileges? No. Linux ignores the SUID bit on shell
scripts. Use sudo instead.
What does $(date) do? Runs the
date command and substitutes its output in place.
How do you read a line of input from the user into a
variable named answer?
read answer.
Write a for loop that prints numbers 1
through 5 using seq.
bash for i in $(seq 1 5); do echo $i; done
What is the purpose of
exec > logfile 2>&1 at the top of a
script? Redirects all standard output and standard error of the
script to logfile.
Where would you place a script that should be available
to all users on the system? /usr/local/bin/ (or
/usr/local/sbin/ for admin scripts).