Day 2 — Basic Script Writing#
Goal: Write your first shell scripts — from simple one-liners to multi-step automation scripts with variables, conditionals, and loops.
What Is a Shell Script?#
A shell script is a text file containing a series of commands that the shell (bash) executes in order. Instead of typing 10 commands one by one, you put them in a file and run it once.
# Instead of typing these every day:
cd ~/projects/tds
git pull
python3 main.py
echo "Done!"
# Put them in a script and run: ./daily.shYour First Script#
Step 1: Create the file#
nano ~/hello.shStep 2: Write the script#
#!/bin/bash
# My first shell script
echo "Hello, World!"
echo "Today is $(date)"
echo "You are: $(whoami)"
echo "You are in: $(pwd)"Step 3: Make it executable#
chmod +x ~/hello.shStep 4: Run it#
~/hello.sh
# Hello, World!
# Today is Mon Jan 15 10:30:00 IST 2026
# You are: alice
# You are in: /home/aliceThe Shebang Line — #!/bin/bash#
The first line of every script should be the shebang:
#!/bin/bashThis tells the system: “Use bash to interpret this file.” Without it, the system might use a different shell.
The name “shebang” comes from # (hash) + ! (bang).
Variables#
#!/bin/bash
# Defining variables (NO SPACES around =)
NAME="Alice"
AGE=25
GREETING="Hello, $NAME"
# Using variables
echo $NAME # Alice
echo ${NAME} # Alice (curly braces — safer in strings)
echo "$GREETING" # Hello, Alice
echo "${NAME}_data" # Alice_data (braces prevent confusion with $NAME_data)Rules for variables#
| ✅ Correct | ❌ Wrong | Why |
|---|---|---|
NAME="alice" | NAME = "alice" | No spaces around = |
echo $NAME | echo NAME | Missing $ to reference |
echo "${NAME}_log" | echo "$NAME_log" | Without braces, bash looks for $NAME_log |
Command substitution — putting command output in a variable#
# Use $( ) to capture command output:
TODAY=$(date +%Y-%m-%d)
echo "Date: $TODAY" # Date: 2026-01-15
PYTHON_VER=$(python3 --version)
echo "Python: $PYTHON_VER" # Python: Python 3.12.3
FILE_COUNT=$(ls *.csv 2>/dev/null | wc -l)
echo "CSV files: $FILE_COUNT"Arithmetic#
A=10
B=3
SUM=$((A + B)) # 13
DIFF=$((A - B)) # 7
PRODUCT=$((A * B)) # 30
QUOTIENT=$((A / B)) # 3 (integer division)
REMAINDER=$((A % B)) # 1
echo "Sum: $SUM, Product: $PRODUCT"🧠 Knowledge Check#
Q1: How do you correctly assign the value “Alice” to a variable named NAME in a bash script?
- A)
NAME = "Alice" - B)
NAME="Alice" - C)
$NAME="Alice" - D)
set NAME to "Alice"
Answer
B — In bash, variable assignment must not have spaces around the equals sign =.
Q2: How do you capture the output of a command like date and store it in a variable?
- A)
TODAY=date - B)
TODAY=${date} - C)
TODAY=$(date) - D)
TODAY="date"
Answer
C — The $(command) syntax executes the command and substitutes its output, which can then be assigned to a variable.
User Input — read#
#!/bin/bash
echo "What is your name?"
read USER_NAME
echo "Hello, $USER_NAME!"
# With a prompt on the same line:
read -p "Enter project name: " PROJECT
echo "Creating project: $PROJECT"Conditionals — if / elif / else#
#!/bin/bash
FILE="config.json"
if [ -f "$FILE" ]; then
echo "✓ $FILE found"
else
echo "✗ $FILE not found"
fiCommon test conditions#
| Test | Meaning |
|---|---|
[ -f file ] | File exists and is a regular file |
[ -d dir ] | Directory exists |
[ -e path ] | Path exists (file or directory) |
[ -z "$VAR" ] | Variable is empty |
[ -n "$VAR" ] | Variable is non-empty |
[ "$A" = "$B" ] | Strings are equal |
[ "$A" != "$B" ] | Strings are not equal |
[ $NUM -gt 10 ] | Number greater than 10 |
[ $NUM -lt 5 ] | Number less than 5 |
[ $NUM -eq 0 ] | Number equals 0 |
Full example#
#!/bin/bash
read -p "Enter a number: " NUM
if [ $NUM -gt 100 ]; then
echo "That's a big number!"
elif [ $NUM -gt 0 ]; then
echo "That's a positive number."
elif [ $NUM -eq 0 ]; then
echo "That's zero."
else
echo "That's a negative number."
fiLoops#
for loop — iterate over a list#
#!/bin/bash
# Loop over words
for fruit in apple banana cherry; do
echo "I like $fruit"
done
# Loop over files
for file in *.csv; do
echo "Processing $file..."
wc -l "$file"
done
# Loop with a number range
for i in {1..5}; do
echo "Step $i"
done
# Loop over command output
for user in $(cat /etc/passwd | cut -d: -f1 | head -5); do
echo "User: $user"
donewhile loop — repeat while condition is true#
#!/bin/bash
count=0
while [ $count -lt 5 ]; do
echo "Count: $count"
count=$((count + 1))
done
# Read lines from a file
while read -r line; do
echo "Line: $line"
done < input.txt🧠 Knowledge Check#
Q1: Which loop is best suited for iterating over a specific list of items or files?
- A)
whileloop - B)
forloop - C)
ifstatement - D)
repeatloop
Answer
B — A for loop is ideal for iterating through a sequence, list, or set of files (e.g., for file in *.txt; do ...).
Pipes and Redirection in Scripts#
#!/bin/bash
# Redirect output to a file
echo "Report generated on $(date)" > report.txt
# Append to a file
echo "=== File Count ===" >> report.txt
ls -la | wc -l >> report.txt
# Redirect stderr to a separate file
python3 risky_script.py 2> errors.txt
# Use tee to both display AND save
echo "Starting process..." | tee logfile.txtScript Arguments — $1, $2, etc.#
Scripts can accept command-line arguments:
#!/bin/bash
# Usage: ./greet.sh <name> <greeting>
NAME=$1
GREETING=${2:-"Hello"} # default value if $2 is not provided
echo "$GREETING, $NAME!"./greet.sh Alice
# Hello, Alice!
./greet.sh Bob "Good morning"
# Good morning, Bob!Special variables#
| Variable | Meaning |
|---|---|
$0 | Script name |
$1, $2, … | Arguments |
$# | Number of arguments |
$@ | All arguments (as separate words) |
$? | Exit code of last command (0 = success) |
A Real Example — Workspace Setup Script#
#!/bin/bash
# setup-workspace.sh — Create a project workspace
# Usage: ./setup-workspace.sh <project-name>
PROJECT_NAME="${1:?Usage: $0 <project-name>}"
BASE_DIR="$HOME/tds-bootcamp"
PROJECT_DIR="$BASE_DIR/$PROJECT_NAME"
# Check if it already exists
if [ -d "$PROJECT_DIR" ]; then
echo "Error: $PROJECT_DIR already exists!"
exit 1
fi
echo "Creating project: $PROJECT_NAME"
# Create structure
mkdir -p "$PROJECT_DIR"/{data,outputs,scripts,docs}
# Create README
cat > "$PROJECT_DIR/README.md" << EOF
# $PROJECT_NAME
Created: $(date +%Y-%m-%d)
Author: $(whoami)
## Directory Structure
- \`data/\` — input data files
- \`outputs/\` — generated output files
- \`scripts/\` — shell scripts and automation
- \`docs/\` — documentation
EOF
# Create a placeholder script
cat > "$PROJECT_DIR/scripts/run.sh" << 'EOF'
#!/bin/bash
echo "Running analysis..."
echo "Data directory: $(ls ../data/ 2>/dev/null | wc -l) files"
echo "Done!"
EOF
chmod +x "$PROJECT_DIR/scripts/run.sh"
echo ""
echo "✓ Project created at: $PROJECT_DIR"
tree "$PROJECT_DIR"Q&A#
Q: Why does NAME = "alice" (with spaces) fail?
A: Bash interprets spaces as argument separators. With spaces, bash thinks NAME is a command, = is its first argument, and "alice" is its second argument. Without spaces, bash knows it’s a variable assignment.
NAME = "alice" # Error: NAME: command not found
NAME="alice" # CorrectQ: What does #!/bin/bash actually do?
A: When you run ./script.sh, the OS reads the first line. If it starts with #! (shebang), the OS uses the specified program (/bin/bash) to interpret the rest of the file. Without it, the OS might use /bin/sh (a more limited shell) or fail.
Q: What is the difference between single quotes and double quotes?
A:
- Double quotes
" "— variables and commands are expanded:NAME="Alice" echo "Hello $NAME" # Hello Alice echo "Date: $(date)" # Date: Mon Jan 15... - Single quotes
' '— everything is literal (no expansion):echo 'Hello $NAME' # Hello $NAME (literally) echo 'Date: $(date)' # Date: $(date) (literally)
Q: How do I debug a script that isn't working?
A: Use bash -x to run the script in debug mode. It shows each command before executing it:
bash -x ./myscript.sh
# + NAME=alice
# + echo 'Hello, alice'
# Hello, aliceThe + lines show what bash is actually executing.
Exercises#
Exercise 1: Variable practice
Write a script info.sh that prints your username, home directory, current date, and number of files in your home directory.
Solution
#!/bin/bash
# info.sh — System information
USERNAME=$(whoami)
HOME_DIR=$HOME
TODAY=$(date +%Y-%m-%d)
FILE_COUNT=$(ls ~ | wc -l)
echo "=== System Info ==="
echo "User: $USERNAME"
echo "Home: $HOME_DIR"
echo "Date: $TODAY"
echo "Files in home: $FILE_COUNT"chmod +x info.sh
./info.shExercise 2: Conditional script
Write a script check-tools.sh that checks if git, python3, and uv are installed, printing ✓ or ✗ for each.
Solution
#!/bin/bash
# check-tools.sh — Verify required tools
for tool in git python3 uv; do
if command -v "$tool" > /dev/null 2>&1; then
echo "✓ $tool is installed: $(command -v $tool)"
else
echo "✗ $tool is NOT installed"
fi
donechmod +x check-tools.sh
./check-tools.shcommand -v checks if a command exists. The > /dev/null 2>&1 suppresses output so we only see our custom messages.
Exercise 3: Loop script
Write a script that creates 5 files named day-1.md through day-5.md in a directory called /tmp/bootcamp/, each containing the text “Notes for Day X”.
Solution
#!/bin/bash
# create-days.sh — Create daily note files
mkdir -p /tmp/bootcamp
for i in {1..5}; do
echo "# Notes for Day $i" > "/tmp/bootcamp/day-$i.md"
echo "Created day-$i.md"
done
echo ""
echo "Files created:"
ls -la /tmp/bootcamp/chmod +x create-days.sh
./create-days.sh
cat /tmp/bootcamp/day-3.md
# # Notes for Day 3Exercise 4: Backup script
Write a script backup.sh that:
- Takes a directory name as an argument (
$1) - Checks if the directory exists
- If yes, copies it to
<name>-backup-<date>/ - If no, prints an error
Solution
#!/bin/bash
# backup.sh — Backup a directory
# Usage: ./backup.sh <directory>
DIR="${1:?Usage: $0 <directory>}"
DATE=$(date +%Y%m%d)
BACKUP="${DIR}-backup-${DATE}"
if [ ! -d "$DIR" ]; then
echo "Error: '$DIR' is not a directory or does not exist."
exit 1
fi
if [ -d "$BACKUP" ]; then
echo "Error: Backup '$BACKUP' already exists."
exit 1
fi
cp -r "$DIR" "$BACKUP"
echo "✓ Backed up '$DIR' → '$BACKUP'"
echo "Contents:"
tree "$BACKUP" -L 1Test it:
mkdir -p /tmp/myproject/{src,data}
chmod +x backup.sh
./backup.sh /tmp/myproject
# ✓ Backed up '/tmp/myproject' → '/tmp/myproject-backup-20260115'Exercise 5: MCQ
Q1: What is the correct shebang line for a bash script?
- A)
# /bin/bash - B)
#!/bin/bash - C)
//bin/bash - D)
@!/bin/bash
Answer
B — The shebang is #! followed by the path to the interpreter. #!/bin/bash tells the OS to use bash.
Q2: What does ${2:-"default"} mean?
- A) The variable
2minus"default" - B) Use
$2if provided, otherwise use"default" - C) Set variable 2 to
"default" - D) Delete variable 2
Answer
B — This is bash’s default value syntax. If $2 is not provided (or empty), it uses "default" instead.
Q3: What does $? contain after running a command?
- A) The command’s output
- B) The command’s name
- C) The exit code (0 = success, non-zero = failure)
- D) The command’s process ID
Answer
C — $? holds the exit code of the last command. Convention: 0 = success, anything else = failure. You can use it in conditionals:
grep "error" logfile.txt
if [ $? -eq 0 ]; then
echo "Errors found!"
fi