- Learn
- Computer Science & IT
- Information Technology
- Linux Basics
- Shell Scripting
Shell Scripting Basics
BeginnerLesson 4 of 7 in Linux Basics Course
What you will learn
- Turn a sequence of commands you type again and again into a script file, start it with the right interpreter line, and run it from any directory.
- Turn repeated commands into a script fileWrite commands into a plain text file in order, add comments, and start the file with a shebang line that names Bash as the interpreter.
- Run a script and choose where it livesMake a script executable, run it by path or through an interpreter, put it in a directory on the PATH, and know when a script can change your current shell.
- Turn repeated commands into a script file
- Keep text, numbers and command output in variables, read input from the user, and use quoting so that spaces and special characters reach commands unchanged.
- Create, read and export variablesAssign and use shell variables, choose good names, read input from the user, and pass variables to the programs a script starts.
- Quote words, variables and special charactersDecide between single quotes, double quotes and backslashes, and quote every variable expansion so that spaces and wildcards in values cannot break a command.
- Capture command output and calculate with integersStore a command's output with command substitution and do whole-number arithmetic, for example to build dated file names and keep counters.
- Create, read and export variables
- Read and set the exit status of commands and scripts, chain commands on success or failure, and find and stop errors before they cause damage.
- Read, set and chain exit codesUse the exit status that every command returns, end a script with a meaningful code, and run commands only when an earlier one succeeded or failed.
- Find errors and stop a script safelyCheck and trace a script to find mistakes, send error messages to standard error, and make a script stop on failures, unset variables and failed pipelines.
- Read, set and chain exit codes
- Test files, text and numbers, choose what to do with if and case, and repeat commands over lists, files and lines with for, while and until loops.
- Test files, strings and numbersWrite conditions with test, [ ] and [[ ]] that check whether files exist, compare text and compare whole numbers, and combine or negate them.
- Choose actions with if, elif, else and caseRun different commands depending on a condition with if, elif and else, and match one value against several patterns with case.
- Repeat commands over lists and files with forLoop over a list of words, the files that match a pattern, a range of numbers or a script's arguments, and handle file names with spaces.
- Repeat until a condition changes with while and untilRun commands while a condition succeeds or until it does, read a file line by line, and control loops with break, continue and sleep.
- Test files, strings and numbers
- Give a script its input on the command line, read the arguments inside the script, check them, and supply defaults so the same script works for many cases.
- Read positional parameters and the argument countUse
$0,$1to$9,${10},$#and"$@"to read the words given after a script's name, and useshiftto work through them. - Check arguments, show usage and set defaultsValidate the number and kind of arguments, print a usage message and exit with an error code, use default values, and handle simple options.
- Read positional parameters and the argument count
- Run scripts at fixed times or intervals without being logged in, with cron or with systemd timers, and check that scheduled runs happen and succeed.
- Schedule jobs with cronWrite crontab entries with the five time fields, edit and list them with crontab, and prepare scripts for cron's minimal environment.
- Schedule jobs with systemd timersPair a service unit with a timer unit, write calendar and interval schedules, enable the timer, and check the next run and the logs.
- Schedule jobs with cron
Shell Scripting study guide
1Write and run your first Bash scripts
A few commands become easier to repeat when they live in one script. You will give the file a Bash interpreter, keep its instructions readable, and choose how to start it. File permissions, command search paths and child shells explain several common surprises: why a script works with bash but fails with ./, why its name is not found, and why its directory changes do not move your terminal.
1.1 Turn repeated commands into a script file
A shell script saves a sequence of commands in a plain text file. Bash reads the commands in order, just as it reads commands you enter at a prompt. A script can repeat a task without someone typing each step, and it gives you one place to correct or improve that task.
Put the interpreter on the first line
The shebang tells Linux which program should read an executable script. Start a Bash script with #!/bin/bash. The #! must be the first two characters in the file: no spaces or blank line before them. Bash is the Bourne Again Shell. It is widely used as an interactive shell on Linux and also reads script files.
An interpreter is a program that reads and runs another language. The same mechanism lets #!/usr/bin/python3 select Python or #!/usr/bin/awk -f select awk. The rest of a Bash script must contain shell commands that Bash understands.
Create backup-notes.sh in a plain text editor such as nano or vi:
#!/bin/bash
# Keep a copy of the notes and show the backup folder.
mkdir -p ~/backup
cp ~/notes.txt ~/backup/
ls -l ~/backup
Bash first makes the destination directory, then copies the notes, then lists the destination. mkdir -p also succeeds when the directory already exists, so a later run can reuse it. The source file ~/notes.txt must exist for the copy to work. Saving the commands does not create that source file.
Make the file readable to people
The second line is a comment. Outside quoted text, # begins a comment at the start of a word; Bash ignores the rest of that line. Write comments about the purpose of a step, such as why a backup must be kept. Blank lines and indentation make groups of commands easier to see. Keep indentation consistent as you add conditions and loops.
The .sh ending helps a reader recognize a shell script. Linux does not require that ending, and adding it does not select Bash or grant execute permission. A file named backup-notes can run the same commands.
Match the shell to the language
| First line | How the interpreter is chosen |
|---|---|
#!/bin/bash | Uses Bash at the fixed path /bin/bash. |
#!/usr/bin/env bash | Uses env to find Bash through PATH. |
#!/bin/sh | Uses the system's sh, which may be a different shell. |
On Debian and Ubuntu, /bin/sh normally selects dash. Bash features such as [[ ... ]] fail there. A Bash script needs a Bash interpreter even if simple commands happen to work under either shell. Calling a file something.sh does not fix a mismatched shebang.
Recognize a line-ending problem
Windows editors can save a carriage return before each newline. On Linux, that invisible character may become part of the shebang path. An error such as /bin/bash^M: bad interpreter: No such file or directory points to this problem, even though /bin/bash exists.
Convert the file to Unix line endings with dos2unix backup-notes.sh, then save future scripts with Unix line endings in the editor. The commands in the file can be correct while the extra character prevents Linux from starting their interpreter.
Key Takeaways
- A script is plain text containing commands to run in order.
- The shebang must begin at the very start of the file and name the right interpreter.
- Use comments and indentation to make the purpose and structure clear.
- A
.shextension is a convention; it does not select a shell. - Unix line endings prevent a hidden carriage return from breaking the shebang.
1.2 Run a script and choose where it lives
A script can be read by Bash directly or started as a program. These routes differ in the permissions they need and in how the interpreter is chosen. The path you type also determines whether the shell searches for a command or uses one exact file.
Two ways to start the same file
bash backup-notes.sh
chmod u+x backup-notes.sh
./backup-notes.sh
The first command starts Bash and asks it to read the file. The script needs read permission; its own execute bit is not required. Bash treats the shebang as a comment because the interpreter has already been chosen.
The second command adds execute permission for the file's owner. In a permission string such as -rwxr--r--, the owner's x means executable. The third command runs the file as a program and uses its shebang. chmod +x is also common; without a named permission class, the umask limits which classes gain execute permission. chmod u+x explicitly targets the owner.
If ./backup-notes.sh reports "Permission denied," check execute permission and access to the path. If bash backup-notes.sh works, that does not establish that direct execution has the permissions it needs.
Understand the command search
./ means the current directory. When a command contains a slash, Bash follows that path. When you type a name without a slash, Bash searches the directories in PATH. The current directory is normally absent from that list, so being inside a script's directory is not enough to run it by name alone.
echo "$PATH"
command -v backup-notes.sh
type backup-notes.sh
PATH is a colon-separated list, such as /home/sam/.local/bin:/usr/local/bin:/usr/bin:/bin. The first matching executable wins. The last two commands help identify what a name will run, including an older copy elsewhere on PATH.
To make a personal script available by name, put it in a directory on your PATH. Many distributions add ~/bin or ~/.local/bin at login when that directory exists; check your actual PATH. /usr/local/bin is a common location for scripts shared by users and needs administrator rights to write to.
Choose a child shell or the current shell
Running ./settings.sh or bash settings.sh starts a child Bash process. Its variables and directory changes belong to that child. A script containing cd /var/log does not move the terminal that launched it. When it ends, you are still in the terminal's original directory.
Sourcing reads commands into the current shell:
source ./settings.sh
# Equivalent short form:
. ./settings.sh
Use this for a settings file meant to change your session. Sourcing needs read permission, not execute permission, and a shebang does not launch a separate interpreter. Sourcing a Bash settings file therefore assumes that the current shell understands Bash syntax.
Read the result of a run
Immediately after a script ends, echo $? shows its exit status. Unless the script uses exit with a chosen code, its status comes from its last command. A successful final listing can therefore hide an earlier failed copy; later error checks will let the script report the task's actual result.
Key Takeaways
bash filereads a script with Bash;./fileneeds execute permission and uses the shebang.- PATH is searched only for command names without a slash.
- Check a command name with
command -vortype. - Execution starts a child shell; sourcing can change the current shell.
2Store values in variables and quote them safely
Variables let the same commands work with changing paths, names and counts. Quoting keeps those values intact while Bash builds a command line, so a space in a name stays part of the name. You will read input, pass values to child programs, capture command output and calculate with integers. These pieces work together when a script builds a dated file name or keeps a running count.
2.1 Create, read and export variables
A variable gives a value a name so the same script can reuse it. Bash variables normally hold text, even when that text looks like a number. You assign the value once and expand the name wherever a command needs that value.
Assign without spaces
city=Lisbon
greeting="Good morning"
echo "$greeting from $city"
The output is "Good morning from Lisbon." There are no spaces around the assignment =. In city = Lisbon, Bash treats city as a command and the other words as arguments. Quotes around "Good morning" keep both words in one value.
Use letters, digits and underscores in variable names, with a letter or underscore first. Names are case-sensitive: file, File and FILE are different. Choose lowercase names such as backup_dir for values owned by your script. This helps avoid replacing environment variables such as PATH or HOME. Setting PATH=/data changes command lookup and can make ordinary commands stop being found.
Mark where a name ends
A dollar sign introduces an expansion. Braces show the boundary when more text immediately follows:
file=report
echo "${file}_old"
This prints "report_old." With "$file_old", Bash looks up a variable named file_old, not file followed by _old. Without set -u, an unset name expands to empty text. A misspelling can therefore produce an empty path or message with no immediate error.
A value such as count=5 stores the text "5." Adding text beside an expansion does not calculate: echo "$count+1" prints "5+1." Arithmetic expansion gives numbers a different role when calculations are needed.
Read a line of input
read -r -p "Your name: " name
echo "Hello, $name"
read stores the entered line in name. -p supplies the prompt and -r preserves backslashes. With the input "Lee Kim," the greeting is "Hello, Lee Kim." A script run without a person present needs its input from arguments, files or defaults; a prompt cannot supply a missing value reliably.
Export values for child programs
An ordinary shell variable belongs to the shell where it is set. Exporting marks it for inclusion in the environment copied to programs that shell starts:
export BACKUP_DIR=/mnt/usb
bash backup-notes.sh
The child Bash can now read BACKUP_DIR. If that child assigns a different value, the parent keeps its own value. Environment inheritance moves from parent to child; it does not send later edits back up.
| Name | Usual meaning |
|---|---|
HOME | Home directory |
USER | User name |
PWD | Current working directory |
PATH | Directories searched for command names |
env or printenv displays environment variables. unset name removes the variable named name; it does not assign an empty string. Under normal expansion both may look empty, but an unset-variable check can distinguish a missing variable from a set one.
Key Takeaways
- Assignments use
name=valuewith no spaces around=. - Variable names are case-sensitive; use lowercase names for your own values.
${name}marks the end of a variable name before adjoining text.read -rreads input while preserving backslashes.- Exported values are copied to child processes, never back to a parent.
unsetremoves a variable.
2.2 Quote words, variables and special characters
Bash reads a command before the program receives its arguments. Spaces separate words, and characters such as *, ?, $, ;, &, |, < and > have special roles. Quoting controls which of those roles remain active.
Choose what should expand
| Form | What Bash does |
|---|---|
'fixed text' | Keeps everything inside literal. |
"text with $name" | Preserves spaces and wildcard characters but expands variables, command output and arithmetic. |
\* | Treats the next character literally outside quotes. |
With HOME=/home/sam, compare:
echo 'Home: $HOME *'
echo "Home: \$HOME *"
The first prints "Home: $HOME *". The second prints "Home: /home/sam *". The asterisk stays literal in both, but only double quotes allow the variable to expand. A single quote cannot be placed directly inside a single-quoted string.
Inside double quotes, backslashes can protect a dollar sign, backtick, double quote or backslash. For example, echo "Price: \$5" prints "Price: $5". Outside quotes, echo 5 \* 3 prints "5 * 3", and touch my\ file.txt creates one file with a space in its name.
Follow an unquoted value through Bash
file="my report.txt"
cp $file backup/
First Bash expands the variable. Then word splitting divides its value at spaces, tabs and newlines. The command receives my, report.txt and backup/ as separate arguments. It tries to copy two source files instead of the one intended file.
If a resulting word contains wildcard characters, globbing then replaces it with matching file names. A stored value of *.txt can become several file arguments when expanded without quotes. These extra steps happen before cp starts; the program cannot recover your intended grouping.
cp "$file" backup/
Now the expanded value stays one argument, "my report.txt". The quote characters themselves are removed by Bash; they are instructions for parsing, not part of the path passed to cp. Put double quotes around variable and command substitutions whenever their full value should stay together.
Keep an empty value present
Quoting also matters when a value is empty. In [ $answer = yes ], an empty answer disappears, leaving an invalid test. [ "$answer" = yes ] passes an empty string as one argument, so the test is valid and false.
The same grouping rule applies to literal words. mkdir new folder creates two directories, while mkdir "new folder" creates one. Quoting an entire path is useful whether its space was typed directly or came from a variable.
Combine literal and expanded text
Adjacent quoted parts with no separating space form one word. With price=8, echo 'Price: $'"$price" prints "Price: $8". The fixed part is single-quoted and the changing part is double-quoted. A search pattern can likewise be kept fixed: grep 'cost: \$[0-9]' prices.txt passes its pattern without shell expansion, leaving grep to interpret the pattern.
Key Takeaways
- Single quotes keep text literal; double quotes allow expansions while keeping values together.
- Unquoted expansions undergo word splitting and then filename expansion.
- Quotes preserve an empty value as an argument.
- A backslash can make one special character literal.
- Adjacent quoted parts form one word when no space separates them.
2.3 Capture command output and calculate with integers
Commands often produce a value that another step needs. Bash can capture that output, place it inside text, or use it in a calculation. Command substitution and arithmetic expansion have similar punctuation but do different jobs.
Capture standard output
today=$(date +%F)
cp notes.txt "notes-${today}.txt"
$(...) runs a command and replaces the substitution with its standard output. On 2026-10-22, today holds "2026-10-22" and the copy is named notes-2026-10-22.txt. A year-month-day date keeps file names in date order when sorted by name. You can also put the substitution directly in the name: "notes-$(date +%F).txt".
Trailing newline characters are removed from captured output. The older backtick form also captures output, but $(...) is easier to read and nest. Quote a substitution used as a command argument, such as "$(date +%F)", to keep its result together.
Command substitution captures standard output only. Errors sent to standard error still go to their usual destination. result=$(some_command 2>&1) combines both streams inside the capture when that is what the script needs. Mixing errors into a value intended to contain a number would make later arithmetic fail.
Capture a count without a file name
lines=$(wc -l < access.log)
echo "Lines: $lines"
If the file contains 342 newline characters, wc supplies the count 342. Redirecting the file into wc makes its output contain only the count; wc -l access.log also prints the file name. A numeric-looking result can then be reused, provided the command succeeded.
Calculate with whole numbers
$(( ... )) performs arithmetic expansion:
echo "$(( 17 / 5 ))"
echo "$(( 17 % 5 ))"
The results are 3 and 2. Integer division drops the remainder; % returns it. Addition, subtraction and multiplication use +, - and *. Variable names can appear without a dollar sign inside the expression: total=$(( price * qty )).
With a=5, echo "$a+3" prints "5+3" as text. echo "$(( a + 3 ))" calculates and prints 8. The extra parentheses select arithmetic; $(a + 3) would instead try to run a command named a.
A counter begins with a value such as count=0 and updates with count=$(( count + 1 )). Bash also offers (( count++ )); this is an arithmetic command with its own exit status, which matters in scripts using error options.
Keep the needed precision
Bash arithmetic uses integers. 7 / 2 gives 3, and a decimal value such as 2.5 causes an arithmetic syntax error. A separate tool can calculate decimals:
echo "scale=2; 7 / 2" | bc
This produces 3.50. With integer arithmetic, multiply before dividing when the product retains a fraction you still need. For 45 out of 60, $(( 45 * 100 / 60 )) gives 75. Dividing first gives zero for 45 / 60, so $(( 45 / 60 * 100 )) remains zero; the lost fraction cannot be recovered by later multiplication.
Key Takeaways
$(command)captures standard output and removes trailing newlines.$((expression))calculates with integers.- Use
/for integer division and%for its remainder. - A counter stores its updated value after each increment.
- Multiplying before dividing can prevent early loss of integer precision.
- Use a tool such as
bcwhen a decimal result is required.
3Report success and failure with exit codes
A script needs a result that both people and programs can understand. Printed output explains what happened, while an exit status lets another command decide whether to continue. You will save that status, check dependent steps and keep pipeline failures visible. Syntax checks, execution traces and error options help find the cause of a failed run before later commands make its effects harder to see.
3.1 Read, set and chain exit codes
A command reports its result in two ways: it may print text, and it returns an exit status. Programs use that status to decide what happens next. A script can print a reassuring message and still have failed, or print nothing and succeed.
Read a status before it changes
An exit status is a whole number from 0 to 255. Zero means success. A nonzero value reports failure or another documented result. grep, for example, uses 0 for a match, 1 for no match and 2 for an error. A search finding no match can be a normal outcome for your task even though its status is nonzero.
$? holds the status of the most recent command:
grep -q root /etc/passwd
status=$?
echo "Search status: $status"
The assignment saves the result before another command replaces it. In ls /missing; echo $?; echo $?, GNU ls returns 2 for the inaccessible command-line path. The first echo prints 2, then succeeds. The second prints 0, the status of that echo, not the original listing.
End with a useful result
exit 1 ends a script immediately and reports 1 to its caller. exit 0 reports success. An exit without a number uses the previous command's status; reaching the end normally uses the last command's status. Choose codes within 0–255: exit 256 wraps to 0 and would incorrectly report success.
Some statuses come from Bash itself:
| Code | Meaning |
|---|---|
| 127 | The command was not found. |
| 126 | The command was found but could not be executed. |
128 + n | The command ended because of signal number n. |
Ctrl+C sends signal 2, so a command terminated that way commonly reports 130. Ordinary programs also document their own codes; 1 and 2 do not have one universal meaning across all tools.
Chain dependent commands
mkdir -p ~/backup && cp notes.txt ~/backup/
cd /data/reports || exit 1
&& runs the next command only after success. Here the copy depends on creating or finding the backup directory. || runs the next command only after failure. Here a failed directory change stops the script before later commands use the wrong working directory.
true returns 0; false returns 1. A leading ! reverses a command's success or failure. ! grep -q ERROR app.log succeeds when the search finds no matching line, although it also succeeds if grep fails with an error; use explicit status checks when those outcomes must be distinguished.
Check a whole pipeline
grep ERROR missing.log | wc -l
By default, a pipeline has the status of its final command. grep fails on the missing file, but wc reads an empty stream, prints 0 and succeeds. The pipeline's status is therefore 0.
set -o pipefail makes a pipeline fail when any member fails. It reports the rightmost nonzero status, or 0 if all commands succeed. This makes upstream errors visible to if, &&, || and the caller. A scheduler can then distinguish a successful report from one that never read its input.
Key Takeaways
- Exit status 0 means success; nonzero results have meanings documented by each program.
- Save
$?immediately if you need to use it after another command. exitsupplies a result to the script’s caller.&&continues after success, and||continues after failure.- Without
pipefail, a pipeline reports only its last command’s status.
3.2 Find errors and stop a script safely
Bash normally continues after a command fails. If the next command depends on that work, continuing can turn one small failure into a larger problem. Check dependent steps explicitly, then use syntax checking and tracing to find why a run behaves differently from your plan.
Stop before a failed step spreads
cd /backups || exit 1
Only commands after a successful directory change can now run. This matters before file operations: if a directory change fails and the script continues, relative paths still refer to the old directory. Check the destination before running commands that change or delete files.
An error message and a failure status serve different readers:
echo "Error: $file not found" >&2
exit 1
The message goes to standard error, leaving standard output available for data. With ./report.sh > report.txt, the message remains visible on the terminal while normal output enters the file. The nonzero status lets another program detect the error without reading the sentence.
Check syntax, then trace execution
| Command | What it does |
|---|---|
bash -n script.sh | Reads syntax without executing commands. |
bash -x script.sh | Runs the script and prints expanded commands before executing them. |
A missing fi, done or closing quote is a syntax problem. bash -n can locate it, often through an "unexpected end of file" message. A syntactically valid but misspelled file path needs an execution check.
Tracing prefixes commands with +. A line such as cp "$src" "$dest" reveals the actual paths it received. set -x and set +x turn tracing on and off around one section.
Understand the error options
set -euo pipefail combines three controls:
-eexits after an unhandled command failure.-ureports an unset variable as an error.pipefailmakes failed pipeline members affect the pipeline status.
-e leaves room for expected failures in if, while and until conditions, negated commands, and nonfinal commands in && or || lists. Without pipefail, a failure before the final pipeline command does not trigger an exit. Explicit checks remain useful because they state what the script should do when a particular step fails.
An arithmetic command also reports true or false. (( count++ )) returns status 1 when the old value is 0, so an unhandled use under -e can stop the script even though the increment happened. The assignment count=$(( count + 1 )) avoids that arithmetic-command status.
-u catches a typo such as $filname when only filename was set. It does not reject a variable that exists but is empty. ${dir:?not set} checks for missing or empty dir at that expansion. An expected no-match result can be handled with a test; command || true suppresses every failure of that command, so use it only when all those failures are acceptable.
Read messages and clean up
"Command not found" points to a name, installed program or PATH problem. "Permission denied" points to execution or file access. "Unary operator expected" commonly comes from an empty unquoted value in [ ]; "integer expression expected" means a numeric test received text.
An EXIT trap installs cleanup, such as trap 'rm -f "$tmpfile"' EXIT, after tmpfile has been assigned. The quoted command runs when Bash exits, including an explicit exit or an error exit under -e. ShellCheck can also inspect scripts for common quoting and logic mistakes before a run.
Key Takeaways
- Check a step before running commands that depend on it.
- Write diagnostics to standard error and report failure with a nonzero status.
bash -nchecks syntax;bash -xexecutes and traces.set -ehas exceptions for tested failures.set -ucatches unset variables, while${name:?message}also rejects empty values.- An EXIT trap can remove a temporary file when the script ends.
4Make decisions and repeat work
A useful script chooses actions and repeats them over changing input. Tests turn questions about files, text and numbers into success or failure. Branches select an action, while loops apply commands to lists or keep going until a condition changes. You will trace the value used on each pass and preserve spaces, records and counters as data moves through the script.
4.1 Test files, strings and numbers
A condition in Bash is a command whose status means true or false. Tests let you ask about paths, text and numbers. Choose an operator for the kind of value you have; the same printed characters can have different meanings as text and as a number.
Give the test its own words
test -f notes.txt and [ -f notes.txt ] ask whether a path refers to an existing regular file. [ is a command, and its final argument must be ]. Spaces separate its arguments:
[ -f "$file" ]
Without the space after [, Bash may look for a command named [-f. In [ "$a"="$b" ], the missing spaces around = form one nonempty word, which tests true even if the values differ. Operators must be separate arguments.
Choose a file test
| Operator | True when the path… |
|---|---|
-e | Exists. |
-f | Is an existing regular file. |
-d | Is an existing directory. |
-r, -w, -x | Is readable, writable or executable by the current user. |
-s | Exists and has a size greater than zero. |
-L | Is a symbolic link. |
For a directory named logs, -e and -d succeed while -f fails. File tests normally follow a symbolic link to its target; -L checks the link itself. A broken link can therefore satisfy -L and fail -e.
Compare text or whole numbers
String tests use = and != for equality and inequality. -z checks for zero length and -n for nonzero length. [ "$answer" = yes ] matches exactly "yes"; "Yes" and "yes " are different text. Double quotes keep an empty value in the test.
Integer comparisons use -eq, -ne, -lt, -le, -gt and -ge: equal, not equal, less than, less than or equal, greater than, and greater than or equal. [ "$count" -gt 100 ] asks whether the number exceeds 100.
[ 07 -eq 7 ]
[ 07 = 7 ]
The numeric comparison succeeds because the values are equal. The string comparison fails because one text begins with an extra zero.
Do not use a bare > for a numeric test inside [ ]. In [ 10 > 9 ], Bash redirects output to a file named 9 and tests the remaining nonempty word. In Bash [[ 10 < 9 ]], the operator compares strings, so the initial character 1 sorts before 9. Use -lt or -gt for numeric comparisons, or Bash arithmetic such as (( count > 100 )).
Combine conditions
[ ! -d "$dir" ] succeeds when the path is not an existing directory; it can be a regular file or be missing. [ -f "$f" ] && [ -r "$f" ] succeeds only when both tests succeed. Separate tests joined by || succeed when at least one succeeds.
Bash also has the conditional keyword [[ ... ]]. It prevents word splitting of its variable expansions and allows && and || inside. An unquoted pattern on the right of == performs pattern matching: [[ $file == *.log ]] matches "app.log". Quoting that pattern would make it literal. This syntax needs Bash; dash does not implement it.
Key Takeaways
- A test reports true with status 0 and false with status 1.
- Put spaces around
[and its operators, and close with]. - File tests distinguish existence, type and access.
- String equality compares text; numeric operators compare integer values.
- Use
-gtand-ltfor numbers, not bare redirection symbols. - Negation reverses a test;
&&requires both conditions. - Bash
[[ ... ]]supports patterns and avoids word splitting.
4.2 Choose actions with if, elif, else and case
Conditions become useful when they select actions. An if block chooses a branch from command results. A case block selects a branch by matching one value against patterns. Both make the available actions visible in the script.
Let a command choose a branch
if grep -q ERROR app.log; then
echo "Errors found"
else
echo "No successful match"
fi
if runs the command and enters the first branch when it returns 0. Otherwise it enters else. The search itself is the condition; there is no need to run it, print something, and then inspect $?. A grep error is also nonzero, so this example describes the else branch as no successful match, not proof that the file contains no errors.
then must begin a new command: put it on the next line or after a semicolon. fi closes the block. A missing fi often produces an end-of-file syntax error because Bash is still waiting for the block to close.
Order overlapping conditions
elif adds a test that runs only when earlier tests failed. For a whole-number disk-use percentage:
if [ "$usage" -ge 90 ]; then
echo "CRITICAL: disk ${usage}% full"
elif [ "$usage" -ge 75 ]; then
echo "WARNING: disk ${usage}% full"
else
echo "OK: disk ${usage}% full"
fi
With usage=82, the first test fails and the second succeeds, producing "WARNING: disk 82% full". With 95, the first branch runs and the warning branch is skipped. If the 75 test came first, 95 would match it and never reach the critical test. Arrange overlapping ranges so the more restrictive condition gets its turn.
Only the first successful branch runs. else needs no condition and handles what remains. Either elif or else can be omitted when the task does not need it.
Match one value with case
case "$1" in
start) echo "Starting" ;;
stop) echo "Stopping" ;;
y|Y|yes) echo "Confirmed" ;;
*) echo "Usage: $0 start|stop|y|Y|yes" >&2; exit 2 ;;
esac
Each pattern ends with ), each branch ends with ;;, and esac closes the block. The | within a pattern gives alternatives. The final * is the catch-all and belongs last because it matches every value.
case uses file-style patterns, not regular expressions. *.jpg matches a value ending in .jpg. [0-9]* matches a value whose first character is a digit; the * can then match any following text, so it also matches "7draft". It does not prove that an entire value contains digits only.
Choose the structure that fits
Use case when one input such as a subcommand or answer has several named forms. Use if when conditions examine different things, such as whether a file exists and whether a count exceeds a limit. A pattern match asks about the characters in a value; a file test asks about the path on disk. A name ending in .log can match a pattern even when no such file exists.
Key Takeaways
ifbranches on a command’s exit status; any command can be a condition.eliftests run only after earlier conditions fail.- Order overlapping thresholds from the stricter one to the broader one.
caseselects the first matching pattern and uses;;to end each branch.- A pattern match does not establish that a file exists.
4.3 Repeat commands over lists and files with for
A for loop gives the same body of commands a new value on each pass. The list can contain literal words, matching file names, a number range or the script's arguments. Bash assigns one list item to the loop variable before running the body.
Walk through a fixed list
for host in web1 web2 db1; do
echo "Checking $host"
done
The first pass has host=web1, the second host=web2, and the third host=db1. The loop prints three lines. do opens the body and done closes it. The variable remains available after the loop, holding its last assigned value.
A loop can also accumulate a result:
total=0
for n in 4 8 15; do
total=$(( total + n ))
done
echo "$total"
The stored total changes from 0 to 4, then 12, then 27. Initialize the total before the loop so the first addition has a defined starting value.
Loop over matching files
for f in *.log; do
[ -f "$f" ] || continue
gzip -- "$f"
done
Bash expands the glob into matching names before the loop body runs. Each matched file name remains one list item even if it contains spaces. Quoting "$f" inside the body keeps that item together when it is passed to gzip. Here the regular-file test also skips directories named with a .log ending.
With Bash's default glob settings, an unmatched *.log stays literal. A loop without a guard would run once on the characters *.log and the command would fail. [ -e "$f" ] || continue skips nonexistent paths; use -f when only regular files are wanted.
Do not build a filename loop from $(ls). Its output is text, so word splitting can turn "my report.txt" into two list items. A glob already produces the file names with their boundaries intact.
Build a new name from each old one
for f in *.jpeg; do
[ -f "$f" ] || continue
mv -- "$f" "${f%.jpeg}.jpg"
done
${f%.jpeg} removes the ending .jpeg from the value. For "beach day.jpeg", the destination becomes "beach day.jpg". The two quoted arguments keep both paths intact. -- tells mv that subsequent arguments are names, even if one begins with -. Use a destination folder with no conflicting names when trying the example, since mv can replace an existing destination.
Count through a range
for i in {1..5} supplies 1 through 5. Bash expands the braces before expanding variables, so {1..$n} does not produce a variable-length range. For a stored limit, use the arithmetic form:
for (( i = 1; i <= n; i++ )); do
echo "$i"
done
The first expression initializes the counter, the middle tests it before each pass, and the last updates it. for i in $(seq 1 "$n") also produces a number list for a suitable positive integer limit. The intentional unquoted substitution here supplies separate numbers.
For arguments, for arg in "$@" supplies one item per original argument. The shorter for arg; do means the same thing, including preservation of spaces inside each argument.
Key Takeaways
- A
forloop runs its body once per list item. - Globs provide whole file names; quote the loop variable in the body.
- An unmatched glob stays literal under Bash’s default settings, so guard file loops.
- Avoid filename loops built from
$(ls). - Brace ranges are fixed before variable expansion; use an arithmetic loop for a variable limit.
"$@"preserves the original argument boundaries.
4.4 Repeat until a condition changes with while and until
Some repetition ends when a condition changes, not when a list runs out. while and until test before every pass. A while body runs while its condition succeeds; an until body runs while its condition fails. Either loop can run zero times.
Update what the condition depends on
i=1
while [ "$i" -le 3 ]; do
echo "Attempt $i"
i=$(( i + 1 ))
done
The condition first sees 1, then 2, then 3. The body prints those three attempt numbers and increases the counter each time. After the update to 4, the next test fails and the loop ends. If the update were missing, i would stay at 1 and the loop would keep running. Ctrl+C stops a running foreground loop.
To wait for a file:
until [ -f /tmp/export.done ]; do
sleep 5
done
If the file already exists, the loop body never runs. Otherwise sleep 5 pauses five seconds before the next check. A pause prevents the loop from testing continuously and wasting processor time.
Read records as lines
while IFS= read -r line; do
echo "Host: $line"
done < hosts.txt
read supplies one line on each pass and returns a failing status at end-of-file. IFS= preserves spaces at the start and end of the line; -r keeps backslashes. The input redirection after done feeds the whole loop, not just its final command.
A line such as "web1 production" stays one value. for line in $(cat hosts.txt) would split at spaces, tabs and newlines, treating those two words as separate iterations. That form cannot preserve arbitrary line-based records.
Keep changes after the loop
A pipe into a loop, such as cat hosts.txt | while read -r line; do ...; done, normally runs the loop in a child process in Bash. A counter changed there is lost to the parent when the child ends. Redirect the file into the loop when later commands need its accumulated values:
count=0
while IFS= read -r line; do
count=$(( count + 1 ))
done < hosts.txt
echo "$count"
This processes newline-terminated lines and keeps the updated count in the current shell. No pipeline creates a child for the loop.
Handle the final record and early exits
A file can end with text but no final newline. read stores that last text yet reports failure, so a plain while read does not enter its body for that record. Add a second test to accept the stored nonempty text:
while IFS= read -r line || [ -n "$line" ]; do
echo "Host: $line"
done < hosts.txt
break exits the loop immediately. continue skips the remaining commands in the current pass, then returns to the next condition check. A while true loop therefore needs a break, an exit or a signal to end; it can support a menu that breaks when the input is "q". Place an update before any continue that would otherwise skip it in a counter loop, or that pass could repeat forever.
Key Takeaways
whilerepeats after success;untilrepeats after failure.- Both loops test before entering their bodies.
- Update a counter or pause a polling loop so it makes progress without busy waiting.
IFS= read -rpreserves spaces and backslashes in line-based input.- Redirect a file into the loop to keep changed variables in the current shell.
- An extra nonempty-string test preserves a final record without a newline.
breakleaves a loop;continueskips to its next pass.
5Pass arguments to scripts
Command-line arguments let one script serve many tasks without editing its code. You will read individual values, preserve a complete argument list and consume leading options. Before work begins, count and validate the inputs, explain a correct call and supply defaults for optional values. The result can run at a prompt or unattended, with clear messages and an exit status when input is unusable.
5.1 Read positional parameters and the argument count
Arguments turn a script with fixed input into a reusable command. Bash places the words after the script name into positional parameters. Their boundaries are set by the calling shell, so quotes matter both when the script is called and when it uses those values.
Identify the parameters
./backup.sh /home/sam/docs /mnt/usb
Inside the script, $0 is ./backup.sh, $1 is /home/sam/docs, and $2 is /mnt/usb. $# is 2, the number of arguments. The script can use cp -r "$1" "$2" to copy the source folder to the destination without hard-coding either path.
$0 describes the name used to start the script; it is not counted in $#. With ./greet.sh Ana "Lee Kim", the first argument is "Ana" and the second is "Lee Kim". Without the quotes around the second name, the script would receive three arguments.
Use braces after the ninth argument
$1 through $9 name the first nine positional parameters. The tenth needs ${10}, and later positions follow the same form. $10 means the expansion of $1 followed by the literal character 0. If the first argument is "Ana", $10 becomes "Ana0" even when a tenth argument exists.
An omitted parameter expands to empty text under normal settings. With two arguments, $3 is missing; under set -u, expanding it causes an unset-variable error. Check the count before reading required positions, or use an expansion that supplies a default.
Preserve all the argument boundaries
Suppose a script receives "a b" and c. Compare:
| Expansion | Words passed onward |
|---|---|
"$@" | Two: "a b" and "c". |
"$*" | One: "a b c", with the usual space separator. |
$@ | Three: "a", "b" and "c" under normal splitting. |
"$@" is the usual choice for forwarding arguments:
for arg in "$@"; do
echo "Argument: $arg"
done
The loop runs twice for that call, once per original argument. With no arguments it runs zero times. Double-quoting "$@" has a special meaning: each positional parameter stays a separate word, so the quotes do not join the entire list into one word.
"$*" joins values using the first character of IFS, which is normally a space. That can be useful for a single display string, but it loses the individual boundaries a later command may need.
Consume parameters with shift
shift removes the first positional parameter, moves the later ones down and decreases $#. In the backup call, one shift makes $1 become /mnt/usb and the count become 1. shift 2 removes the first two parameters at once.
This supports a loop that examines $1, handles it, then shifts before the next pass. Test that arguments remain with [ "$#" -gt 0 ]; otherwise the script can read a missing value or try to shift beyond the available arguments. Shifting does not change $0, and it cannot change the argument list of the parent shell that launched the script.
Key Takeaways
$0is the invocation name, while$1onward hold arguments.$#counts arguments, with quoted spaces kept inside one argument.- Use
${10}for the tenth argument;$10is$1followed by0. "$@"preserves arguments separately, while"$*"joins them.shiftremoves leading parameters and changes the remaining positions.
5.2 Check arguments, show usage and set defaults
A script should establish that its input is usable before it starts work. Check the number of arguments, then check their meaning. Optional inputs can have defaults; missing required inputs need a clear diagnostic and a nonzero exit status.
Explain the expected call
if [ "$#" -ne 2 ]; then
echo "Usage: $0 SOURCE DEST" >&2
exit 2
fi
This checks the count before expanding $1 or $2, so it also works under set -u. The usage message goes to standard error. Capitals name values the user supplies, [DEST] would mean an optional destination, and FILE... means one or more file arguments. A help option such as -h normally prints usage and exits with 0 because asking for help is successful use.
An argument count alone cannot tell whether a source is usable. [ -d "$1" ] checks for an existing directory. A regular-file check can select files, but the operation itself still needs a result check: permissions or other errors can prevent a read.
Use a default or require a value
logdir=${1:-/var/log}
src=${1:?Usage: backup.sh SOURCE [DEST]}
The first form uses /var/log when the first parameter is missing or empty. The second stops a noninteractive script in either case and prints its message on standard error. The colon is part of both forms. A default is a value used for this expansion; ${1:-...} does not add an argument to the positional parameter list.
Keep a record of partial failure
A file-counting script can continue after a bad input while still reporting that the whole task was incomplete:
#!/bin/bash
if [ "$#" -eq 0 ]; then
echo "Usage: $0 FILE..." >&2
exit 2
fi
status=0
for f in "$@"; do
if [ -f "$f" ]; then
if lines=$(wc -l < "$f"); then
echo "$f: $lines lines"
else
echo "$f: could not count lines" >&2
status=1
fi
else
echo "$f: not a regular file" >&2
status=1
fi
done
exit "$status"
For notes.txt missing.txt, the script prints the readable file's count and reports the missing file, then exits with 1. The assignment is tested directly, so a read failure does not become a successful empty count. Later success never resets the stored failure flag to 0.
Handle simple options before file names
verbose=0
while [ "$#" -gt 0 ]; do
case "$1" in
-v) verbose=1; shift ;;
-h) echo "Usage: $0 [-v] FILE..."; exit 0 ;;
--) shift; break ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) break ;;
esac
done
Each recognized flag is consumed with shift. The first ordinary argument ends option parsing; -- also ends it and is itself consumed. The remaining "$@" contains the file names, including a name beginning with - after --. Bash's getopts supports fuller short-option parsing.
An unattended script needs all required input from arguments, defaults or configuration. read can block waiting for a terminal or fail immediately at end-of-file. Replacing a missing argument with a prompt would leave a scheduled job without a dependable way to proceed.
Key Takeaways
- Validate the count before expanding required arguments.
- A usage message names required, optional and repeatable inputs.
${1:-value}provides a default;${1:?message}rejects a missing or empty value.- Check the operation’s result even after checking the input’s file type.
- Preserve an overall failure status when continuing across several inputs.
- Consume options with
shiftand honor--as the end of options.
6Schedule scripts to run automatically
An unattended script needs a schedule and a way to inspect its results. Cron expresses matching clock times in one line; systemd pairs a service with a timer. You will read both forms, supply the environment the job needs and keep its output available. Checking the next activation and the last task result separately helps distinguish a schedule that is active from work that actually succeeded.
6.1 Schedule jobs with cron
Cron starts commands at matching clock times. Each user's crontab stores that user's schedule, and jobs run as that user. A scheduled script needs both a correct time expression and an environment that provides its paths, inputs and output destination.
Read the five fields
Use crontab -e to edit your table and crontab -l to inspect it. crontab -r deletes the entire table without asking; use the editor to remove a single job.
| Field, in order | Values |
|---|---|
| Minute | 0–59 |
| Hour | 0–23 |
| Day of month | 1–31 |
| Month | 1–12 |
| Day of week | 0–7; 0 and 7 mean Sunday |
30 2 * * * /home/sam/bin/backup.sh
This runs every day at 02:30. Asterisks accept every value in a field. 0 9 * * 1-5 selects 09:00 on weekdays, and 0 0 1 * * selects midnight on the first day of each month. Times use the system's time zone unless the cron configuration specifies another supported zone.
Combine ranges, lists and steps
A comma lists values: 1,15 in the day-of-month field selects the first and fifteenth. A hyphen gives a range, and / introduces a step. */15 in the minute field selects 0, 15, 30 and 45.
*/20 8-10 * * * selects three minutes in each of three hours: nine daily runs, starting at 08:00 and ending at 10:40. Steps restart within their field. */45 runs at minute 0 and minute 45 of each hour; the gap to the following hour's minute 0 is only 15 minutes.
When both day-of-month and day-of-week are restricted, either match is enough. 0 12 13 * 5 selects noon on the 13th and on Fridays, not only Friday the 13th. The other fields still need to match.
Special strings replace all five fields: @hourly, @daily, @weekly, @monthly, @yearly and @reboot. @daily means midnight, while @reboot runs once at startup.
Supply paths and keep the output
Cron normally uses /bin/sh for its command line, a short PATH, and no interactive shell startup settings. An executable script's own Bash shebang still selects Bash. Use absolute paths for the script and its files, or set PATH at the top of the crontab.
30 2 * * * /home/sam/bin/backup.sh >> /home/sam/backup.log 2>&1
The job appends both output streams to the log. Without redirection, cron sends output by mail when mail is configured, including through MAILTO; otherwise output may be lost. A percent sign has a special role in a crontab command: it starts a newline and subsequent input. Escape a date format there, as in date +\%F. A date command inside a separate script uses ordinary %F.
Distinguish system schedules and catch-up jobs
/etc/crontab and files in /etc/cron.d/ add a user field after the five time fields. For example, 30 2 * * * root /usr/local/bin/backup.sh runs as root. Do not add that user field to a personal crontab.
Many distributions also run eligible executables in /etc/cron.daily/, /etc/cron.weekly/ and /etc/cron.monthly/. Debian's default run-parts naming rules skip names containing periods, so use backup-notes, not backup-notes.sh, there. Cron misses times that pass while the computer is off. Anacron can catch up periodic daily, weekly or monthly jobs after startup.
Key Takeaways
- A personal crontab has five time fields followed directly by a command.
- Lists, ranges and steps select values within each field.
- Restricted day-of-month and day-of-week fields use an either-match rule.
- Use absolute paths and an output log for unattended jobs.
- Escape
%in a crontab command, including date formats. - System crontabs add a user field; periodic folders also enforce eligibility rules.
- Cron misses runs while the machine is off; anacron supports periodic catch-up.
6.2 Schedule jobs with systemd timers
Systemd separates the work from its schedule. A service unit describes the command to run. A timer unit describes when to start it. By default, backup.timer activates backup.service, using the shared base name to connect the pair.
Write the service and the calendar schedule
Place these system units in /etc/systemd/system/:
# backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Type=oneshot suits a script that runs once and ends. Use an absolute command path and make the script executable with a Bash shebang. The timer supplies the schedule:
# backup.timer
[Unit]
Description=Run backup.service daily at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
OnCalendar matches dates and clock times. Here the wildcard date accepts every day. Persistent=true enables catch-up after the timer was inactive: if at least one calendar run was missed, it starts the service once on reactivation. It does not replay every missed run.
Load and activate the timer
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
Reload after creating or changing unit files. enable --now starts the timer immediately and enables it for future boots. Enabling only the service does not create a schedule. Test the work separately with sudo systemctl start backup.service.
Choose clock time or elapsed time
| Setting | Schedule |
|---|---|
OnCalendar=Mon..Fri *-*-* 09:00:00 | Weekdays at 09:00 |
OnCalendar=*:0/15 | Minutes 0, 15, 30 and 45 each hour |
OnBootSec=10min | Ten minutes after boot |
OnUnitActiveSec=1h | One hour after the service was last activated |
Calendar expressions can use daily for midnight each day or weekly for Monday at midnight. Check an expression with systemd-analyze calendar "Mon..Fri 09:00" to see its interpretation and next occurrence.
The last two settings are monotonic timers: they measure elapsed time from an event. Combine them for a first run after boot and later hourly activations. Persistent=true applies to calendar timers; it does not add calendar catch-up to monotonic timers.
Inspect scheduling and results separately
systemctl list-timers shows the next and last activations. systemctl status backup.service shows the service's result, and journalctl -u backup.service shows its output and errors. A listed next run establishes that a schedule is active, not that the script's previous task succeeded.
Timers have a firing window. AccuracySec defaults to one minute; AccuracySec=1s narrows the coalescing window. RandomizedDelaySec deliberately adds a random delay to spread work across machines. These settings do not guarantee an exact start second under load.
Run a user's own timer
User unit files live in ~/.config/systemd/user/, and commands use systemctl --user, including daemon-reload, enable --now and list-timers. A user manager normally runs with that user's session. An administrator can enable lingering with loginctl enable-linger username to keep it available without a login.
Cron needs a compact time line and works without systemd. Timers add a service result and journal, calendar catch-up and more control. Neither normally loads ~/.bashrc; both need scripts that work with explicit paths and no interactive prompts.
Key Takeaways
- The service says what to run; the timer says when.
- Reload changed units and enable the
.timerwith--now. - Calendar timers follow clock time; monotonic timers measure from events.
Persistent=truecatches up missed calendar work once on reactivation.- Check both the timer’s next activation and the service’s status and journal.
- User timers need a running user manager, with lingering for runs without a session.
Shell Scripting examples
1Save a repeatable folder report
9 steps
- 1IntroductionGive a repeated task one home
You often check where you are, list a folder and print a completion message. Saving the commands makes their order repeatable.
A script is a plain text file read by an interpreter. You will create a small report, select Bash explicitly and run it in a child shell. The file’s name, execute permission and location each answer a different question about starting it.
- 2ExampleWrite the commands in order
Save this as
folder-report.shin a plain text editor:#!/bin/bash pwd ls echo "Report finished"The shebang selects Bash when the file is executed directly. The comment explains the task but does not run. Bash then runs
pwd,lsandechoin that order. The.shending helps a reader recognize the file; it does not select the interpreter. - 3ChallengeCheck what selects the language
A colleague changes the first line to
#!/bin/shand later adds[[ ... ]]. The file still ends in.sh. Why can it fail?---reveal---
The shebang selects
sh, which can bedashand may not understand Bash’s[[ ... ]]. The extension does not choose the language. Restore a Bash shebang for Bash features, with#!at the very start of the first line. - 4ExplanationRead the file with Bash first
Run
bash folder-report.sh. This starts Bash and passes it the readable script file; execute permission on the script is not required. The chosen interpreter is already Bash, so the shebang does not make this choice.You should see a path, a listing and the message "Report finished". The relative paths used by commands inside the script are resolved from its working directory, not automatically from the script file’s folder.
- 5ExampleAllow direct execution
Add execute permission for the owner and run the exact path:
chmod u+x folder-report.sh ./folder-report.shThe
./names the current directory. Direct execution uses the file’s shebang. If the file is readable but not executable, the earlierbashcommand can work while this command fails with "Permission denied." Permission and interpreter selection are separate parts of the launch. - 6TipFind the script by name
Typing
folder-report.shwithout a slash asks Bash to search PATH. The current directory normally is not on that list.A personal bin directory lets you run the script from other folders when it is on PATH. Inspect
echo "$PATH"and usecommand -v folder-report.shto see which executable that name selects. If an old copy comes first in PATH, editing a newer copy elsewhere will not change what this name runs. - 7ChallengeKeep a directory change in the right shell
The script now starts with
cd /var/log. After./folder-report.shfinishes, has your terminal moved there? How could a settings file move it?---reveal---
The executed script runs in a child shell, so its directory change does not move the parent terminal. Sourcing a file with
source ./settings.shruns its commands in the current shell and can change that session’s directory. Sourcing uses the current shell’s language. - 8ChallengeDiagnose the launch before editing commands
A readable report works with
bash folder-report.sh. Direct execution reports "Permission denied," and typing its name alone reports "command not found." What two launch issues should be checked?---reveal---
Check the script’s execute permission for direct execution. Then check PATH or use its explicit
./path for lookup. A valid Bash shebang selects the interpreter but cannot grant permissions or place the file on PATH. - 9RecapA launch has several separate choices
The file holds the command sequence. The interpreter reads its language, permissions allow direct execution, and PATH or a slash locates the file.
An executed script works in a child shell. A sourced settings file works in the shell already open, so its assignments and directory changes can remain in that session.
2Build a dated report name without losing spaces
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow one value into a command
- 2ExampleStore a multiword destination
- 3ConnectionPass the setting to a child
- 4ChallengeSeparate a name from its suffix
- 5ExamplePreserve the stored path
- 6ChallengeChoose fixed text or changing text
- 7ExampleCapture a date for the name
- 8ExampleCalculate before inserting the value
- 9ChallengeLocate the lost fraction
- 10ChallengeCombine the path and the captured value
- 11RecapDistinguish the three jobs
3Keep a failed search from becoming a successful report
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionTreat the result as part of the task
- 2ExampleSee the hidden upstream failure
- 3Key InsightMake the pipeline report its failure
- 4ChallengeSave the status that matters
- 5ExampleHandle the failed work explicitly
- 6ExplanationChoose syntax checking or execution tracing
- 7ExplanationRead the error options as separate controls
- 8ChallengeExplain why this script continues
- 9ChallengeKeep the caller informed
- 10RecapRead both the data and the result
4Inspect incoming files and read their records
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionLet each kind of input choose its next step
- 2ExampleChoose a test for the path’s type
- 3ExplanationCompare values in the right way
- 4ChallengeInterpret a negated file test
- 5ExampleSelect an action with a command result
- 6ChallengeTrace overlapping thresholds
- 7ExplanationMatch an action word with case
- 8ExampleGive a glob loop one file per pass
- 9ChallengeExplain the empty-folder case
- 10TipChoose a variable number limit
- 11ExampleRead a record without breaking its spaces
- 12ChallengeKeep the last record without a newline
- 13ConnectionWait without continuously checking
- 14ChallengeDiagnose a total that stays zero
- 15RecapMatch the structure to the data
5Accept file inputs and report partial failure
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake input part of the command call
- 2ExampleInspect the call’s boundaries
- 3ExplanationConsume the leading flag
- 4ChallengeKeep an empty argument in the count
- 5ExampleReject a call with no file inputs
- 6ExplanationChoose a default only for optional input
- 7ExampleCheck the read operation itself
- 8ChallengeRemember the earlier failure
- 9ChallengeStop parsing before a hyphenated file name
- 10RecapGive reusable commands a dependable contract
6Schedule a report and verify its last run
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionSchedule only work that has complete input
- 2ExampleRead a personal crontab entry
- 3ChallengeTrace a minute step across an hour boundary
- 4TipGive the job an output destination
- 5ConnectionDistinguish a missed run from a failed run
- 6ExamplePut the work in a service
- 7ExamplePut the time in the matching timer
- 8ExplanationLoad changes and enable the schedule
- 9ChallengeCheck the result as well as the next time
- 10ExplanationCount time from an event when needed
- 11ChallengeChoose catch-up after downtime
- 12RecapInspect the schedule and the task separately
Shell Scripting practice questions
Set 1: Trace values, records and schedules10 Practice Questions
Question 1 of 10
done. What command can detect that syntax problem without running the script?Show answerHide answer
bash -n reads the script and checks its shell structure without executing the commands. It can detect a loop that is missing its closing done. bash -x also reads the script but executes it with tracing, so earlier valid commands could run before the syntax problem is encountered.Question 2 of 10
[ 06 = 6 ] and [ 06 -eq 6 ], in that order?Show answerHide answer
Question 3 of 10
left=$(( 29 % 6 )) assign?Show answerHide answer
% operator returns that remainder. The quotient 4 would come from integer division with /, while Bash arithmetic does not produce a decimal quotient such as 4.83 from this expression.Question 4 of 10
/bin/bash^M: bad interpreter, but /bin/bash exists. What file change addresses the indicated problem?Show answerHide answer
^M indicates a carriage return from Windows line endings, making the interpreter path differ from /bin/bash. Converting the file to Unix line endings removes that extra character. Execute permission cannot correct characters inside the interpreter path, and PATH does not resolve a fixed shebang path.Question 5 of 10
site?Show answerHide answer
=. Quotes preserve the two words as the value. With spaces around =, Bash treats site as a command; without quotes around the value, Hall becomes a command word instead of part of the assignment.Question 6 of 10
a.csv and late batch.csv, both regular files. What values are used in for f in *.csv; do echo "$f"; done?Show answerHide answer
"$f" in the body keeps that value together when it is passed onward to a command.Question 7 of 10
A file contains oak, a newline, then elm with no final newline. What count is printed?
count=0
while IFS= read -r line; do
count=$(( count + 1 ))
done < trees.txt
echo "$count"
Show answerHide answer
|| [ -n "$line" ] would also process the final record and produce 2.Question 8 of 10
./task.sh -v "blue room" output.txt, the script runs shift. What are $1 and $# now?Show answerHide answer
shift consumes the first argument, "-v", and moves the remaining two arguments down. The quoted name becomes $1, and $# falls from 3 to 2. The space inside the name stays intact; shifting does not split the text or consume the second argument as well.Question 9 of 10
*/25 10-12 * * * run, and when is its last run?Show answerHide answer
Question 10 of 10
inventory.timer?Show answerHide answer
inventory.timer starts inventory.service. The timer stores the schedule, while the service defines the work. Appending another extension to the full timer name would not identify the default matching unit.Set 2: Read and run small Bash scripts10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
[[ ... ]] syntax, but its first line is #!/bin/sh. On this machine, sh is dash. What first line selects the intended interpreter?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
label="west side", what arguments does printf '<%s>\n' "$label" format?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
file="shift notes.txt". How should it copy that one file into the existing directory saved?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
archive exists and is a directory. What are the results of [ -f archive ] and [ -d archive ], in that order?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What is printed by this code?
used=14
limit=20
percent=$(( used * 100 / limit ))
if [ "$percent" -ge 60 ]; then
used=$(( used + 2 ))
elif [ "$percent" -ge 70 ]; then
used=$(( used + 5 ))
fi
echo "$used"
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
./label.sh "East Wing" report.txt. What are $# and $2?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
dir=${1:-/srv/logs}. It is called with one argument that is an empty quoted string. What value is assigned?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
25 4 * * * /home/rae/bin/audit.sh specify?Show answerHide answer
Save this lesson to see this answer.
Set 3: Follow data through a complete script10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
zone=west. The file settings.sh contains only zone=east. The terminal runs bash settings.sh, then source ./settings.sh, then bash -c 'echo "${zone:-unset}"'. The variable was never exported. What does the final command print?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
name=archive, what does echo "${name}_ready" print?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
completed=18 and total=32. What whole-number result is produced by $(( completed * 100 / total ))?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
The file absent.log does not exist. What exit status reaches the caller?
#!/bin/bash
set -o pipefail
grep NOTICE absent.log | wc -l
status=$?
echo "Count ended"
exit "$status"
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What is printed by this script?
set -e
false || echo "Handled"
echo "Next"
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
*) follow the named action patterns?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
The current directory contains only regular files draft one.jpeg, final.jpeg and notes.txt. What is the output?
count=0
for f in *.jpeg; do
[ "$f" = final.jpeg ] && continue
echo "${f%.jpeg}.jpg"
count=$(( count + 1 ))
done
echo "$count"
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
until [ -f /tmp/upload.ready ]; do sleep 3; done. The regular file already exists before the loop begins. How often does the body run?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
status=0. A bad input sets status=1, and a later input succeeds. What ending reports that the request was incomplete?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Diagnose input, control flow and downtime10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
weekly.sh to weekly, with contents and permissions unchanged. What does losing the extension do to Linux’s shebang-based interpreter choice?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
export REGION=north, then starts a script that assigns REGION=south and exits. What value remains in the parent?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
pattern="*.txt". What argument does touch "$pattern" receive?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What happens in this noninteractive script?
set -eu
dest=
if [ -z "$dest" ]; then
dest=${2:-/srv/archive}
fi
echo "$dest"
The script receives only one argument, input.txt.
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
The variable count contains 10, and the current directory is writable with no file named 4. What happens?
if [ "$count" > 4 ]; then
echo "High"
else
echo "Low"
fi
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
case "$value" in [0-9]*) echo "Match" ;; *) echo "No" ;; esac. What happens for value=8draft?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What is printed?
i=0
total=0
while [ "$i" -lt 4 ]; do
i=$(( i + 1 ))
[ "$i" -eq 2 ] && continue
total=$(( total + i ))
[ "$total" -gt 5 ] && break
done
echo "$i:$total"
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
-v, an empty quoted string and "red room", in that order. It runs shift, then for arg in "$@"; do echo "<$arg>"; done. What is printed?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
0 11 9 * 2 /home/rae/bin/check.sh select in ordinary cron?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Persistent=true. Three daily activations pass while the computer is off. What catch-up does it arrange when reactivated?Show answerHide answer
Save this lesson to see this answer.
Set 5: Combine quoting, failure checks and reusable input10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
A Bash terminal is in /home/rae, with PATH /home/rae/bin:/usr/local/bin:/usr/bin:/bin. Executable Bash scripts named scan exist in both bin folders and the current directory. Each contains only a Bash shebang and cd /srv/cache; that directory exists and is accessible. What file does the first command run, and what paths are printed?
scan
pwd
source ./scan
pwd
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
a.txt and b.txt. With default Bash splitting and glob settings, how many data arguments are formatted by printf '<%s>\n' $value after value="*.txt spare"?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
ready to standard output and warning to standard error, then succeeds. What is stored by message=$(the_command)?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What exit status reaches the caller?
#!/bin/bash
false
saved=$?
true && echo "Handled"
exit $(( saved + 2 ))
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
set -u and has already assigned folder="". It must stop with a useful diagnostic before using a missing or empty folder. What expansion supplies that check?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
"$text" has nonzero length?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
limit=3, what values are printed by for i in {1..$limit}; do echo "$i"; done under ordinary Bash expansion?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What count is printed under default Bash pipeline behavior? The file contains three newline-terminated lines.
count=5
cat tasks.txt | while IFS= read -r line; do
count=$(( count + 1 ))
done
count=$(( count + 2 ))
echo "$count"
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
A script parses its inputs with this code, then requires exactly two file arguments. After the call ./copy.sh -v -- "early notes.txt" -final.txt, how many arguments remain and what should happen next?
while [ "$#" -gt 0 ]; do
case "$1" in
-v) verbose=1; shift ;;
--) shift; break ;;
-*) echo "Unknown option" >&2; exit 2 ;;
*) break ;;
esac
done
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
/home/rae/.local/bin. What change addresses the likely difference?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Anacron
Argument count
$#, with the invocation name excluded../greet.sh Ana "Lee Kim" because the quoted name is one argument.Argument forwarding
"$@" to preserve each boundary."$@" keeps an empty input and a multiword name as separate arguments.Arithmetic expansion
$(( ... )) and inserts the calculated value into the command."$(( count + 1 ))" into the next whole-number count.Assignment
name=value, with no spaces around the equals sign.name="Lee Kim" keeps the two words together as one stored value.B
Bash
[[ ... ]] selects Bash in its shebang so its conditional syntax is understood.Brace expansion
{1..5} before Bash performs variable expansion on the command.{1..$n} into a range based on the value stored in n.Branch
break
C
Calendar timer
OnCalendar=Mon..Fri 09:00 selects weekday mornings at 09:00.case
Catch-all pattern
* at the end of a Bash case block.*) reports unknown commands after the named subcommands were checked.Child process
Command substitution
$(command).today=$(date +%F) captures a date for use in a backup file name.Comment
# at a word boundary outside quoted text.# Keep a copy for recovery explains why the following file copy is needed.Conditional keyword
[[ begins a shell test construct with its own parsing and pattern-matching rules.[[ allows [[ $name == *.log ]] without word splitting of the name.continue
Counter
count=0 can increase once for each line that a loop reads.cron
Cron step
*/45 in minutes selects 0 and 45, then restarts at 0 in the next hour.Crontab
crontab -l displays the current user’s crontab so its schedules can be inspected.D
Default value
${1:-/var/log} for a missing or empty argument.Double quotes
echo "Home: $HOME *" allow the home path to expand while keeping the asterisk.E
elif
End-of-file
read reports failure when it encounters this condition.read reports failure.Environment variable
errexit
set -e option, which exits on unhandled failures while allowing specified contexts that test command status.if condition to select its else branch instead of ending the script.Execute permission
chmod u+x job.sh lets the owner try running ./job.sh.exit
exit 2 after a usage error so its caller receives a failure result.Exit status
grep -q distinguishes a match, no match and a problem reading the input.Export
F
Failure flag
File test
[ -d "$src" ] accepts a source directory but rejects a regular file.for loop
web1 web2 db1 runs its body once for each of the three host names.G
Globbing
*.log into separate matching file names before a for loop begins.I
IFS
read preserves leading and trailing whitespace.IFS= read -r line keeps spaces at the edges of each record read from the input file.Integer division
$(( 7 / 2 )) produce 3 even though the exact quotient is 3.5.Interpreter
#!/usr/bin/python3 reads Python code instead of Bash commands.Iteration
J
Journal
journalctl -u backup.service shows messages from the backup task.L
Line ending
^M.Lingering
M
Minimal environment
Monotonic timer
N
Negation
[ ! -d "$p" ] accepts a regular file as well as a path that does not exist.nounset
set -u option, which treats an expansion of an unset variable as an error in a noninteractive script.Numeric comparison
-eq and -gt, independently of their text length.[ "$count" -ge 8 ] succeeds for a stored count of 12.O
Option
-v can enable extra messages before the script processes the remaining file names.P
PATH
job.sh, but follows the explicit current-directory path in ./job.sh.pipefail
grep can make the whole pipeline fail.Pipeline status
pipefail changes that rule.grep fails but the following wc successfully counts no input.Positional parameter
$1, with braces required for positions after nine.${10} retrieves the tenth argument without appending a zero to $1.Q
Quoting
"$file" preserves "monthly report.txt" as one argument when copying the file.R
read
read -r -p "Name: " name prompts for a name and keeps backslashes in the entered text.Remainder
% operator.$(( 17 % 5 )) is 2 because three complete groups of five use 15.S
Service unit
backup.service defines the script activated by the matching backup timer.Shebang
#! that names the interpreter Linux should use to execute a script.#!/bin/bash starts at the first character, before any comment or blank line.Shell script
shift
-v, the next command-line word becomes $1 for the option parser.Short-circuit execution
&& or || list runs.cd /data || exit 1 stops dependent commands after a failed directory change.Single quotes
echo '$HOME' print the characters "$HOME" instead of a home path.Sourcing
./settings.sh loads its variable assignments directly into the open Bash session.Standard error
>&2 remains visible when standard output goes to a file.String comparison
Suffix removal
${f%.jpeg}.Syntax check
bash -n detects a missing done before the loop can run any commands.T
Timer unit
backup.timer establishes the schedule for the backup service.Tracing
bash -x reveals an empty source argument passed to a file-copy command.Trap
U
until loop
Usage message
V
Validation
Variable
city stores "Lisbon" after city=Lisbon, so echo "$city" can display it.Variable expansion
"${file}_old" into "report_old" when file contains "report".W
while loop
read reaches end-of-file and reports failure.Word splitting
cp runs.About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.