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
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
Add the following:
#!/bin/bash
echo "Hello, Linux!"
echo "I am learning Bash scripting."
Save the file and exit the editor.
Let's understand the two important parts.
-
#!/bin/bashis called the shebang. It tells the system which interpreter should execute the script when it is run as an executable. -
echoprints text to the terminal.
You can execute the script using:
bash hello.sh
Alternatively, make it executable:
chmod +x hello.sh
./hello.sh
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."
Notice that there are no spaces around the = sign when assigning a variable.
This works:
name="Evans"
This does not work as an ordinary assignment:
name = "Evans"
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"
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!"
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: $#"
Save it as arguments.sh, then run:
bash arguments.sh apple mango
The output will be:
First argument: apple
Second argument: mango
Total arguments: 2
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"
The script checks the number of arguments before proceeding.
-
-nemeans not equal. -
exit 1terminates 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
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
This is incorrect:
if ["$X" -gt "$Y"]; then
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
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
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
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
This gives the owner read and write permissions while removing those permissions from group members and others.
The numeric values represent combinations of permissions:
-
4means read. -
2means write. -
1means execute.
Therefore, 6 represents read plus write, because (4 + 2 = 6).
You can inspect file details using:
stat file1.txt
And change a file's modification timestamp using:
touch -d "2022-01-01 10:30:00" file1.txt
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"
You can inspect environment variables with:
printenv
Or search for a particular variable:
printenv PATH
The PATH variable contains a list of directories where the shell searches for executable commands.
When you type:
ls
your shell searches for an executable named ls in the directories listed in PATH.
You can inspect those directories with:
echo "$PATH"
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"
Add it to PATH for the current shell session:
export PATH="$HOME/myBins:$PATH"
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
You can run:
01exec
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
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'
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'
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
The > operator writes output to a file, replacing its previous contents.
To append instead:
echo "Another line" >> output.txt
You can redirect errors:
ls missing-file 2> errors.txt
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"
For this simple example, you can also write:
grep "Evans" names.txt
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
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
You can edit your personal cron schedule with:
crontab -e
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
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
Save it as search-file.sh, then run:
chmod +x search-file.sh
./search-file.sh notes.txt "Bash"
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:
- Fundamentals: commands, shebangs, variables, input, and output.
- Control flow: conditions, comparisons, logical operators, and loops.
- Arguments and validation: positional parameters, exit codes, and error handling.
- Filesystem operations: files, directories, permissions, and timestamps.
-
Shell environment: environment variables,
PATH, aliases, andsource. -
Command composition: pipes, redirection,
grep, and text processing. - Automation: cron jobs, logging, and scheduled execution.
- 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)
🙌