Cover image for πŸš€ The Missing Shell Scripting Crash Course

πŸš€ The Missing Shell Scripting Crash Course

godcrampy profile image Sahil Bondre Updated on ・6 min read

Bash is a shell and a command language. You can use it like any other language to write scripts. Bash scripts can run on Linux and Mac right off. For Windows, there's a small workout to be done. In this tutorial I'll be going over Shell Syntax and not Shell Commands like ls, grep, cat.

This crash-course will teach you all the necessary stuff you need to get up and running with writing shell scripts.

1. Hello World

All you need in this tutorial is your terminal and a text editor. Let's write a simple hello world in bash. Create a new file called script.sh and write the following code:


echo "Hello World"

Now save this file. To make this executable, run chmod +x ./script.sh in your terminal. Then you can run the above script using ./script.sh in your terminal.

Hello World

Tah Dah! Your first bash script. Let's examine the code closely. The first line #!/bin/bash is called a shebang. It tells your computer which program to give this code to run. In our case, it is /bin/bash.
If you wanted to write javascript instead of bash you could write the following code.


console.log("Hello Javascript")'
$ ./script.sh
Hello Javascript

The above code will work only if you have node installed in /bin/ directory. So essentially you can write code in any language. All you need to do is specify which program can handle that code and your system will pass on that file to that program.

Let's hop back to bash now. In the above code, the second line is echo "Hello World". echo is a command which prints stuff to the Standard Output which is the terminal in our case.

2. Variables

Let's modify the above code a little bit.



echo "Hello, $name"
$ ./script.sh
Hello, Bash

We create a variable called name and store the string "Bash" to it. Then to access it, we need to reference it with $. If you forget the $ sign, bash will treat name as a string literal, and you'll get Hello name as output instead.

Note that there are no spaces around the = while defining a variable. Also, use double quotes while referencing.

3. User Input

Let's continue modifying our script. We'll request the user for their name and then greet them back.


read -p "What is you name: " name

echo "Hello $name"
$ ./script.sh
What is you name: godcrampy
Hello godcrampy

The read command takes in one line of input from the Standard Input and saves it to the variable name. The -p flag allows us to pass a prompt of "What is your name: " before taking in the input.

One neat trick to reference variables in a string is to use curly braces:


read -p "Enter an action: " verb

echo "You are ${verb}ing"
$ ./script.sh
Enter an action: cook
You are cooking


# Comments start with hash

Multiline Comments are a bit of a complicated mess.

5. Arguments

$1, $2 and so on, store the arguments passed into the script. $0 stores the file name.


echo $0
echo $1
echo $2
echo "${@}" # Access all the arguments [More on this later]
$ ./script.sh first second
first second

6. Expressions

There are 3 common expressions: return values, arithmetic evaluation and test command. All three of these return either a true or false value.

Return Values
This is literally the return value of programs like grep, find.

Arithmetic Evaluation
Bash uses a parenthesis to denote arithmetic expressions.
Example: (( 1 <= 5))

Test Command
The test command often denoted by [ or [[ can carry out more complex operations:

[[ -e "$file" ]] # True if file exists
[[ -d "$file" ]] # True if file exists and is a directory
[[ -f "$file" ]] # True if file exists and is a regular file
[[ -z "$str" ]]  # True if string is of length zero
[[ -n "$str" ]]  # True is string is not of length zero

# Compare Strings
[[ "$str1" == "$str2" ]]
[[ "$str1" != "$str2" ]]

# Integer Comparisions
[[ "$int1" -eq "$int2" ]] # $int1 == $int2
[[ "$int1" -ne "$int2" ]] # $int1 != $int2
[[ "$int1" -gt "$int2" ]] # $int1 > $int2
[[ "$int1" -lt "$int2" ]] # $int1 < $int2
[[ "$int1" -ge "$int2" ]] # $int1 >= $int2
[[ "$int1" -le "$int2" ]] # $int1 <= $int2

Note that [ is actually a command. Try running $ where [.
test, [ and [[ are almost similar. There are a few subtle differences.

And & Or

[[ ... ]] && [[ ... ]] # And
[[ ... ]] || [[ ... ]] # Or

7. Conditionals

Now since we know, expressions let's use them in conditional statements.

# 1. Return values

# If notes.md file doesn't exist, create one and 
# add the text "created by bash"
if find notes.md
  echo "notes.md file already exists"
  echo "created by bash" | cat >> notes.md
# 2. Arithmetic Evaluations
read -p "Enter your age: " age

if (( "$age" > 18 ))
  echo "Adult!"
elif (( "$age" > 12 ))
  echo "Teen!"
  echo "Kid"
# 3. Test Expressions
# Check if argument was passed
# "$1" corresponds to first argument
if [[ -n "$1" ]]
  echo "Your first argument was $1"
  echo "No Arguments passed"

The if statement has to end with fi.

If you are keen enough, you might wonder why I am using "$var" and not $var to reference the variables. Here's your answer. (Hint: Both work but double quotes are safer).

8. Looping

# print numbers 1 to 10

# c like for loop
for (( i = 1; i <= 10; ++i ))
  echo "$i"

# for in
for i in {1..10}
  echo "$i"

# while
while [[ "$i" -le 10 ]]
  echo "$i"

# until
until [[ "$i" -eq 11 ]]
  echo "unitl $i"

Iterating over arrays
Arrays are declared using parenthesis without commas between elements. More on arrays later on.

arr=(a b c d)

# For in
for i in "${arr[@]}"
  echo "$i"

# c like for
for (( i = 0; i < "${#arr[@]}"; i++))
  echo "${arr[$i]}"

# while
while [[ "$i" -le "${#arr[@]}" ]]
  echo "${arr[$i]}"
  (( i++ ))

${arr[@]} allows you to iterate over an array while ${#arr[@]} returns the length of the array.

Iterating over arguments
@ holds all the arguments passed in to the script

for i in "$@"
  echo "$i"

continue and break work the same way as in other programming languages. continue skips the current iteration. break quits the loop.

9. Arrays

Arrays in bash are defined with parenthesis with no commas separating the elements.

arr=(a b c d)


echo "${arr[1]}"     # Single element
echo "${arr[-1]}"    # Last element
echo "${arr[@]:1}"   # Elements from 1
echo "${arr[@]:1:3}" # Elements from 1 to 3


arr[5]=e                            # direct address and insert/update
arr=(${arr[@]:0:1} new ${arr[@]:1}) # Adding 'new' to array


arr=(a b c d)
unset arr[1]
echo << "${arr[1]}" # Outputs nothing

Notice how once we delete the element at position 1, the following item does not take up its place. Once removing is done, we need to re-index the array.

arr=(a b c d)
unset arr[1]
echo << "${arr[1]}" # c

10. Functions

Functions in bash have a kind of similar syntax as in other programming languages. Arguments to a function are accessed identically to the arguments to the script.

greet() {
  echo "Hello, $1"

greet Bash # Hello, Bash

The function definition does not specify any information of the arguments passed to it. Notice how while calling a function, parenthesis is not used. Instead, arguments are passed in separated by space.

All the arguments of a function can be accessed using @.

greet() {
  echo "Hello, ${@}"

greet every single body # Hello, every single body

And that's it for this crash course. You can now start writing your own shell scripts. There's more to be explored beyond what I have mentioned here.

🌟 I made some Cheat-Sheets
πŸš€ Find me on Instagram | Github | Twitter | Website
πŸ˜„ Have a wonderful day!

Posted on by:


markdown guide

This is a great article! Once you get the basics down, ShellCheck is a great tool to use to make these and other syntax and style "rules" easier to remember (or not have to remember at all!). shellcheck.net

It can be easily added as a linter to Vim, Emacs, VS Code, and others.

ShellCheck screenshot


This sounds like something I definitely need! Thanks!


Thank you for your tutorial!

There’s one thing I see missing: If you use bash, you can do quick string operations in variables:


strip suffix:
echo ${A%.txt} # abc123foo
strip suffix with globbing:
echo ${A%foo*} # abc123

strip prefix:
echo ${A#abc} # 123foo.txt
strip prefix with globbing:
echo ${A#*c} # 123foo.txt


Thanks, that's really useful!


This is an excellent article. I've been working (less than I'd like to admit) on learning Bash. I've observed that floating point arithmetic is amazingly difficult (Or i've missed something obvious) and dealing with dates that are not GNU formatted typically yields my writing embarrassing and nonsensical pipes into sed / awk.

Any pointers for doing floating point math and handling dates for newbs like me?

Appreciate your sharing.


Let me tell you a secret. Before writing this article I didn't know Bash myself. I've realized that teaching is the best way to learn. Maybe you can write a part two to this article explaining dates and floating-pointing arithmetic. I'm pretty sure you'll learn a lot from the process! Hope this helps πŸ˜ƒ


Hello @dsbarnes ,
You can not do floating point arithmetic natively in Bash, but there is a trick I use i.e. using the 'bc' command like:

echo "3.142 + 3.142" | bc -l # add the value of pi to itself
echo "sqrt(49)" | bc -l # find the square root of 49
echo "scale=2; sqrt(91)" | bc -l # To find the square root of 91 to just 2 decimal places.

The capabilities of bc is extremely wide. Check the Manpage for its full documentation.


Thanks for this fun handzone πŸ‘Œ

**small typo in section 6
[[ -n "$str" ]] # True if string is not of length zero


on the begining of the tutorial, i had to use chmod +x ./script.sh instead of chmod -x ./script.sh to make it executable, am I missing something?


Yup, yours is correct. I added the - out of habit! Fixed. Thanks πŸ˜„




This is really useful, you saved me a lot of time. Also, great posts πŸ‘ŒπŸ»πŸ‘ŒπŸ».


I'm glad! And thank-you for reading!


A well written and concise crash course! This is a great diving board into other shell scripting topics like simple automation.