loading...
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:

#!/bin/bash

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.

#!/bin/node

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.

#!/bin/bash

name="Bash"

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.

#!/bin/bash

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:

#!/bin/bash

read -p "Enter an action: " verb

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

4. Comments

# 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.

#!/bin/bash

echo $0
echo $1
echo $2
echo "${@}" # Access all the arguments [More on this later]
$ ./script.sh first second
./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
then
  echo "notes.md file already exists"
else
  echo "created by bash" | cat >> notes.md
fi
# 2. Arithmetic Evaluations
read -p "Enter your age: " age

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

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 ))
do
  echo "$i"
done

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

# while
i=1
while [[ "$i" -le 10 ]]
do
  echo "$i"
  ((i++))
done

# until
i=1
until [[ "$i" -eq 11 ]]
do
  echo "unitl $i"
  ((i++))
done

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[@]}"
do
  echo "$i"
done

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

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

${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 "$@"
do
  echo "$i"
done

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)

Read

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

Insert

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

Delete

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]
arr=("${arr[@]}")
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!

Discussion

pic
Editor guide
Collapse
crcastle profile image
Chris Castle

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

Collapse
godcrampy profile image
Sahil Bondre Author

This sounds like something I definitely need! Thanks!

Collapse
arnebab profile image
Arne Babenhauserheide

Thank you for your tutorial!

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

A=abc123foo.txt

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

Collapse
godcrampy profile image
Sahil Bondre Author

Thanks, that's really useful!

Collapse
dsbarnes profile image
dsbarnes

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.

Collapse
godcrampy profile image
Sahil Bondre Author

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 πŸ˜ƒ

Collapse
pystar profile image
Pystar

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.

Collapse
hoss3inf profile image
Hossein Farrokhi

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?

Collapse
godcrampy profile image
Sahil Bondre Author

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

Collapse
shlomilachmish profile image
shlomi-lachmish

Thanks for this fun handzone πŸ‘Œ

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

Collapse
godcrampy profile image
Collapse
irregula_exprsn profile image
Muhammad Iliyas

πŸ‘πŸ½πŸ‘πŸ½πŸ‘πŸ½

Collapse
thechazhall profile image
C.F. Hall

Thanks great article.

Collapse
godcrampy profile image
Sahil Bondre Author

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

Collapse
totallymustafa profile image
Syed Ahmad Mustafa

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

Collapse
godcrampy profile image
Collapse
tslarge profile image
Travis Large

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

Collapse
godcrampy profile image
Sahil Bondre Author

Thanks πŸ˜„

Collapse
bobbyiliev profile image
Bobby Iliev

Great article! πŸ™Œ

You should check out this Open-Source Introduction to Bash Scripting Ebook on GitHub as well!