Basics of Bash


Bash/Shell scripting is one of the most vital skills that a developer needs to understand. Let’s take a quick historical tour of Bash scripting
Pre-Shell Era
Unix Shell: In the early days of Unix (circa 1971–1979), the first shell was the Thompson shell, written by Ken Thompson.
It was soon replaced by the Bourne Shell (
sh
), written by Stephen Bourne in 1979. This shell became the standard for Unix systems.Introduction to control structures like
if
,for
,while
The Rise of Shell Variants (1980)
C Shell & Korn Shell came with an introduction to C-like Syntax
Each shell had its strengths, but fragmentation became an issue: different systems used different default shells.
Birth of Bash (1989)
Bash stands for Bourne Again SHell.
Developed by Brian Fox for the GNU (GNU's Not Unix) Project.
It incorporated all the features from
csh
,ksh
, `sh
I believe in Project-Based Learning. So, let’s make a Shell Script for creating a Node.js Backend using Express.js
# function with params
greeting () {
if [[ -n $1 ]]; then
echo "Hello, $1!"
else
echo "Hello, unknown!"
fi
return 0
}
greeting Denys # Hello, Denys!
greeting # Hello, unknown!
Below is a function that takes a name and returns 0, indicating successful execution. This function takes an input and echoes it into the console.
what is -n
?
This reads the input, but does not execute it.
what is $1
?
In Bash, when you run a script with arguments like this:
./myscript.sh hello world
Inside the script, these are available as positional parameters:
$0
→ script name (./
myscript.sh
)$1
→"hello"
$2
→"world"
etc.
Short Name | Option Name | Description |
-f | noglob | Disable filename expansion (globbing). |
-i | interactive | Script runs in interactive mode. |
-n | noexec | Read commands, but don't execute them (syntax check). |
(none) | pipefail | Make pipelines fail if any command fails, not just if the final one fails. |
-t | (—) | Exit after the first command. |
-v | verbose | Print each command to stderr before executing it. |
-x | xtrace | Print each command and its expanded arguments to stderr before executing. |
Project Implementation: We will create a function to generate the project’s file structure and install all the necessary packages required for the development
# create file-structure
generate_express_app() {
}
generate_expess_app $1 $2
$1
, $2
They are placeholders for arguments for the project name and package manager to be used when creating the project. Now, every time before making a project, we need to validate if we are provided with the correct arguments. We achieve this by using an if-else block
# if-else
if [[ condition ]]; then
command
else
command
fi
In our case, the code would look like this
if [[ -n $1 && -n $2 ]]; then
# script for project setup
else
echo "Usage: generate_express_app <project-name> <npm|yarn|pnpm>"
fi
Now, let’s start the scripting
- We can create variables and refer to them like this
PROJECT_NAME=$1
PKG_MGR=$2
echo "$PROJECT_NAME" # projects the values stored in PROJECT_NAME
We have slightly different commands for installing packages and running scripts in npm
, yarn
and pnpm
. We handle this using this piece of code.
mkdir "$PROJECT_NAME" && cd "$PROJECT_NAME" || exit
if [[ $PKG_MGR = "npm" ]]; then
$PKG_MGR init -y
$PKG_MGR install express cors morgan http-errors express-session
$PKG_MGR install --save-dev typescript ts-node nodemon prettier dotenv @types/express @types/node @types/cors @types/morgan @types/express-session
else
$PKG_MGR init
$PKG_MGR add express cors morgan http-errors express-session
$PKG_MGR add -D typescript ts-node nodemon prettier dotenv @types/express @types/node @types/cors @types/morgan @types/express-session
fi
In this block of code, we first create the project directory and then, according to the package manager, we initialise the project and install the necessary packages.
Now we add all the commands that will be needed to start the projects
npm tsc --init
mkdir src && touch src/index.ts
, etc...
Now we will have to create files and add some code into them, for example, we need to setup the tsconfig.json
in our TypeScript projects. So we can use cat
the command with EOF
cat > tsconfig.json <<EOF
{
"compilerOptions": {
"target": "es2016",
"module": "node16",
"moduleResolution": "node16",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
EOF
The EOF
Command is one of the most interesting commands as it considers all the characters inside <<EOF ..... EOF
a string and replaces all the text in the files with exact indentation. We can use this command to popular code into their respective locations. Another Example Use Case
cat > .env <<EOF
PORT="8080"
SESSION_SECRET="your-secret"
FRONTEND_URL="http://localhost:3000"
DATABASE_URL=""
EOF
cp .env .env.example
Now, how would we setup our .gitignore
files for the projects?
We can use the above-mentioned method, but what if we use curl
a command in this case?
We make a get
request to an API Endpoint, which will return all the necessary gitignore
config for the project
curl -sL https://www.toptal.com/developers/gitignore/api/node -o .gitignore
In this case, we use the word famous Toptal gitignore to get the gitignore file for node-based projects.
Note : This is the gist Link for the final outcome for this project
Subscribe to my newsletter
Read articles from Abhinab Choudhury directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
