Bash Scripting

Variables

name="Alice"             # assign (NO spaces around =)
count=42
msg="Hello ${name}"      # variable substitution with braces (preferred)
today=$(date +%F)        # command substitution
 
echo $name               # reference
echo "${name}"           # preferred: braces prevent ambiguity

Rules:

  • No spaces around =
  • Names: start with letter, no whitespace/punctuation
  • Reference: $var or ${var} (braces preferred)

Environment Variables

export MYVAR=Value       # export to child processes
printenv                 # list all env vars
echo $SHELL              # read specific var

Quoting

QuoteBehavior
"double"$var and \ interpreted; other special chars protected
'single'Everything literal — NO variable substitution
\charEscape single character
var="hello world"
echo "$var"   # → hello world (single argument, correct)
echo $var     # → hello world (but word-splitting can cause bugs)
echo '$var'   # → $var (literal, no substitution)

Always quote variable references: "$var" (prevents word splitting and glob expansion).

Functions

# Definition must come BEFORE call
greet()
{
    local name="$1"    # local = function-scoped
    local age="$2"
    echo "Hello ${name}, age ${age}"
}
 
greet "Alice" 30       # call (no brackets)

Positional Parameters

VariableMeaning
$0Script name
$1$9Arguments 1–9
$#Number of arguments
$?Exit status of last command
if [ $# -gt 0 ]; then
    echo "Got $# arguments, first is $1"
fi

Exit Status

  • 0 = success, non-zero = failure
  • $? = exit status of last command
ls /usr/bin; echo $?    # → 0 (success)
ls /nodir;  echo $?     # → 2 (failure)
exit 0                  # script success
exit 1                  # script failure

if / test / [ ]

if [ expression ]; then
    commands
elif [ expression ]; then
    commands
else
    commands
fi

test Expressions

File tests:

ExpressionTrue if
-f filefile exists and is regular file
-d filefile is a directory
-e filefile exists (any type)
-r filefile is readable
-w filefile is writable
-x filefile is executable
f1 -nt f2f1 is newer than f2

String tests:

ExpressionTrue if
-z stringstring is empty
-n stringstring is not empty
s1 = s2strings are equal
s1 != s2strings are not equal
if [ -f "$HOME/.bashrc" ]; then
    echo "File exists"
fi

Shebangs

#!/usr/bin/env bash     # portable — recommended
#!/bin/bash             # hardcoded path
#!/usr/bin/env python   # Python scripts

First line of any script. env finds the interpreter in PATH — more portable than hardcoding.

chmod — File Permissions

Numeric

Format: chmod <owner><group><world> file

ValuePermission
4read (r)
2write (w)
1execute (x)

Add them: chmod 755 = owner(rwx=7) + group(r-x=5) + world(r-x=5)

ModeMeaningUse for
777rwxrwxrwxFull access everyone
755rwxr-xr-xScripts/dirs (execute for all)
644rw-r—r—Regular files
700rwx------Owner only
400r--------Read-only by owner

Symbolic

chmod u+x script.sh        # add execute for owner
chmod a+r file.txt         # add read for all
chmod a-x file.txt         # remove execute for all
chmod go+rw file.txt       # add read+write for group+world
chmod -R 755 /path/dir/   # recursive

u=user/owner, g=group, o=others, a=all

chown

sudo chown kate file.txt           # change owner
sudo chown :mygroup file.txt       # change group
sudo chown kate:mygroup file.txt   # change both

source vs bash

CommandShell instanceVariables/functions persist?
source file.sh (or . file.sh)CurrentYes
bash file.shNewNo

Use source for libraries; bash/./script.sh for standalone scripts.

source .venv/bin/activate    # classic use case: activate venv in current shell

See Also