Your First Script#

Concepts#

What Is a Shell Script?#

A shell script is a text file containing a sequence of commands that the shell executes in order. Instead of typing commands one by one, you write them in a file and run the file. Scripts automate repetitive tasks, glue commands together, and can include logic (decisions, loops).

The Shebang#

The first line of every script should be the shebang (also called hashbang):

#!/bin/bash

This tells the system which program should interpret the script. Without it, the system might use a different shell (like sh or dash), which may not support all Bash features.

Common shebangs:

Shebang Interpreter
#!/bin/bash Bash (use this for this course)
#!/bin/sh POSIX shell (more portable, fewer features)
#!/usr/bin/env bash Bash, found via PATH (more portable across systems)
#!/usr/bin/env python3 Python 3

Debian note: On Debian, /bin/sh is dash (a minimal, fast POSIX shell), NOT Bash. Scripts using Bash-specific features (like [[, arrays, {1..10}) must use #!/bin/bash, not #!/bin/sh.

Creating and Running a Script#

Step 1: Write the script#

nano ~/myscript.sh
#!/bin/bash
echo "Hello, $(whoami)!"
echo "Today is $(date)"
echo "You are in: $(pwd)"

Step 2: Make it executable#

chmod +x ~/myscript.sh

Step 3: Run it#

# Using the path
~/myscript.sh

# Or explicitly with bash
bash ~/myscript.sh

# From the current directory
cd ~
./myscript.sh

Why ./? When you type a command without a path, the shell looks for it in directories listed in $PATH. Your current directory is usually not in $PATH for security reasons. ./ explicitly means “this directory.”

Script Structure#

A well-written script follows this structure:

#!/bin/bash
# Description: Brief description of what this script does
# Author: Your Name
# Date: 2024-10-15

# --- Configuration / Variables ---
LOGFILE="/var/log/myapp.log"
MAX_RETRIES=3

# --- Functions (if any) ---
# (covered in lesson 05)

# --- Main Logic ---
echo "Starting..."
# ... commands ...
echo "Done."

Comments#

# This is a comment — the shell ignores it
echo "This runs"  # Inline comment — everything after # is ignored

Comments are essential for explaining why (not what) your code does.

Exit Codes#

Every command returns an exit code (also called return code or exit status):

  • 0 = success
  • Non-zero (1 to 255) = failure
# Check the exit code of the last command
ls /etc/hosts
echo $?
# 0 (success)

ls /nonexistent
echo $?
# 2 (failure — file not found)

$? is a special variable that holds the exit code of the last command.

Your scripts should also set exit codes:

#!/bin/bash
# Exit with success
exit 0

# Exit with failure
exit 1

If no exit statement is given, the script exits with the exit code of the last command executed.

Running Commands in Sequence#

# Semicolon — run regardless of success/failure
command1 ; command2

# AND (&&) — run command2 only if command1 succeeds
command1 && command2

# OR (||) — run command2 only if command1 fails
command1 || command2

Examples:

# Update and install, but only install if update succeeds
sudo apt update && sudo apt install -y nginx

# Try to cd into a directory; if it fails, print an error
cd /some/dir || echo "Directory does not exist!"

# Common pattern: do something or exit
cd /important/directory || exit 1

Reading User Input#

#!/bin/bash
echo -n "What is your name? "
read name
echo "Hello, $name!"
# Read with a prompt (no echo needed)
read -p "Enter your age: " age
echo "You are $age years old."

# Read silently (for passwords)
read -sp "Enter password: " password
echo ""  # newline after hidden input
echo "Password received (${#password} characters)."

# Read with a timeout
read -t 5 -p "Enter something (5 seconds): " response || echo "Timed out!"

# Read with a default (using parameter expansion)
read -p "Port [8080]: " port
port=${port:-8080}
echo "Using port: $port"

Script Arguments#

Scripts can receive arguments from the command line:

#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
./myscript.sh hello world
# Script name: ./myscript.sh
# First argument: hello
# Second argument: world
# All arguments: hello world
# Number of arguments: 2
Variable Meaning
$0 Script name/path
$1 to $9 Positional arguments 1-9
${10} Argument 10+ (braces required)
$@ All arguments (as separate words)
$* All arguments (as a single string)
$# Number of arguments
$$ PID of the script itself
$! PID of the last background process
$? Exit code of the last command

Lab#

Exercise 1: Your First Script#

mkdir -p ~/lab/scripts
cd ~/lab/scripts

cat > hello.sh << 'SCRIPT'
#!/bin/bash
# My first shell script
echo "Hello, $(whoami)!"
echo "Today is $(date '+%A, %B %d, %Y')"
echo "Uptime: $(uptime -p)"
SCRIPT

chmod +x hello.sh
./hello.sh

Exercise 2: Exit Codes#

cd ~/lab/scripts

cat > exitcodes.sh << 'SCRIPT'
#!/bin/bash
# Demonstrating exit codes

echo "Trying to list /etc/hosts..."
ls /etc/hosts
echo "Exit code: $?"

echo ""

echo "Trying to list /nonexistent..."
ls /nonexistent
echo "Exit code: $?"

echo ""

echo "Using && and ||:"
ls /etc/hosts && echo "Success!" || echo "Failed!"
ls /nonexistent && echo "Success!" || echo "Failed!"
SCRIPT

chmod +x exitcodes.sh
./exitcodes.sh

Exercise 3: Script Arguments#

cd ~/lab/scripts

cat > greet.sh << 'SCRIPT'
#!/bin/bash
# Greet a user by name

if [ $# -eq 0 ]; then
    echo "Usage: $0 <name>"
    exit 1
fi

echo "Hello, $1!"
echo "This script received $# argument(s): $@"
SCRIPT

chmod +x greet.sh
./greet.sh
./greet.sh Alice
./greet.sh Alice Bob Carol

Exercise 4: Reading Input#

cd ~/lab/scripts

cat > interactive.sh << 'SCRIPT'
#!/bin/bash
# Interactive script

read -p "What is your name? " name
read -p "What is your favorite color? " color
read -p "How old are you? " age

echo ""
echo "Summary:"
echo "  Name:  $name"
echo "  Color: $color"
echo "  Age:   $age"
SCRIPT

chmod +x interactive.sh
./interactive.sh

Exercise 5: A Practical Script — System Info#

cd ~/lab/scripts

cat > sysinfo.sh << 'SCRIPT'
#!/bin/bash
# Display system information

echo "=== System Information ==="
echo "Hostname:     $(hostname)"
echo "OS:           $(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '"')"
echo "Kernel:       $(uname -r)"
echo "Architecture: $(uname -m)"
echo "Uptime:       $(uptime -p)"
echo "Users logged: $(who | wc -l)"
echo ""
echo "=== Memory ==="
free -h | head -2
echo ""
echo "=== Disk Usage ==="
df -h / | tail -1 | awk '{printf "Root filesystem: %s used of %s (%s)\n", $3, $2, $5}'
echo ""
echo "=== Top 5 Processes by Memory ==="
ps aux --sort=-%mem | head -6
SCRIPT

chmod +x sysinfo.sh
./sysinfo.sh

Clean Up#

Keep the ~/lab/scripts/ directory — you will use it in the next lessons.


Review#

1. What is the shebang and why is it important?

The shebang (#!/bin/bash) is the first line of a script. It tells the system which interpreter to use. Without it, the system may use a different shell that does not support Bash-specific features.

2. How do you make a script executable?

chmod +x script.sh. Then run it with ./script.sh (from the current directory) or /full/path/to/script.sh.

3. Why do you need `./` to run a script in the current directory?

The current directory is not in $PATH by default (for security). ./ explicitly tells the shell to look in the current directory.

4. What does `$?` contain?

The exit code of the last executed command. 0 means success; any non-zero value means failure.

5. What is the difference between `&&` and `||`?

&& runs the next command only if the previous one succeeded (exit code 0). || runs the next command only if the previous one failed (non-zero exit code).

6. How do you access the first argument passed to a script?

$1. The second argument is $2, all arguments are $@, and the count of arguments is $#.

7. Why does `/bin/sh` behave differently on Debian vs Ubuntu?

On Debian, /bin/sh is dash (a minimal POSIX shell). On Ubuntu, /bin/sh is also dash (not Bash). Scripts using Bash features (arrays, [[, brace expansion) must use #!/bin/bash, not #!/bin/sh.


Previous: Logs and journalctl | Next: Variables and Quoting