| | |

LFCA 56 ๐Ÿง Shell Scripting โ€” Your First Script

Shell scripting is the art of writing a list of commands into a file so the computer can run them for you. Instead of typing the same five commands every morning to back up a folder, you type them once into a script, make it executable, and run it with a single word. That is the entire premise: automation of the repetitive.

The LFCA exam places shell scripting under Linux Fundamentals, which carries 20% of the total weight . Within that domain, the competencies include “command line” and “basic programming” . Shell scripting sits at the intersection of those two: it is programming with commands. You are not writing a compiled application; you are orchestrating the tools the operating system already provides.

This chapter covers the anatomy of a shell script, the shebang line, executable permissions, variables, input handling, and the commands that turn a text file into a working automation tool.

Key point: A shell script is a text file containing shell commands. When you run it, the shell reads the file line by line and executes each command as if you had typed it. The first line, the shebang, tells the operating system which shell should interpret the file. Without it, the script may run under the wrong interpreter, or not at all .


Why shell scripts exist

You could argue that every shell script is just a sequence of commands you could type manually. That is true. But the moment you type a sequence more than once, you have a reason to script it.

The repetition problem. System administration is full of repetitive tasks: checking disk space, rotating logs, backing up directories, creating user accounts in bulk. A script turns a five-minute manual process into a one-second command. The LFCA exam expects you to understand this โ€” the System Administration Fundamentals domain (30%) includes “best practices,” and automation is a core best practice .

The consistency problem. When you type commands manually, you make mistakes. You forget a flag, misspell a filename, run the steps in the wrong order. A script encodes the correct sequence once and runs it identically every time. This is why scripts are considered infrastructure, not convenience.

The documentation problem. A well-written script is documentation. It shows exactly what commands were run, in what order, with what arguments. A new administrator can read the script and understand the procedure without asking anyone.

The composition problem. Scripts can call other scripts. They can be scheduled with cron. They can be triggered by system events. A single script is a building block; a collection of scripts is an automation system.

The trade-off. Shell scripts are not compiled, not type-checked, and not portable across all Unix systems without care. They are best for glue โ€” connecting commands, automating sequences, handling files. For complex logic, a proper programming language is often better. But for the 80% of system tasks that are “run these commands in this order,” shell scripting is the right tool.


a. The Anatomy of a Script: Shebang, Comments, Commands

Every shell script has three layers: the shebang, optional comments, and the commands themselves.

The shebang is the first line of the file. It begins with #! followed by the path to the interpreter. For Bash scripts, the standard shebang is #!/bin/bash . The #! characters tell the operating system “this file is a script, and the program that follows is the interpreter.” When you execute the script, the kernel reads that line, launches the specified interpreter, and passes the script file to it .

There is a more portable variant: #!/usr/bin/env bash. This uses the env command to find bash in the user’s PATH, which is useful on systems where Bash is installed somewhere other than /bin . For most Linux systems, #!/bin/bash is fine. For maximum portability, #!/usr/bin/env bash is safer.

Comments begin with #. Everything after the hash on that line is ignored by the shell . Comments are not optional in real scripts. They explain why the script does what it does โ€” the what is visible in the commands themselves.

#!/bin/bash
# backup.sh - Backs up the Documents folder to an external drive
# Author: LFCA Student
# Date: 2026-05-03

# Set the date suffix for the backup filename
BACKUPDATE=$(date +%b-%d-%y)

# Define source and destination
SOURCEFOLDER=/home/user/Documents
DESTINATION=/media/user/data/DOCUMENTS-$BACKUPDATE.tar.gz

# Create the backup
tar -cpzf "$DESTINATION" "$SOURCEFOLDER"

echo "Backup created: $DESTINATION"

The commands are executed line by line, in order . The shell reads the first command, runs it, waits for it to finish, then moves to the next. If a command fails, the shell (by default) continues to the next line. This behavior is important: a script does not stop on error unless you tell it to.


b. Making It Executable: Permissions and Execution

A script file is not executable by default. When you create a file with touch or a text editor, it gets read and write permissions for the owner, and read permissions for everyone else . The execute bit is off.

To make the script runnable, you use chmod to add the execute permission:

chmod +x hello_world.sh

The +x adds execute permission for all users (owner, group, and others). A more precise version is chmod u+x hello_world.sh, which adds execute permission only for the owner . Either works for personal scripts.

Once the script is executable, you run it by specifying its path:

./hello_world.sh

The ./ prefix is necessary because the current directory is usually not in your PATH. The shell interprets ./hello_world.sh as “run the file named hello_world.sh in the current directory” .

There is an alternative execution method that does not require the execute bit:

bash hello_world.sh

This explicitly invokes Bash and passes the script file as an argument. The shebang line is ignored in this case because you have already specified the interpreter on the command line .

The difference matters. When you run ./script.sh, the kernel reads the shebang and uses the specified interpreter. When you run bash script.sh, you override the shebang and use Bash regardless. For most scripts, both produce the same result. But if the script uses a feature specific to another shell (like zsh or sh), the shebang matters.


c. Variables and Input: Making Scripts Flexible

A script that hardcodes every value is barely better than typing the commands manually. Variables and input handling turn a script into a reusable tool.

Variables in Bash are assigned without spaces around the equals sign. GREETINGS="Hello" is correct; GREETINGS = "Hello" is not . You reference a variable with a dollar sign: echo $GREETINGS or echo ${GREETINGS}. The braces are optional but recommended when the variable is adjacent to other text, because ${GREETINGS}world is unambiguous while $GREETINGSworld would try to read a variable named GREETINGSworld.

Variables can hold the output of commands through command substitution:

BACKUPDATE=$(date +%b-%d-%y)

The $(...) syntax runs the command inside the parentheses and substitutes its output . This is how scripts capture dynamic values โ€” dates, filenames, system state โ€” and use them in subsequent commands.

The read command lets a script accept input interactively:

echo "What is your name?"
read NAME
echo "Hello, $NAME"

When the script reaches read, it pauses and waits for the user to type something and press Enter. The typed text is stored in the variable NAME .

For scripts that should accept arguments when invoked, Bash provides positional parameters. $1 is the first argument, $2 is the second, and so on. $0 is the name of the script itself. $# is the number of arguments passed.

#!/bin/bash
# greet.sh - Greets the user by name

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

echo "Hello, $1"

Running ./greet.sh Alice prints Hello, Alice. Running ./greet.sh with no arguments prints the usage message and exits with code 1 .

The if statement checks a condition. [ $# -eq 0 ] is true when the number of arguments is zero. The exit 1 terminates the script with a non-zero exit code, which signals failure to whatever called the script.


Complete Example Session

This session builds a working script from scratch, then adds variables, input, and a conditional check.

# ============================================
# PART 1: THE HELLO WORLD SCRIPT
# ============================================

#!/bin/bash
echo "Hello, World!"

# ============================================
# PART 2: THE VARIABLES SCRIPT
# ============================================

#!/bin/bash
GREETINGS="Hello! How are you"
echo $GREETINGS

# ============================================
# PART 3: THE READ INPUT SCRIPT
# ============================================

#!/bin/bash
echo "What is your name?"
read NAME
echo "Hello, $NAME"

# ============================================
# PART 4: THE COMMAND SUBSTITUTION SCRIPT
# ============================================

#!/bin/bash
TODAY=$(date +%Y-%m-%d)
echo "Today is $TODAY"

# ============================================
# PART 5: THE ARGUMENT CHECK SCRIPT
# ============================================

#!/bin/bash
if [ $# -eq 0 ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi

echo "You provided: $1"

# ============================================
# PART 6: THE FILE EXISTENCE CHECK
# ============================================

#!/bin/bash
if [ -f "$1" ]; then
    echo "File exists: $1"
    wc -l "$1"
else
    echo "File not found: $1"
    exit 2
fi

# ============================================
# PART 7: THE BACKUP SCRIPT
# ============================================

#!/bin/bash
BACKUPDATE=$(date +%b-%d-%y)
DESTINATION=/tmp/backup-$BACKUPDATE.tar.gz
SOURCEFOLDER=/home/user/Documents

tar -cpzf "$DESTINATION" "$SOURCEFOLDER"
echo "Backup created: $DESTINATION"

# ============================================
# PART 8: THE LOOP SCRIPT
# ============================================

#!/bin/bash
echo "Counting from 1 to 5:"
for i in {1..5}; do
    echo "Number: $i"
done

# ============================================
# PART 9: THE EXIT CODE CHECK
# ============================================

#!/bin/bash
ls /etc/passwd > /dev/null
if [ $? -eq 0 ]; then
    echo "The file /etc/passwd exists."
else
    echo "The file does not exist."
fi

# ============================================
# PART 10: THE COMPLETE SCRIPT WITH COMMENTS
# ============================================

#!/bin/bash
# check_file.sh - Reports line, word, and character counts
# Usage: ./check_file.sh <filename>

# Check if an argument was provided
if [ $# -eq 0 ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi

filename="$1"

# Check if the file exists
if [ ! -f "$filename" ]; then
    echo "Error: File '$filename' not found."
    exit 2
fi

# Count lines, words, and characters
lines=$(wc -l < "$filename")
words=$(wc -w < "$filename")
chars=$(wc -c < "$filename")

echo "File statistics for '$filename':"
echo "Lines: $lines"
echo "Words: $words"
echo "Characters: $chars"

exit 0

The ten parts cover the Hello World script, variables, read input, command substitution, argument checking, file existence checking, a backup script, a loop, exit code checking, and a complete script with comments.


Quick Reference

The Script Structure

ElementPurposeExample
ShebangSpecify interpreter#!/bin/bash
CommentDocument the script# backup.sh - Backs up Documents
CommandAction to performecho "Hello"
VariableStore a valueNAME="Alice"
Command substitutionCapture command outputTODAY=$(date)

The Execution Methods

MethodRequires Execute BitUses Shebang
./script.shYesYes
bash script.shNoNo

The Special Variables

VariableMeaning
$0Script name
$1, $2First and second arguments
$#Number of arguments
$?Exit code of last command

The Test Conditions

ConditionTrue When
[ -f "$file" ]File exists and is a regular file
[ -d "$dir" ]Directory exists
[ $# -eq 0 ]No arguments provided
[ $? -eq 0 ]Last command succeeded

Best Practices

โœ… Do This:

#!/bin/bash                                          # โœ…
# Add a comment explaining the script's purpose      # โœ…
# and any non-obvious commands
chmod +x script.sh                                   # โœ…
if [ $# -eq 0 ]; then                                # โœ…
    echo "Usage: $0 <filename>"
    exit 1
fi
filename="$1"                                        # โœ… quote variables
if [ -f "$filename" ]; then
exit 0                                               # โœ… explicit success

โŒ Don’t Do This:

echo "Hello"                                         # โŒ no shebang
NAME = "Alice"                                       # โŒ spaces around =
if [ $# -eq 0 ]; then                                # โŒ no usage message
    exit 1
fi
if [ -f $filename ]; then                            # โŒ unquoted variable
./script.sh                                          # โŒ no chmod +x first

Common Pitfalls

PitfallWhy It HappensFix
Permission deniedScript not executableRun chmod +x script.sh
Command not foundMissing shebang or wrong pathAdd #!/bin/bash as first line
Variables emptySpaces around = in assignmentUse NAME="value" not NAME = "value"
Script ignores arguments$1 used without checking $#Add a usage check
Unquoted variable errorFilename with spacesQuote variables: "$filename"
Script continues after errorDefault behaviorUse exit or check $?

Real-World Examples

1. Hello World

#!/bin/bash
echo "Hello, World!"

2. Variable Greeting

#!/bin/bash
GREETINGS="Hello! How are you"
echo $GREETINGS

3. Read Input

#!/bin/bash
echo "What is your name?"
read NAME
echo "Hello, $NAME"

4. Date Stamp

#!/bin/bash
TODAY=$(date +%Y-%m-%d)
echo "Today is $TODAY"

5. Argument Check

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

6. File Existence

if [ -f "$1" ]; then
    echo "File exists"
fi

7. Backup with tar

BACKUPDATE=$(date +%b-%d-%y)
tar -cpzf /backup/data-$BACKUPDATE.tar.gz /home/user/data

8. For Loop

for i in {1..5}; do
    echo "Number: $i"
done

9. Exit Code Check

ls /etc/passwd > /dev/null
if [ $? -eq 0 ]; then
    echo "Found"
fi

10. Complete File Check

#!/bin/bash
if [ ! -f "$1" ]; then
    echo "Not found: $1"
    exit 2
fi
wc -l "$1"

Visual: Script Anatomy

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SCRIPT ANATOMY                              โ”‚
โ”‚                                              โ”‚
โ”‚  #!/bin/bash          โ† shebang (interpreter)โ”‚
โ”‚  # backup.sh - Backup  โ† comment             โ”‚
โ”‚  # Author: LFCA Student                     โ”‚
โ”‚                                              โ”‚
โ”‚  BACKUPDATE=$(date)    โ† variable            โ”‚
โ”‚  SOURCE=/home/user     โ† variable            โ”‚
โ”‚                                              โ”‚
โ”‚  tar -cpzf "$DEST" "$SRC"  โ† command         โ”‚
โ”‚  echo "Done"           โ† command             โ”‚
โ”‚                                              โ”‚
โ”‚  exit 0                โ† exit code           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Execution Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  EXECUTION FLOW                              โ”‚
โ”‚                                              โ”‚
โ”‚  ./script.sh                                 โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Kernel reads shebang: #!/bin/bash           โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Bash launches and opens script file         โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Line 1 command โ†’ run โ†’ wait                 โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Line 2 command โ†’ run โ†’ wait                 โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  ...                                         โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  End of file โ†’ exit with last code           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Variables and Arguments

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  VARIABLES AND ARGUMENTS                     โ”‚
โ”‚                                              โ”‚
โ”‚  Script: ./greet.sh Alice Bob                โ”‚
โ”‚                                              โ”‚
โ”‚  $0 = ./greet.sh    โ† script name            โ”‚
โ”‚  $1 = Alice         โ† first argument         โ”‚
โ”‚  $2 = Bob           โ† second argument        โ”‚
โ”‚  $# = 2             โ† argument count         โ”‚
โ”‚                                              โ”‚
โ”‚  NAME="Alice"       โ† script variable        โ”‚
โ”‚  echo "$NAME"       โ† reference variable     โ”‚
โ”‚                                              โ”‚
โ”‚  Always quote: "$NAME"                       โ”‚
โ”‚  Not: $NAME (breaks on spaces)               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Test Conditions

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  TEST CONDITIONS                             โ”‚
โ”‚                                              โ”‚
โ”‚  [ -f "$file" ]     file exists              โ”‚
โ”‚  [ -d "$dir" ]      directory exists         โ”‚
โ”‚  [ $# -eq 0 ]       no arguments             โ”‚
โ”‚  [ $? -eq 0 ]       last command succeeded   โ”‚
โ”‚  [ "$a" = "$b" ]    strings equal            โ”‚
โ”‚                                              โ”‚
โ”‚  if [ condition ]; then                      โ”‚
โ”‚      commands                                โ”‚
โ”‚  else                                        โ”‚
โ”‚      other commands                          โ”‚
โ”‚  fi                                          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Shebang#!/bin/bash
Comment#
Variable assignmentNAME="value" (no spaces)
Variable reference$NAME or ${NAME}
Command substitution$(command)
Inputread NAME
Arguments$1, $2, $#
Test[ condition ]
Exit codeexit 0 for success
Executechmod +x then ./script.sh

Key takeaways:

  • A shell script is a text file of commands with a shebang. The #!/bin/bash line tells the system to use Bash to interpret the file. Without it, the script may not run correctly .
  • The execute bit is required for ./script.sh. Use chmod +x to add it. Alternatively, bash script.sh runs the script without the execute bit .
  • Variables are assigned without spaces and referenced with $. NAME="Alice" is correct; NAME = "Alice" is not. Quote variables when using them in tests or commands to handle spaces .
  • Command substitution captures output. TODAY=$(date +%Y-%m-%d) runs the date command and stores its output in TODAY .
  • read accepts interactive input. The script pauses and waits for the user to type. Positional parameters ($1, $2) accept input when the script is invoked .
  • Test conditions enable logic. [ -f "$file" ] checks file existence. [ $# -eq 0 ] checks for arguments. [ $? -eq 0 ] checks the last command’s exit code .
  • Comments document intent. A script without comments is a puzzle. Add a header, explain non-obvious commands, and record the author and date.

Remember: Your first script does not need to be complex. The Hello World script is a legitimate script โ€” it has a shebang, a command, and an output. From there, add variables, then input, then conditionals. Each addition makes the script more useful. The LFCA exam tests the fundamentals: shebang, permissions, variables, and basic logic. Master those, and you have the foundation for every automation task that follows.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!