Turn commands you repeat into reusable scripts — write and run your first script, use variables and arguments, make decisions with if, loop over files, factor logic into functions, and debug safely.
A shell script is just a text file of commands you would type anyway. The first line — the "shebang" #!/usr/bin/env bash — tells the system which interpreter to run it with. Save this as hello.sh.
#!/usr/bin/env bash
# hello.sh — my first script
echo "Hello from a script!"
echo "Today is: $(date)"A new script is not runnable until you grant it the execute permission with chmod +x (from the permissions lesson). Then ./hello.sh runs it — the ./ means "the file right here", which you need because the current folder is not on your PATH. Alternatively, bash hello.sh runs it without changing permissions.
Grant the execute permission
chmod +x hello.shRun it (./ = "in this folder")
./hello.shOr run it directly with bash, no chmod needed
bash hello.shSet a variable with name=value (no spaces around =) and read it back with $name. Scripts also receive arguments: $1 is the first word after the script name, $@ is all of them. export turns a variable into an environment variable so programs the script launches can see it too — the same mechanism behind PATH and the env you saw earlier.
#!/usr/bin/env bash
name="world" # set a variable (no spaces around =)
echo "Hello, $name" # read it back with $
# $1 is the first argument passed to the script; $@ is all of them
echo "First argument: $1"
echo "All arguments: $@"
# export makes a variable visible to programs this script runs
export APP_ENV="production"if runs commands only when a test passes. The test goes in [ ... ] with spaces inside the brackets. Common checks: -f file exists, -d directory exists, -z string is empty. This example bails out early if a required file is missing — the kind of guard that makes a script safe to run.
#!/usr/bin/env bash
config="/etc/myapp/config.yml"
# -f tests "this file exists"
if [ -f "$config" ]; then
echo "Config found, starting up."
else
echo "ERROR: $config is missing."
exit 1 # stop the script with an error code
fiA for loop repeats commands once per item — perfect for processing every file in a folder or each line of a list. A while loop repeats as long as a condition holds. Looping is where scripts pay off: one rule applied to a hundred files automatically.
#!/usr/bin/env bash
# Do something for each .log file in the current folder
for file in *.log; do
echo "Compressing $file"
gzip "$file"
done
# while: keep going as long as the condition is true
count=1
while [ $count -le 3 ]; do
echo "Attempt $count"
count=$((count + 1)) # arithmetic
doneA function is a named block of commands you can call repeatedly, so you write logic once and reuse it. Inside a function, arguments work just like a script's: $1, $2, and so on. Functions keep longer scripts readable and avoid copy-pasting the same steps.
#!/usr/bin/env bash
# Define a function that takes one argument
log() {
echo "[$(date +%H:%M:%S)] $1"
}
# Call it like a command
log "Starting backup"
log "Backup finished"Three settings at the top of a script save hours. set -e stops the moment any command fails, instead of charging on with broken state. set -u treats use of an unset variable as an error, catching typos. set -x prints each command as it runs, so you see exactly what executed. Combine them while developing, then drop -x once it works.
#!/usr/bin/env bash
set -euo pipefail # -e exit on error, -u error on unset vars, pipefail catch pipe failures
# Turn on command tracing for the noisy part
set -x
cp /etc/hosts /tmp/hosts.bak
rm /tmp/hosts.bak
set +x # turn tracing back off