Linux Operating Systems Fundamentals · Lesson 9

Writing Scripts

Shell variables and startup files, Bash scripting with input, tests, loops, and functions, job control and signals, and scheduling with at and cron.

  • linux
  • bash
  • scripting
  • variables
  • job-control
  • cron
  • at
  • lpic-1

Lesson purpose

Administrators repeat the same tasks constantly: checking disk space, creating accounts, rotating backups. A shell script turns a sequence of commands into a reusable, testable tool, and scheduling runs it without anyone watching. This lesson builds from shell variables and startup files to complete Bash scripts with input, decisions, loops, and functions. It then covers running scripts in the background, controlling jobs and signals, and scheduling work with at and cron.

Learning objectives

  • Distinguish local and environment (global) variables, set and export them, and explain which startup files each kind of shell reads.
  • Create aliases and make variables and aliases persistent.
  • Combine commands with ;, redirection, and pipes, and write, permission, and run a script that begins with a shebang line.
  • Use echo, variables, positional parameters, read, exit status, command substitution, and arithmetic in scripts.
  • Control script flow with if/test, case, for, while/until, and functions.
  • Run jobs in the background, keep them running after logout with nohup, send signals, and manage jobs with jobs, bg, and fg.
  • Schedule one-time jobs with at and recurring jobs with cron, and recognize systemd timers.

LPIC-1 exam focus

  • 105.2 Customize or write simple scripts
  • 107.2 Automate system administration tasks by scheduling jobs

1. Shell variables

The shell stores data in memory as named variables that commands and scripts can read. By convention, system variables are UPPERCASE. Use lowercase for your own variables so you don’t accidentally overwrite a system variable.

Environment (global) and local variables

Local (shell) variable Environment (global) variable
Visible in Only the current shell The current shell and every child process it starts
Created by name=value export name (or export name=value)
Listed by set (shows local and environment variables, plus functions) printenv or env
$ echo $HOME                     # read a variable: put $ in front of its name
/home/rich
$ test='testing a long string'   # no spaces around =; quote values that contain spaces
$ bash                           # start a child shell
$ echo $test                     # empty: local variables aren't inherited
$ exit
$ export test                    # export by NAME (no $)
$ bash
$ echo $test
testing a long string

The two classic mistakes:

  1. test2 = test runs a command named test2. There must be no spaces around =.
  2. export $test exports a variable named after the value of test. Export the name: export test. One of the course slides shows export $test, which is wrong.

A child process can never change its parent’s variables. When a child shell exits, any variables it set are gone.

Common environment variables

Variable Meaning
HOME Your home directory
USER / LOGNAME Your username
UID Your numeric user ID
PATH Directories searched for commands, separated by colons
PWD / OLDPWD Current and previous directory
SHELL Your login shell
PS1 / PS2 Primary and continuation prompts
HOSTNAME Host name
LANG Locale (Lesson 6)
TZ Time zone override (Lesson 6)
EDITOR / PAGER Preferred editor and pager, used by tools such as crontab -e and man
TERM Terminal type
HISTSIZE Number of commands kept in history
DISPLAY X11 display for GUI programs
LD_LIBRARY_PATH Extra directories to search for shared libraries
MAIL Your mail spool file
$ export PATH="$PATH:$HOME/bin"      # add a directory to the command search path
$ unset test                         # remove a variable

2. Startup files: where the environment comes from

Bash reads different files depending on how it starts.

Shell type How it starts Files read
Login shell Console login, SSH login, su - /etc/profile (and /etc/profile.d/*.sh), then the first one found of ~/.bash_profile, ~/.bash_login, ~/.profile
Interactive non-login shell A new terminal window in the GUI, or typing bash ~/.bashrc, which usually sources /etc/bash.bashrc (Debian) or /etc/bashrc (Red Hat)
Non-interactive shell Running a script The file named in $BASH_ENV, if it’s set (usually it isn’t)
  • On logout, a login shell runs ~/.bash_logout.
  • Most ~/.bash_profile files source ~/.bashrc, so interactive settings apply to login shells too.
  • System-wide settings for every user go in /etc/profile.d/ (preferred) or /etc/profile. Personal settings go in ~/.bashrc (aliases, functions, prompt) or ~/.bash_profile (environment variables).
  • The files beginning with a dot are hidden, so use ls -a to see them. New users get copies from /etc/skel (Lesson 7).
  • Reload a file you’ve edited, without logging out, by sourcing it: source ~/.bashrc or . ~/.bashrc.

3. Aliases

An alias is a shortcut name for a command and its options.

$ alias -p                           # list current aliases
alias ll='ls -l --color=auto'
$ alias li='ls -il'                  # create an alias
$ li
$ unalias li                         # remove it
$ \ls                                # run the real ls, skipping the alias
  • Aliases behave like local variables: they exist only in the shell where you defined them and aren’t inherited by child shells.
  • Put aliases in ~/.bashrc so every interactive shell gets them.
  • Many distributions already alias ls (to add --color) and sometimes rm -i. type ls shows whether a name is an alias.

4. Building blocks: sequences, redirection, and pipes

Several commands on one line

$ date ; who                    # run one, then the other, regardless of success
$ mkdir backup && cp *.conf backup/    # run the second only if the first succeeds
$ ping -c1 server || echo "Server down"  # run the second only if the first fails

File descriptors and redirection

Descriptor Name Default
0 STDIN Keyboard
1 STDOUT Terminal
2 STDERR Terminal
Operator Effect
> file Send STDOUT to a file, overwriting it
>> file Append STDOUT to a file
2> file Send STDERR (error messages) to a file
&> file or > file 2>&1 Send both STDOUT and STDERR to a file
< file Read STDIN from a file
2>/dev/null Throw error messages away
$ date > today.txt
$ who >> today.txt
$ find / -name '*.conf' 2>/dev/null > conf-list.txt

Pipes

| sends one command’s STDOUT into the next command’s STDIN. There’s no limit to how many commands you can chain:

$ ls | sort
$ cut -d: -f7 /etc/passwd | sort | uniq -c | sort -rn     # count login shells

Metacharacters and quoting

Characters such as > < | & ; $ * ? ' " \ have special meanings to the shell. To use one literally, escape it:

  • \ escapes one character: echo 5 \> 3.
  • Single quotes keep everything literal: echo '$HOME' prints $HOME.
  • Double quotes keep spaces together but still expand $variables and $(commands): echo "Home is $HOME".

5. Writing and running a script

A shell script is a plain text file of commands. Write scripts in a text editor (nano, vi, gedit, KWrite), never a word processor, because a word processor adds hidden formatting characters.

#!/bin/bash
# test1.sh - show the date and who is logged in
date
who
  • Shebang (#!) on the first line tells the kernel which interpreter runs the file. Use #!/bin/bash for Bash scripts. #!/bin/sh may be Dash on Debian and Ubuntu, which lacks Bash features.
  • Comments begin with #. Use them to explain why the script does something.
  • Each line is one command, so no semicolons are needed.
  • A .sh extension is a convention that makes scripts easy to recognize. Linux doesn’t require it.

Making it run

$ test1.sh
test1.sh: command not found          # the current directory isn't in $PATH
$ ./test1.sh
bash: ./test1.sh: Permission denied  # no execute permission
$ chmod u+x test1.sh
$ ./test1.sh
Thu Sep 24 19:48:27 CDT 2026
rich     :0           2026-09-24 19:15 (:0)
How you run it What happens
./script.sh Runs in a child shell. Needs execute permission and a shebang.
bash script.sh Runs in a child Bash shell. Execute permission isn’t needed.
source script.sh or . script.sh Runs in the current shell, so it can see and change your local variables. Used for startup files.
exec ./script.sh Replaces the current shell with the script. When the script ends, the terminal session ends too.

The book says to use exec so a script can see local variables. That’s incorrect: exec replaces your shell, so the terminal closes when the script finishes. To run a script in the current shell, use source (or .).

To run your own scripts by name, put them in a directory on PATH, such as ~/bin (many distributions add it automatically if it exists) or /usr/local/bin for scripts shared by all users.

6. Output, variables, and arguments in scripts

echo

#!/bin/bash
echo The current date and time is:
date
echo                                  # blank line
echo "Let's see who's logged into the system:"
who
  • Use double quotes when the text contains a single quote (an apostrophe).
  • echo -n prints without a trailing newline, which is useful for prompts.
  • printf gives precise formatting: printf "%-10s %5d\n" "$user" "$count".

Variables in scripts

#!/bin/bash
echo "User info for userid: $USER"
echo "UID: $UID"
echo "HOME: $HOME"

days=10
guest=Katie
echo "$guest checked in $days days ago"
echo "${guest}'s stay: ${days}days"      # braces separate the name from following text
  • Shell variables are text strings. There are no separate data types; arithmetic just interprets the string as a number.
  • Always double-quote variable references ("$file") so values that contain spaces stay together.
  • Variables created in a script disappear when the script ends. A script can’t export variables back to the shell that started it; only sourcing changes the current shell.

Positional parameters and special variables

Variable Contains
$0 The script’s name
$1 … $9, ${10} Command-line arguments
$# Number of arguments
$@ All arguments as separate words (use "$@")
$* All arguments as one string
$? Exit status of the last command
$$ PID of the current shell or script
$! PID of the last background job
#!/bin/bash
# checkin.sh NAME DAYS
if [ $# -ne 2 ]; then
    echo "Usage: $0 NAME DAYS" >&2
    exit 1
fi
echo "$1 checked in $2 days ago"

A missing argument produces no error, just an empty value (./test4.sh rich prints “rich checked in days ago”). Check $# and print a usage message.

Reading input

read name                              # read a line into name
read -p "Enter your name: " first last # prompt; split into words (extra words go to the last variable)
read -p "Enter a number: "             # no variable name: the input goes into $REPLY
read -t 5 -p "Name? " name             # time out after 5 seconds (read returns non-zero)
read -n1 -p "Continue [Y/N]? " answer  # return after 1 character, no Enter needed
read -s -p "Password: " pass; echo     # silent: don't echo what's typed

read -s turns off terminal echo. The book says it sets the text color to match the background; that isn’t how it works.

Exit status

  • Every command returns an exit status from 0 to 255. 0 means success, and anything else means failure. Check it immediately in $?, because the next command overwrites it.
  • End a script with exit N to report success or a specific error code to whatever ran it (a user, another script, cron).
$ who > /dev/null ; echo $?
0
$ ls /nonexistent 2>/dev/null ; echo $?
2

7. Command substitution and arithmetic

Command substitution

Command substitution captures a command’s output in a variable:

today=$(date +%F)                       # preferred form
users=`who | wc -l`                     # older backtick form
echo "Backup for $today; $users users logged in"
tar -czf "/backup/home-$(date +%F).tgz" /home

The backtick (`) is not a single quote, which confuses many students. $( ) is easier to read and can be nested, so use it in class.

Integer arithmetic

result=$(( 25 * 5 ))           # 125 (preferred)
count=$(( count + 1 ))
(( count++ ))
result=$[ 25 * 5 ]             # older form shown in the book; still works but is deprecated
echo $(( 7 / 2 ))              # 3: Bash math is integers only
echo $(( 7 % 2 ))              # 1: remainder

Floating point with bc

$ echo "scale=4; 3.44 / 5" | bc
.6880
$ avg=$(echo "scale=2; $total / $n" | bc)

scale sets the number of decimal places. The default is 0, which is why 3.44 / 5 gives 0 until you set it.

8. Decisions: if, test, and case

if and test

if [ condition ]; then
    commands
elif [ other-condition ]; then
    commands
else
    commands
fi
  • [ condition ] is another name for the test command. The spaces inside the brackets are required: [ "$a" -eq 1 ].
  • if actually runs a command and checks its exit status, so any command can be the condition: if grep -q root /etc/passwd; then.
Numeric String File
-eq equal = equal -e exists
-ne not equal != not equal -f regular file
-gt greater than -z empty -d directory
-ge greater or equal -n not empty -r / -w / -x readable / writable / executable
-lt less than \< / \> sort order (must be escaped inside [ ]) -s exists and isn’t empty
-le less or equal -O / -G owned by you / your group
f1 -nt f2 / f1 -ot f2 newer / older than

Common trap: -eq compares numbers and = compares strings. Also, < and > inside [ ] are redirection operators unless you escape them.

#!/bin/bash
# compare.sh N1 N2
if [ "$1" -eq "$2" ]; then
    echo "Both values are equal"
elif [ "$1" -gt "$2" ]; then
    echo "The first value is greater than the second"
else
    echo "The first value is less than the second"
fi
  • Combine tests with && (and) and || (or): if [ -f "$f" ] && [ -r "$f" ]; then.
  • Bash also has [[ ... ]], which is safer with empty variables and supports pattern matching ([[ $file == *.log ]]). Use [ ] to match the exam and the book, and know that [[ ]] exists.

case

case compares one value against several patterns. It’s cleaner than a long if/elif chain.

case "$USER" in
    rich | barbara)
        echo "Welcome, $USER" ;;
    testing)
        echo "Special testing account" ;;
    *)
        echo "Sorry, you're not allowed here" ;;
esac
  • | separates alternative patterns.
  • Every branch ends with ;;.
  • *) is the catch-all default.
  • The statement ends with esac (“case” spelled backward, just as fi ends if).
  • Patterns use wildcards: [Yy]*) matches “y”, “Y”, and “yes”.

9. Loops

for

for file in /etc/*.conf; do          # a glob is safer than $(ls)
    if [ -d "$file" ]; then
        echo "$file is a directory"
    elif [ -f "$file" ]; then
        echo "$file is a file"
    fi
done

for n in $(seq 1 2 9); do echo $n; done     # 1 3 5 7 9 (start, step, end)
for n in {1..5}; do echo $n; done           # brace expansion
for (( i=1; i<=5; i++ )); do echo $i; done  # C-style

The book’s example loops over $(ls | sort), which breaks on file names that contain spaces. Loop over a glob (for f in *) instead.

seq takes arguments in the order start, step, end when you give three of them (seq 1 2 9). The book lists them as “start, end, and interval,” which gets the order wrong.

while and until

#!/bin/bash
# factorial.sh N
number=$1
factorial=1
while [ "$number" -gt 0 ]; do
    factorial=$(( factorial * number ))
    number=$(( number - 1 ))
done
echo "The factorial of $1 is $factorial"
  • while repeats as long as its condition is true. until repeats until its condition becomes true.
  • Always change something inside the loop that eventually makes the condition fail, or the loop runs forever. Press Ctrl+C to stop a runaway loop.
  • Read a file line by line with: while read -r line; do ...; done < file.txt.
  • break leaves a loop early, and continue skips to the next pass.

10. Functions

Functions name a block of code so you can reuse it.

#!/bin/bash
# Two equivalent ways to define a function
function banner {
    echo "=============================="
    echo " $1"
    echo "=============================="
}

log_msg() {
    echo "$(date +%T) $*" >> /tmp/script.log
}

banner "Disk report"
df -h /
log_msg "Report generated by $USER"
  • Define a function before calling it. Call it by name, like a command.
  • Inside a function, $1, $2, and so on are the function’s arguments, not the script’s.
  • return N sets the function’s exit status (0–255), which you read in $?. It isn’t meant for returning data. The book’s dbl example returns doubled values through return and breaks for any result above 255.
  • To return data, echo it and capture it with command substitution: result=$(dbl 21).
  • local var=value keeps a variable private to the function.
dbl() {
    echo $(( $1 * 2 ))
}
result=$(dbl 200)
echo "The new value is $result"      # 400

11. Background jobs, nohup, and signals

Background mode

$ ./backup.sh &
[1] 19555            # [job number] PID
$ jobs
[1]+  Running        ./backup.sh &
...
[1]+  Done           ./backup.sh
  • & runs a command in the background and gives you the prompt back right away.
  • The job number belongs to this shell. The PID belongs to the whole system.
  • Background jobs still write to the terminal. Redirect their output (> job.log 2>&1 &).
  • Background jobs are tied to the terminal. When the terminal closes, they get SIGHUP and usually exit.

nohup

$ nohup ./long-task.sh &
nohup: appending output to 'nohup.out'
$ nohup ./long-task.sh > task.log 2>&1 &      # better: choose the log file yourself

nohup makes the command ignore SIGHUP, so it keeps running after you log out. If you don’t redirect output, it’s appended to nohup.out in the current directory, so several jobs can mix their output in one file. screen and tmux are alternatives when you need to reattach to a session later.

Signals from the keyboard

Keys Signal Effect
Ctrl+C SIGINT (2) Interrupt: most programs exit
Ctrl+Z SIGTSTP (20) Stop (pause) the foreground job; it stays in memory
Ctrl+D (not a signal) End of input (EOF)
  • ps shows a stopped job with state T.
  • If you try to exit with stopped jobs, Bash warns “There are stopped jobs.” Typing exit a second time exits and kills them.
  • kill PID sends SIGTERM (15), which asks politely. kill -9 PID sends SIGKILL (9), which can’t be caught or ignored. The book’s job-control listing uses kill -9 and then describes it as sending SIGHUP; it’s SIGKILL.
  • kill %2 sends a signal to job 2 instead of a PID.

12. Job control

$ ./test19.sh
This is a test program 29011
Loop #1
^Z
[1]+  Stopped                 ./test19.sh
$ ./test19.sh > test19.out &
[2] 28861
$ jobs -l
[1]+ 29011 Stopped                 ./test19.sh
[2]- 28861 Running                 ./test19.sh > test19.out &
$ bg 1          # resume job 1 in the background
$ fg 2          # bring job 2 to the foreground
Command Action
jobs List this shell’s jobs
jobs -l / -p Include PIDs / show only PIDs
jobs -r / -s Only running / only stopped jobs
jobs -n Only jobs whose status changed since the last report
bg [%n] Resume a stopped job in the background (sends SIGCONT)
fg [%n] Bring a job to the foreground
  • + marks the current default job, which fg and bg use when you don’t give a number.
  • - marks the next job in line. When the + job ends, the - job becomes the default.

13. Scheduling one-time jobs with at

at queues a job to run once at a future time. The atd daemon checks the queue (/var/spool/at or /var/spool/cron/atjobs) about every minute.

$ at -f backup.sh 22:30              # run a script file at 10:30 PM
$ at now + 25 minutes                # type commands, then Ctrl+D
at> tar -czf /tmp/etc.tgz /etc
at> <EOT>
$ at 4PM tomorrow -f report.sh
$ atq                                # list pending jobs: number, time, queue
$ at -c 7                            # show what job 7 will run
$ atrm 7                             # remove job 7
  • Time formats: 10:15, 10:15PM, now, noon, midnight, teatime (4 PM), dates like MMDDYY, MM/DD/YY, DD.MM.YY, or Jul 4, and offsets like now + 25 minutes or 10:15 + 7 days. A time that has already passed today means tomorrow.
  • Output isn’t shown on a terminal. It’s mailed to the user who submitted the job, using the local mail system from Lesson 7. -m sends mail even when there’s no output.
  • Queues are a–z. a is the default, and later letters run at lower priority (a higher nice value). batch runs a job when the system load drops.
  • Jobs run with /bin/sh (“commands will be executed using /bin/sh”), in the directory and environment where you ran at.
  • Users can remove only their own jobs.
  • Access control: /etc/at.allow (if it exists, only listed users can use at) and /etc/at.deny.
  • On systemd systems, systemd-run --on-calendar=... or systemd-run --on-active=30m creates a one-time timer, and systemctl list-timers shows it.

14. Scheduling recurring jobs with cron

The cron daemon wakes every minute and runs any job whose schedule matches.

Crontab format

# min  hour  day-of-month  month  day-of-week  command
  15   10    *             *      *            /home/rich/bin/report.sh
  15   16    *             *      1            /home/rich/bin/weekly.sh
  0    12    1             *      *            /home/rich/bin/monthly.sh
  */15 *     *             *      *            /usr/local/bin/check-disk.sh
  30   2     *             *      1-5          /usr/local/bin/backup.sh > /var/log/backup.log 2>&1
Field Range
Minute 0–59
Hour 0–23
Day of month 1–31
Month 1–12 or jan–dec
Day of week 0–7 (0 and 7 are both Sunday) or sun–sat

Field syntax:

  • * means every value.
  • 1-5 is a range.
  • 1,15 is a list.
  • */15 means every 15th value.
  • Shortcuts include @reboot, @daily, @hourly, @weekly, and @monthly.

Rules that prevent most cron failures:

  • Use full paths for commands and files, because cron’s PATH is minimal.
  • Redirect output to a log file, or cron mails it to you.
  • The job runs as the crontab’s owner, so that user needs permission to run everything in it.
  • Escape % as \% (for example, inside a date +%F), because cron treats an unescaped % as a newline.

Managing crontabs

$ crontab -l            # list your crontab ("no crontab for rich" if none)
$ crontab -e            # edit your crontab (uses $EDITOR, often vi or nano)
$ crontab -r            # remove your crontab (careful: no confirmation)
$ sudo crontab -u alice -l   # root can view or edit another user's crontab
  • User crontabs are stored in /var/spool/cron/ (Red Hat) or /var/spool/cron/crontabs/ (Debian). Always edit them with crontab -e, never directly.
  • System crontabs are /etc/crontab and the files in /etc/cron.d/. They have an extra user field before the command: 0 3 * * * root /usr/local/bin/cleanup.sh.
  • Periodic directories: scripts placed in /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, or /etc/cron.monthly/ run on that schedule. anacron runs missed daily, weekly, and monthly jobs on machines that aren’t always on.
  • Access control: /etc/cron.allow and /etc/cron.deny work the same way as the at files.
  • systemd timers are the modern alternative: a .timer unit triggers a .service unit. List them with systemctl list-timers.

Check for understanding: When does 0 */6 * * 1-5 run? (At minute 0 every six hours, at 00:00, 06:00, 12:00, and 18:00, Monday through Friday.)

15. Putting it together: a complete script

Exercise 9.1 from the book, written defensively:

#!/bin/bash
# pwinfo.sh - display password aging information for every account
# Usage: sudo ./pwinfo.sh

if [ "$(id -u)" -ne 0 ]; then
    echo "Run this script with sudo." >&2
    exit 1
fi

for user in $(cut -d: -f1 /etc/passwd); do
    echo "Password information for $user"
    chage -l "$user"
    echo "----------"
done
  • The shebang selects Bash, and the comment explains the script’s purpose and usage.
  • The if block checks for root privileges and exits with status 1 and a message on STDERR if they’re missing. That’s cleaner than running sudo on every pass through the loop, as the book’s version does.
  • cut -d: -f1 extracts the first field (the username) from each line of /etc/passwd (Lesson 7).
  • The for loop runs chage -l for each account. The "$user" variable is quoted.

Classroom application

  1. Variable scope. Set a local variable, start a child shell, and show it’s missing. Export it and repeat. Then set a variable in the child and show the parent can’t see it. Finally, put an alias and an exported variable in ~/.bashrc, and prove they persist in a new terminal.
  2. First script. Write sysinfo.sh, which prints the host name, date, uptime, disk usage of /, and logged-in users with labeled echo lines. Walk through each error students hit on the way (command not found, then permission denied, then success) and explain why each happens.
  3. Arguments and input. Extend the script to take a directory name as $1 (with a usage check on $#) or prompt for one with read -p, then report the directory’s size with du -sh.
  4. Decisions and loops. Write filecheck.sh, which loops over the arguments with for f in "$@" and uses case or if tests to report whether each is a file, a directory, or missing. Exit with status 1 if any argument was missing.
  5. Job control drill. Run sleep 300, press Ctrl+Z, run bg, start two more sleeps, then use jobs -l, fg %2, Ctrl+C, and kill %1. Predict which job is the + job at each step.
  6. Scheduling. Schedule a one-time at job that writes the date to a file in 2 minutes, confirm it with atq, and read the result. Then add a crontab entry that runs the sysinfo.sh script every weekday at 7:30 AM with its output redirected to a log, and explain each of the five time fields.

Common misconceptions

  • “name = value sets a variable.” Spaces around = turn it into a command. Write name=value.
  • “export $var exports var.” Export the name: export var.
  • “A script can set variables in my shell.” A script runs in a child shell. Only source/. runs it in the current shell. exec replaces your shell.
  • “.sh makes a file executable.” Execute permission (chmod u+x) and a shebang do that. The extension is only a naming convention.
  • “-eq and = are interchangeable.” -eq compares numbers and = compares strings.
  • “[condition] works without spaces.” [ is a command, so it needs spaces around it and its arguments.
  • “return sends data back from a function.” It sets an exit status from 0 to 255. Echo the data and capture it with $( ).
  • “Bash can do decimal math.” It does integer math only. Use bc for decimals.
  • “Background jobs survive logout.” They get SIGHUP when the terminal closes. Use nohup, screen, or tmux.
  • “cron uses my normal PATH.” Cron’s environment is minimal. Use full paths and redirect output.

Lesson summary

  • Variables: local variables stay in the current shell, while exported (environment) variables are inherited by child processes. Login shells read /etc/profile and ~/.bash_profile, and interactive shells read ~/.bashrc. Put aliases and personal settings there.
  • Building blocks: ;, &&, and || sequence commands. >, >>, 2>, and < redirect. | pipes. Quoting and escaping control metacharacters.
  • Scripts: start with #!/bin/bash, make the file executable, and run it with ./script, bash script, or source script depending on whether it should run in a child or the current shell. Use echo, variables, $1…$#/"$@", read, exit codes, $( ), and $(( )).
  • Logic: if/test (numeric, string, and file tests), case, for, while/until, and functions (with return for status and echo for data).
  • Running and controlling: &, nohup, Ctrl+C (SIGINT), Ctrl+Z (SIGTSTP), kill, jobs, bg, and fg.
  • Scheduling: at/atq/atrm for one-time jobs, and crontab -e with the five time fields (plus the system cron directories and systemd timers) for recurring jobs.

The next lesson is Securing Your System.