Scripting Basics¶
The fundamentals of writing bash scripts: structure, variables, and quoting.
Script Structure¶
A bash script is a text file containing commands:
The Shebang¶
The first line tells the system how to run the script:
#!/usr/bin/env bash # Recommended - finds bash in PATH
#!/bin/bash # Direct path (less portable)
#!/bin/sh # POSIX shell (more portable, fewer features)
Using env is more portable across systems where bash might be in different locations.
Making Scripts Executable¶
Or run with bash directly:
File Extensions¶
The .sh extension is conventional but not required. What matters is:
- The shebang line
- The execute permission
Variables¶
Basic Assignment¶
Using Variables¶
echo $name # Simple expansion
echo ${name} # Explicit braces
echo "Hello, $name" # Inside double quotes
echo "Files: ${count}" # Braces prevent ambiguity
Variable Names¶
Valid names:
- Start with letter or underscore
- Contain letters, numbers, underscores
- Case-sensitive
valid_name="yes"
_private="yes"
NAME2="yes"
2invalid="no" # Error: starts with number
invalid-name="no" # Error: contains hyphen
Readonly Variables¶
Unset Variables¶
Quoting¶
Quoting is one of the most important concepts in bash scripting.
Double Quotes¶
Variables expand, special characters mostly literal:
name="World"
echo "Hello, $name" # Hello, World
echo "Path is $HOME" # Path is /home/user
echo "Tab:\there" # Tab: here
Single Quotes¶
Everything is literal - no expansion:
No Quotes¶
Word splitting and glob expansion occur:
files="file1 file2"
rm $files # Removes file1 AND file2 (word split)
rm "$files" # Error: no file named "file1 file2"
When to Quote¶
Always quote variables unless you specifically want word splitting:
# Good
echo "$name"
path="$HOME/documents"
[[ -f "$file" ]]
# Bad - can break on spaces or special chars
echo $name
path=$HOME/documents
[[ -f $file ]]
Escaping¶
Backslash escapes special characters:
echo "She said \"Hello\"" # She said "Hello"
echo 'It'\''s working' # It's working (end quote, escape, new quote)
echo "Cost: \$50" # Cost: $50
$'...' Quoting¶
Interprets escape sequences:
Special Variables¶
| Variable | Meaning |
|---|---|
$0 | Script name |
$1 to $9 | Positional arguments |
${10} | 10th argument (braces required) |
$# | Number of arguments |
$@ | All arguments (preserves quoting) |
$* | All arguments (as single word) |
$? | Exit status of last command |
$$ | Current shell PID |
$! | PID of last background job |
Script Arguments¶
#!/usr/bin/env bash
echo "Script: $0"
echo "First arg: $1"
echo "Second arg: $2"
echo "All args: $@"
echo "Count: $#"
Difference Between $@ and $*¶
#!/usr/bin/env bash
echo "Using \$@:"
for arg in "$@"; do
echo " '$arg'"
done
echo "Using \$*:"
for arg in "$*"; do
echo " '$arg'"
done
Always use "$@" when passing arguments to other commands.
Parameter Expansion¶
Default Values¶
echo ${name:-default} # Use 'default' if unset or empty
echo ${name:=default} # Set to 'default' if unset or empty
echo ${name:+alternate} # Use 'alternate' if set
echo ${name:?error message} # Error if unset or empty
String Length¶
Substring¶
str="hello world"
echo ${str:0:5} # hello (from 0, length 5)
echo ${str:6} # world (from 6 to end)
echo ${str: -5} # world (last 5, note space)
Substitution¶
file="document.txt"
echo ${file%.txt} # document (remove suffix)
echo ${file#doc} # ument.txt (remove prefix)
echo ${file%.txt}.md # document.md (change extension)
path="/home/user/file.txt"
echo ${path##*/} # file.txt (basename)
echo ${path%/*} # /home/user (dirname)
Command Substitution¶
Capture command output in a variable:
# Modern syntax (preferred)
date=$(date +%Y-%m-%d)
files=$(ls *.txt)
# Old syntax (avoid)
date=`date +%Y-%m-%d`
Nest command substitutions:
Arithmetic¶
Basic arithmetic (covered in detail in Arithmetic):
Comments¶
# This is a single-line comment
echo "Hello" # Inline comment
: '
This is a
multi-line comment
(actually a null command with a string argument)
'
Exit Status¶
Every command returns an exit status:
0= success1-255= failure
#!/usr/bin/env bash
if command_that_might_fail; then
echo "Success"
else
echo "Failed with status: $?"
fi
Exit your script with a status:
Debugging Shebang¶
Add flags to shebang for debugging:
#!/usr/bin/env -S bash -x # Trace execution
#!/usr/bin/env -S bash -e # Exit on error
#!/usr/bin/env -S bash -u # Error on undefined variables
Or use set in the script:
#!/usr/bin/env bash
set -x # Enable tracing
set -e # Exit on error
set -u # Error on undefined variables
set -o pipefail # Pipe failure propagation
Combine them:
Complete Example¶
#!/usr/bin/env bash
#
# greet.sh - Greet users with a personalized message
#
set -euo pipefail
# Default values
NAME="${1:-World}"
GREETING="${2:-Hello}"
# Main logic
main() {
local message="${GREETING}, ${NAME}!"
echo "$message"
if [[ "$NAME" == "World" ]]; then
echo "(Tip: Pass a name as the first argument)"
fi
return 0
}
main
Try It¶
-
Create a simple script:
-
Practice quoting:
-
Test parameter expansion:
Summary¶
| Concept | Syntax |
|---|---|
| Shebang | #!/usr/bin/env bash |
| Variable assignment | name="value" |
| Variable usage | "$name" or "${name}" |
| Command substitution | $(command) |
| Arithmetic | $((expression)) |
| Default value | ${var:-default} |
| Script arguments | $1, $2, $@, $# |
| Exit status | $?, exit N |
Key rules:
- No spaces around
=in assignments - Always quote variables:
"$var" - Use
"$@"for passing arguments - Start scripts with
set -euo pipefail