DEV Community

Juma Evans
Juma Evans

Posted on

Bash Scripting: From Your First Script to Linux Automation

When I first started working with Linux, the terminal felt like a place where I typed mysterious commands and hoped they worked.

I ran commands such as ls, cd, mkdir, and chmod. I literally copied commands from documentation, fixed errors when something broke, and gradually began to understand what each command does.

But eventually, you encounter a more interesting question:

What if you could combine these commands into a program that makes decisions and performs tasks automatically?

That is where Bash scripting comes in.

In this article, we will move from the fundamentals of Bash to practical concepts such as variables, conditions, command-line arguments, file permissions, environment variables, executable commands, and scheduled tasks.

Whether you are learning Linux for the first time or preparing for practical systems programming exercises, understanding these concepts will help you work more confidently in the terminal.

1. What is Bash?

Bash stands for Bourne Again SHell. It is a Unix shell that allows you to interact with your operating system by entering commands.

A shell can execute programs, manage variables, combine commands, and control how tasks run.

For example, consider these commands:

pwd
ls
mkdir projects
Enter fullscreen mode Exit fullscreen mode

They display your current directory, list its contents, and create a directory called projects.

Instead of entering every command manually, you can place several commands inside a script and execute them together.

A Bash script is simply a text file containing shell commands and, optionally, programming logic.

2. Writing your first Bash script

Let's create a simple script.

First, create a file:

nano hello.sh
Enter fullscreen mode Exit fullscreen mode

Add the following:

#!/bin/bash

echo "Hello, Linux!"
echo "I am learning Bash scripting."
Enter fullscreen mode Exit fullscreen mode

Save the file and exit the editor.

Let's understand the two important parts.

  • #!/bin/bash is called the shebang. It tells the system which interpreter should execute the script when it is run as an executable.
  • echo prints text to the terminal.

You can execute the script using:

bash hello.sh
Enter fullscreen mode Exit fullscreen mode

Alternatively, make it executable:

chmod +x hello.sh
./hello.sh
Enter fullscreen mode Exit fullscreen mode

The chmod +x command adds execute permission, while ./hello.sh runs the file from the current directory.

Key lesson: Writing a script and making a script executable are two different things.

3. Variables: Giving information a name

Variables allow you to store values that can be reused throughout a script.

#!/bin/bash

name="Evans"
language="Bash"

echo "My name is $name."
echo "I am learning $language."
Enter fullscreen mode Exit fullscreen mode

Notice that there are no spaces around the = sign when assigning a variable.

This works:

name="Evans"
Enter fullscreen mode Exit fullscreen mode

This does not work as an ordinary assignment:

name = "Evans"
Enter fullscreen mode Exit fullscreen mode

Bash interprets the second example as a command named name with additional arguments.

To access a variable, prefix its name with $, or use ${name} when clearer boundaries are needed.

project="backend"

echo "Working on ${project}-development"
Enter fullscreen mode Exit fullscreen mode

Reading input from a user

Variables can also store information entered at runtime.

#!/bin/bash

echo "What is your name?"
read -r name

echo "Welcome, $name!"
Enter fullscreen mode Exit fullscreen mode

The read command collects input, and -r prevents backslashes from being interpreted as escape characters.

This makes your script interactive instead of relying entirely on values written into the file.

4. Command-line arguments

Sometimes you want a script to accept information when you execute it rather than ask questions interactively.

Consider this script:

#!/bin/bash

echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
Enter fullscreen mode Exit fullscreen mode

Save it as arguments.sh, then run:

bash arguments.sh apple mango
Enter fullscreen mode Exit fullscreen mode

The output will be:

First argument: apple
Second argument: mango
Total arguments: 2
Enter fullscreen mode Exit fullscreen mode

Here is what the special variables mean:

Variable Meaning
$0 Name or invocation path of the script
$1 First positional argument
$2 Second positional argument
$# Number of positional arguments
$@ All positional arguments
$? Exit status of the previous command

These variables become especially useful when building scripts that accept filenames, directories, or configuration values.

Validating arguments

What if a script requires exactly one filename?

#!/bin/bash

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

echo "Provided file: $1"
Enter fullscreen mode Exit fullscreen mode

The script checks the number of arguments before proceeding.

  • -ne means not equal.
  • exit 1 terminates the script with a nonzero status, conventionally indicating failure.
  • "$1" preserves the argument as a single value, even if it contains spaces.

This is an important programming principle: validate your inputs before using them.

5. Conditions: Modifying scripts to make decisions

So far, our scripts have executed commands in sequence. Conditions allow them to behave differently depending on the situation.

Consider a numerical comparison:

#!/bin/bash

X=10
Y=5

if [ "$X" -gt "$Y" ]; then
    echo "X is greater than Y"
else
    echo "X is not greater than Y"
fi
Enter fullscreen mode Exit fullscreen mode

The condition checks whether X is greater than Y.

Common integer comparison operators include:

Operator Meaning
-eq Equal to
-ne Not equal to
-gt Greater than
-lt Less than
-ge Greater than or equal to
-le Less than or equal to

Why do the spaces matter?

This is valid:

if [ "$X" -gt "$Y" ]; then
Enter fullscreen mode Exit fullscreen mode

This is incorrect:

if ["$X" -gt "$Y"]; then
Enter fullscreen mode Exit fullscreen mode

In the first example, [ is a command that receives the condition's components as separate arguments. The spaces ensure those arguments are separated correctly.

The closing ] must also be a separate argument.

The then keyword begins the block of commands that runs when the condition succeeds, and fi closes the conditional statement.

You can add more branches using elif:

if [ "$X" -gt "$Y" ]; then
    echo "X is greater"
elif [ "$X" -eq "$Y" ]; then
    echo "They are equal"
else
    echo "Y is greater"
fi
Enter fullscreen mode Exit fullscreen mode

6. Working with files and directories

File checks are among the most practical features of Bash scripting.

Suppose you want a script to verify that a file exists before attempting to read it.

#!/bin/bash

if [ -f "$1" ]; then
    echo "File exists"
else
    echo "File does not exist"
fi
Enter fullscreen mode Exit fullscreen mode

The -f test checks whether the path refers to a regular file.

But existence is not the only thing that matters. You might also need to know whether a file is readable, writable, or executable.

Test Meaning
-e Path exists
-f Regular file
-d Directory
-r Read permission is available
-w Write permission is available
-x Execute permission is available

You can combine these checks:

#!/bin/bash

if [ "$#" -ne 1 ]; then
    echo "Error: Provide one file"
    exit 1
fi#!/bin/bash

echo "What is your name?"
read -r name

echo "Welcome, $name!"

file="$1"

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

    if [ -r "$file" ]; then
        echo "File is readable"
    fi

    if [ -w "$file" ]; then
        echo "File is writable"
    fi

    if [ -x "$file" ]; then
        echo "File is executable"
    fi
else
    echo "Not a regular file"
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

This combines argument validation, variables, conditions, and file tests in one practical example.

One important detail: -r, -w, and -x test access available to the current process. They do not simply report whether a permission bit appears in the output of ls -l.

7. File permissions and timestamps

Linux file permissions determine who can read, modify, or execute a file.

For example:

chmod 600 file1.txt
Enter fullscreen mode Exit fullscreen mode

This gives the owner read and write permissions while removing those permissions from group members and others.

The numeric values represent combinations of permissions:

  • 4 means read.
  • 2 means write.
  • 1 means execute.

Therefore, 6 represents read plus write, because (4 + 2 = 6).

You can inspect file details using:

stat file1.txt
Enter fullscreen mode Exit fullscreen mode

And change a file's modification timestamp using:

touch -d "2022-01-01 10:30:00" file1.txt
Enter fullscreen mode Exit fullscreen mode

The -d option lets you specify a date and time.

These commands are useful for understanding permissions, filesystem metadata, and how Linux represents files beyond their names and contents.

Be careful when experimenting with timestamps: touch changes metadata, not the contents of the file.

8. Environment variables and PATH

Some variables are used by a single script, while others are available to child processes.

An environment variable is a variable exported into the environment inherited by child processes.

For example:

export APP_ENV="development"
Enter fullscreen mode Exit fullscreen mode

You can inspect environment variables with:

printenv
Enter fullscreen mode Exit fullscreen mode

Or search for a particular variable:

printenv PATH
Enter fullscreen mode Exit fullscreen mode

The PATH variable contains a list of directories where the shell searches for executable commands.

When you type:

ls
Enter fullscreen mode Exit fullscreen mode

your shell searches for an executable named ls in the directories listed in PATH.

You can inspect those directories with:

echo "$PATH"
Enter fullscreen mode Exit fullscreen mode

Directory entries are separated by colons.

Adding your own executable directory

Suppose you create a directory called myBins in your home directory:

mkdir -p "$HOME/myBins"
Enter fullscreen mode Exit fullscreen mode

Add it to PATH for the current shell session:

export PATH="$HOME/myBins:$PATH"
Enter fullscreen mode Exit fullscreen mode

Now you can place executable scripts in that directory and run them by name.

For example, if an executable script is saved as:

~/myBins/01exec
Enter fullscreen mode Exit fullscreen mode

You can run:

01exec
Enter fullscreen mode Exit fullscreen mode

provided the directory is on PATH and the script has execute permission.

To keep this change across new interactive Bash sessions, add the export command to your ~/.bashrc file and reload it:

source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

The source command executes the file in the current shell, allowing its variable assignments and other shell changes to affect that session.

Important distinction: source runs commands in your current shell; executing a script as a separate process generally does not change the parent shell's variables.

9. Aliases: Creating shortcuts

An alias lets you define a shorter name for a command.

For example:

alias ll='ls -lah'
Enter fullscreen mode Exit fullscreen mode

Now typing ll runs ls -lah in the interactive shell.

You can define a custom alias for a command you use frequently:

alias myfiles='ls -l'
Enter fullscreen mode Exit fullscreen mode

Aliases are useful for interactive convenience, but they are generally not the best way to build reusable scripts. For automation and scripts, prefer functions or standalone executable programs where appropriate.

To make aliases available in future interactive Bash sessions, add them to ~/.bashrc.

10. Redirection and pipes

A command normally receives input from standard input and writes output to standard output. Error messages are commonly written to standard error.

Bash allows you to redirect these streams.

echo "Hello" > output.txt
Enter fullscreen mode Exit fullscreen mode

The > operator writes output to a file, replacing its previous contents.

To append instead:

echo "Another line" >> output.txt
Enter fullscreen mode Exit fullscreen mode

You can redirect errors:

ls missing-file 2> errors.txt
Enter fullscreen mode Exit fullscreen mode

Here, 2> redirects standard error to errors.txt.

A pipe sends one command's standard output into another command's standard input:

cat names.txt | grep "Evans"
Enter fullscreen mode Exit fullscreen mode

For this simple example, you can also write:

grep "Evans" names.txt
Enter fullscreen mode Exit fullscreen mode

Understanding redirection and pipes helps you combine small tools into useful workflows without writing a separate program for every task.

11. Automating tasks with cron

Running a script manually is useful, but sometimes you want it to run automatically.

Cron is a scheduling service on many Unix-like systems that can run commands at specified times.

For example, this cron expression:

0 8 * * * /home/user/scripts/backup.sh
Enter fullscreen mode Exit fullscreen mode

requests execution every day at 8:00 a.m., according to the cron service's local time configuration.

The five fields represent:

minute hour day-of-month month day-of-week
Enter fullscreen mode Exit fullscreen mode

You can edit your personal cron schedule with:

crontab -e
Enter fullscreen mode Exit fullscreen mode

Cron is useful for recurring jobs such as log cleanup, backups, or periodic reports.

However, scripts run by cron often have a more limited environment than scripts run from an interactive terminal. Use absolute paths where practical, configure required environment variables explicitly, and redirect output to a log when debugging.

For example:

0 8 * * * /home/user/scripts/backup.sh >> /home/user/backup.log 2>&1
Enter fullscreen mode Exit fullscreen mode

This appends both standard output and standard error to the log file.

Scheduling automation introduces an important new responsibility: a script must not only work when you run it manually; it must also work reliably in the environment where it is scheduled.

12. Putting the concepts together

Let's combine several of the ideas into one useful script.

This script checks whether a regular file exists, verifies that it is readable, and then searches it for a phrase.

#!/bin/bash

if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <filename> <search-term>"
    exit 1
fi

file="$1"
search_term="$2"

if [ ! -f "$file" ]; then
    echo "Error: File does not exist or is not a regular file"
    exit 1
fi

if [ ! -r "$file" ]; then
    echo "Error: File is not readable"
    exit 1
fi

grep -n -- "$search_term" "$file"
status=$?

if [ "$status" -eq 0 ]; then
    echo "Search completed: matches found"
elif [ "$status" -eq 1 ]; then
    echo "Search completed: no matches found"
else
    echo "Error: Search could not be completed"
    exit "$status"
fi
Enter fullscreen mode Exit fullscreen mode

Save it as search-file.sh, then run:

chmod +x search-file.sh
./search-file.sh notes.txt "Bash"
Enter fullscreen mode Exit fullscreen mode

The script combines several concepts:

  • Command-line argument validation.
  • Variables and quoting.
  • File existence and readability tests.
  • Negation using !.
  • Running an external command.
  • Capturing and interpreting its exit status.
  • Returning meaningful exit codes.

The distinction between grep exit statuses matters here: 0 means matches were found, 1 means no matches were found, and a status greater than 1 indicates an error.

This is no longer just a sequence of terminal commands. It is a small program that validates input, checks its environment, handles different outcomes, and communicates results.

13. How to keep progressing

The best way to learn Bash is to build progressively more capable scripts rather than using single commands in isolation.

A sensible learning path looks like this:

  1. Fundamentals: commands, shebangs, variables, input, and output.
  2. Control flow: conditions, comparisons, logical operators, and loops.
  3. Arguments and validation: positional parameters, exit codes, and error handling.
  4. Filesystem operations: files, directories, permissions, and timestamps.
  5. Shell environment: environment variables, PATH, aliases, and source.
  6. Command composition: pipes, redirection, grep, and text processing.
  7. Automation: cron jobs, logging, and scheduled execution.
  8. Reliable scripts: quoting, defensive checks, debugging, and testing.

As you progress, also learn the difference between shell syntax and external commands. For example, if is a Bash keyword, [ is a command used to evaluate conditions, and grep is an external utility.

That distinction makes errors easier to understand and helps you reason about what your scripts are actually doing.

Final thoughts

Bash scripting is more than learning how to automate a few commands. It teaches you to think in terms of inputs, conditions, execution environments, permissions, and failure handling.

Those ideas are useful well beyond Bash. They also appear in backend development, deployment workflows, CI/CD pipelines, containers, and production troubleshooting.

Start with a script that prints a message. Then write one that accepts arguments, checks a file, and handles errors. Eventually, you will be able to automate tasks that would otherwise require repeated manual work.

Top comments (1)

Collapse
 
csm18 profile image
csm •

🙌