Bash Scripting
Table of Contents
- About the Terminal
- About the Shell
- The Shell as a File Manager
- Running Commands in the Shell
- The PATH to commands
- simple logic in bash
- Bang! Bang! She-Bang!
About the Terminal
If you're reading this, chances are that you have come across a terminal already. A terminal is , at the most basic level, some medium that displays some kind of information. When we talk about the first computers, which were made well over half a century ago, we talk about terminals that used paper to produce output and used punched cards to take input.
Nowadays, most terminals render output to a screen. The ones we are talking about here in particular, are the ones that are present in most unix like operating systems. They output to a screen, but usually , when one talks of say, a linux terminal, one doesn't talk of high end graphics. The point I am trying to make is that most terminals output plain text, and usually nothing more than plain text. (There are some exceptions , such as sixel, as well as kitty's protocols, etc)
Most unix like operating systems have a built in terminal even if they don't have graphical output. This terminal judges what kind of output to give depending on what devices are available. The "terminal" here is simply a program that can output text to a screen and take in input. By itself, it doesn't do much, but the best part about it is that we can write programs to tell it what to output and when to take in input. A classic example of such a program that takes advantage of the terminal is the classic "hello world" program, which is usually the first program that people write regardless of which programming language they are writing in. However, we can make it do a lot of other things as well.
Another example is say, a login screen, where the terminal asks the user for input (their username and password), passing those values back to the "login screen" program to check the credentials. Now there is a lot you can do just by outputting and manipulating text. For example, you can control the operating system itself. And this is actually a common use case. In fact the programs that allow you to control your operating system using a terminal are called "system shells".
Now , using the default "linux terminal" (or more accurately, the "linux console") might be a headache , because by default it provides only one (actually 12, from F1..F12) screens for you to do anything with. The workings of this "console" are more "internal" than regular programs, but for now we need something that can do the same things that the "console" can do, but without the restrictions of having to stay without a GUI (ever !) and having only one screen to stare at.
That is where a "Terminal Emulator" comes in. Simply put, it is a regular program that mimics the linux console. And just like the linux console, it is also, a "terminal" , in that it's job is also simply to display text and take in text input. And again, that is all you need.
Now, while the linux console starts with a "login screen" (called the "login shell", because it too, "controls" the operating system in a way), most terminal emulators default to more "standard" system shells.
What do I mean by "standard" ? Well for one, there's the POSIX standard, but some of these shells do not abide by it, but what I am talking about is the fact that these shells follow a similar structure. That is , they are a programming language with easy access to the operating system's internal functions.
The "shells" are programs that use the terminal (in this case, the terminal emulator) as a means to communicate with the user. Among the system shells in unix like OSes, bash is probably the most well known. Now I must clarify again, when you open a terminal emulator on your desktop, all the text you are seeing are being "rendered" by the terminal, but the "shell" is the one telling the terminal to render that so that it can , well, fulfill it's purpose.
About the Shell
Since there are so many shells in unix like OSes, we'll keep things simple and restrict out discussions to bash. Of course, you are free to use the python shell or the c shell or even the Holy C shell if you like, but they have slight syntactic differences, though they all function in the same way.
Now before we move forward, we need to make sure that everybody is using "bash". for that, simply open a terminal and type "bash" into it. It should probably open bash, because it should be installed by default. If that doesn't work, just type "sh" and we'll do with that for now.
Now whichever shell you found yourself in, it follows some general rules. Try typing random things in it and press enter and see what happens. You might find out that some things seem to do something while others return an error.
Why is that ?
Well, like I said ,most shells follow some basic rules.
each "word" separated by one or more whitespaces is considered an "argument" (it is possible to write literal whitespaces using
" "or' 'or just\)the first argument is searched up until it turns out to be one among the following
- a builtin function of the shell
- a user defined function
- a user defined "alias"
- a path to a file
- a filepath once completed with one of the directories listed in a variable called "PATH"
(in general, if the first argument is not a shell function/builtin/alias, then it is treated as a filepath, and the shell checks if it is executable. If it is , it tries to run the program specified in the filepath. In case you provide something that does not fulfil any of these conditions, the shell will just give you an error)
The Shell as a File Manager
One of the most powerful aspects of the shell is its ability to function as a file manager. In fact, before graphical file managers existed, people managed their files entirely through the command line. Even today, many system administrators and developers prefer the shell for file management tasks because it’s faster, more precise, and scriptable.
When you open a shell, you’re typically placed in your home directory. You can verify this by using the pwd command, which stands for “print working directory”:
pwd
This will output something like /home/yourusername, showing you exactly where you are in the filesystem.
To see what files and directories are in your current location, you use the ls command (short for “list”):
ls
This will show you the contents of your current directory. For more detailed information, you can use the -l flag (for “long format”):
ls -l
This will show you permissions, ownership, size, and modification time for each file and directory.
You can also combine flags. For example, to see all files (including hidden ones that start with a dot) in long format:
ls -la
Navigating between directories is done with the cd command (short for “change directory”). To go to a specific directory:
cd /path/to/directory
To go up one directory level:
cd ..
To go back to your home directory from anywhere:
cd
Or equivalently:
cd ~
The tilde (~) is a shorthand for your home directory.
Creating directories is done with the mkdir command (short for “make directory”):
mkdir new_directory
You can create multiple directories at once:
mkdir dir1 dir2 dir3
Or create a directory tree with the -p flag (which creates parent directories as needed):
mkdir -p parent/child/grandchild
Removing files and directories requires caution, as there’s no trash bin in the shell by default. To remove a file:
rm filename
To remove an empty directory:
rmdir directory_name
To remove a directory and all its contents (use with extreme caution!):
rm -rf directory_name
The -r flag means “recursive” (go into subdirectories) and -f means “force” (don’t ask for confirmation).
Copying files is done with the cp command:
cp source_file destination_file
To copy a directory and all its contents:
cp -r source_directory destination_directory
Moving or renaming files is done with the mv command:
mv old_name new_name
Or to move a file to a different directory:
mv file.txt /path/to/destination/
You can also move multiple files at once:
mv file1.txt file2.txt /path/to/destination/
Running Commands in the Shell
Now that we understand how to navigate and manage files in the shell, let’s look at how to actually run commands and programs.
As we discussed earlier, when you type a command in the shell, it goes through a specific process to determine what to execute. Let’s review that process with some practical examples.
When you type a command like ls, the shell:
- Checks if
lsis a builtin command (it’s not in bash -lsis an external program) - Checks if you’ve defined a function named
ls(you probably haven’t) - Checks if you’ve created an alias for
ls(many systems havelsaliased tols --color=autoby default) - Looks for
lsas a file in the directories listed in yourPATHvariable
Most of the commands you’ll use daily are external programs stored in directories like /bin, /usr/bin, /usr/local/bin, etc. These are found through the PATH mechanism we’ll discuss in the next section.
Let’s try running some simple commands to see how they work:
echo "Hello, World!"
This will output the text “Hello, World!” to your terminal. The echo command simply prints its arguments.
You can also combine commands using pipes and redirection, but we’ll get to that in a later section.
For now, let’s look at how to run a command and capture its output. You can store the output of a command in a variable like this:
current_dir=$(pwd)
echo "You are currently in: $current_dir"
This runs the pwd command, captures its output, stores it in the variable current_dir, and then prints it.
Another useful technique is running a command in the background. If you add an ampersand (&) to the end of a command, it will run in the background:
sleep 30 &
This starts a sleep command that will wait for 30 seconds, but because it’s running in the background, you’ll immediately get your prompt back and can continue working.
You can see what background jobs you have running with the jobs command:
jobs
And you can bring a background job to the foreground with the fg command.
The PATH to Commands
We mentioned the PATH variable briefly when discussing how the shell finds commands to execute. Let’s take a closer look at this important concept.
The PATH variable is an environment variable that contains a list of directories, separated by colons (:). When you type a command, the shell searches through each directory in your PATH (in order) looking for an executable file with that name.
You can view your current PATH with:
echo $PATH
This will output something like:
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Each of these directories is searched in order when you type a command. So if you have a file named myscript in both /usr/local/bin and /usr/bin, the version in /usr/local/bin will be used because it comes first in the PATH.
You can add directories to your PATH temporarily (for the current session) like this:
export PATH="$PATH:/new/directory"
Or to add a directory to the beginning of your PATH (so it’s searched first):
export PATH="/new/directory:$PATH"
If you want to make this change permanent, you’ll need to add the export command to your shell’s startup file (like ~/.bashrc or ~/.bash_profile).
Let’s look at a practical example. Suppose you’ve written a useful script and saved it in ~/scripts/mytool. You could run it every time by typing the full path:
/home/yourusername/scripts/mytool
But that’s tedious. Instead, you can add ~/scripts to your PATH:
export PATH="$PATH:$HOME/scripts"
Now you can simply type:
mytool
And the shell will find it in your ~/scripts directory.
It’s also worth noting that for security reasons, the current directory (.) is not included in the PATH by default. This means you can’t run a script in the current directory just by typing its name. You need to explicitly specify the current directory:
./myscript
The ./ means “the current directory”. This is a safety feature that prevents you from accidentally running a malicious script that might be disguised as a common command like ls or cd.
Simple Logic in Bash
So far we’ve looked at running individual commands, but the real power of the shell comes from being able to make decisions and repeat actions based on conditions. This is where bash’s programming capabilities shine.
Bash supports several types of conditional statements, with the most common being the if statement. Here’s the basic syntax:
if [ condition ]; then
# commands to run if condition is true
fi
Let’s look at a simple example that checks if a file exists:
if [ -f "myfile.txt" ]; then
echo "myfile.txt exists"
else
echo "myfile.txt does not exist"
fi
The [ -f "myfile.txt" ] part is a test that returns true if myfile.txt exists and is a regular file. There are many different tests you can perform:
-f file: True if file exists and is a regular file-d dir: True if dir exists and is a directory-x file: True if file exists and is executable-r file: True if file exists and is readable-w file: True if file exists and is writable-z string: True if string is empty-n string: True if string is not emptystring1 = string2: True if string1 equals string2string1 != string2: True if string1 does not equal string2num1 -eq num2: True if num1 equals numerically num2num1 -ne num2: True if num1 is not numerically equal to num2num1 -lt num2: True if num1 is less than num2num1 -le num2: True if num1 is less than or equal to num2num1 -gt num2: True if num1 is greater than num2num1 -ge num2: True if num1 is greater than or equal to num2
You can also combine conditions using logical operators:
&&means AND||means OR!means NOT
For example:
if [ -f "myfile.txt" ] && [ -r "myfile.txt" ]; then
echo "myfile.txt exists and is readable"
fi
This will only execute the echo command if both conditions are true: the file exists AND it’s readable.
Another useful construct is the case statement, which is like a switch statement in other languages:
case "$variable" in
pattern1)
# commands to run if variable matches pattern1
;;
pattern2)
# commands to run if variable matches pattern2
;;
*)
# commands to run if variable doesn't match any pattern
;;
esac
Here’s a practical example that might determine what action to take based on a file’s extension:
case "$filename" in
*.txt)
echo "This is a text file"
cat "$filename"
;;
*.jpg|*.jpeg)
echo "This is a JPEG image"
;;
*.mp3)
echo "This is an MP3 audio file"
;;
*)
echo "Unknown file type: $filename"
;;
esac
Bang! Bang! She-Bang!
We’ve been talking about running commands in the shell, but how do we actually create reusable scripts that we can run over and over? This is where the “shebang” (or “bang-bang” as some call it) comes in.
A shell script is simply a text file that contains a series of shell commands. To make it executable, you need to do two things:
- Add a shebang line at the very beginning of the file
- Make the file executable with the
chmodcommand
The shebang line looks like this:
#!/bin/bash
This line must be the very first line in the file (no spaces or characters before it). It tells the operating system which interpreter to use to run the script. In this case, we’re specifying that the script should be run using /bin/bash.
You could also use other shells:
#!/bin/shfor the Bourne shell or a compatible shell#!/usr/bin/env python3for Python 3 (though this wouldn’t be a bash script)#!/usr/bin/env perlfor Perl
Using /usr/bin/env is often preferred because it’s more portable across different systems:
#!/usr/bin/env bash
This tells the system to find the bash executable using the user’s PATH, which is more flexible than hardcoding a path like /bin/bash.
Let’s create a simple example script. We’ll make a script that backs up a file by copying it with a timestamp:
#!/usr/bin/env bash
# Simple backup script
if [ $# -ne 1 ]; then
echo "Usage: $0 filename"
exit 1
fi
if [ ! -f "$1" ]; then
echo "Error: File '$1' not found"
exit 1
fi
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP="$1.$TIMESTAMP.bak"
cp "$1" "$BACKUP"
echo "Backup created: $BACKUP"
Let’s break down what this script does:
#!/usr/bin/env bash- The shebang line that tells the system to use bash to run this script# Simple backup script- A comment (lines starting with # are comments in bash)if [ $# -ne 1 ]; then- Checks if the number of arguments ($#) is not equal to 1echo "Usage: $0 filename"- Prints a usage message ($0 is the name of the script)exit 1- Exits the script with an error code (1 indicates an error)if [ ! -f "$1" ]; then- Checks if the first argument ($1) is not a fileTIMESTAMP=$(date +%Y%m%d_%H%M%S)- Creates a timestamp string in the format YYYYMMDD_HHMMSSBACKUP="$1.$TIMESTAMP.bak"- Constructs the backup filenamecp "$1" "$BACKUP"- Copies the original file to the backup fileecho "Backup created: $BACKUP"- Prints a success message
To use this script, you would:
- Save it to a file (for example,
backup.sh) - Make it executable:
chmod +x backup.sh - Run it:
./backup.sh important_document.txt
The chmod +x command makes the file executable. The chmod command is used to change file permissions, and the +x flag adds execute permission.
You can also use octal notation with chmod:
chmod 755 filenamegives read, write, and execute permissions to the owner, and read and execute permissions to group and otherschmod 644 filenamegives read and write permissions to the owner, and read-only to group and others
Now that we’ve covered the basics of bash scripting, you have enough knowledge to start creating your own useful scripts to automate tasks on your system. Remember that the best way to learn is by doing - try creating small scripts to automate repetitive tasks you encounter in your daily work.
As you continue to explore bash, you’ll discover more advanced features like arrays, functions, and more sophisticated flow control structures. But even with what we’ve covered here, you can already create scripts that will save you time and effort.
Happy scripting!
