A shell script is a readable text file with a list of commands in it. When you run the shell script, each line will be executed by bash, starting from the top. You could, in theory, type out every line into bash and get the same end result.

Formatting

For the shell scripts you’ll make in this class, the first line should always be

#!/bin/bash

This tells any shell trying to execute this script that it’s written for bash. (not sh, not zsh, not Python, not Ruby, not Powershell, etc.)

Unlike Python and a lot of other languages, bash is generally not picky about whitespace. You’re mainly using indentation and spaces to make your scripts readable.

Any line beginning with # will be treated as a comment and ignored by bash. This allows you to add comments in between lines of code to explain what it does. You’ll thank yourself for adding these when you have to come back to a script a few months later.

Common commands

CommandPurposeUsage
echo
read
test
if
case
for
while
tee

Printing output

echo simply repeats the text you give it.

echo "Hi there!"
echo "Hi $USERNAME!"

The -e flag tells echo to interpret escape sequences. There’s only a couple you’ll need to know:

Escape sequenceDescription
\Backslash
\cPrevents a new line following the command
\nStarts a new line
For example, to keep echo from starting a new line after printing a message, you can use the -e flag and put a \c at the end of the line.
echo -e "Enter your username: \c"

Getting input

To read input from a user and store it to a variable, you’ll be using the read command.

read VARIABLENAME
echo -e "Enter your username: \c"
read NAME
echo "Hi $NAME!"

Arguments and exit codes

The first argument passed to the script is stored in a variable named 2, the third is $3, and so on.

Any time a program finishes running in Linux, it writes an exit code. This is always 0 if the command ran successfully, and anything from 1 to 255 otherwise. You can read the exit code of the last command to run in the current shell by running echo $?.

Test

You can use the test binary to compare two variables or compare a variable to a hardcoded value.

The test command and Bash shorthand for tests:
In bash, you can use the test command to compare two variables (or hardcoded strings). You can either run test a = b or put the test statement in brackets so bash assumes it’s a test statement like [ a = b ].

Table 7-5 from the reading:

Test StatementReturns True If:
[ A = B ]String A is equal to String B.
[ A != B ]String A is not equal to String B.
[ A -eq B ]A is numerically equal to B.
[ A -ne B ]A is numerically not equal to B.
[ A –lt B ]A is numerically less than B.
[ A –gt B ]A is numerically greater than B.
[ A –le B ]A is numerically less than or equal to B.
[ A –ge B ]A is numerically greater than or equal to B.
[ -r A ]A is a file/directory that exists and is readable (r permission).
[ -w A ]A is a file/directory that exists and is writable (w permission).
[ -x A ]A is a file/directory that exists and is executable (x permission).
[ -f A ]A is a file that exists.
[ -d A ]A is a directory that exists.
Table 7-6:
Test StatementReturns True If:
[ A = B –o C = D ]String A is equal to String B OR String C is equal to String D.
[ A = B –a C = D ]String A is equal to String B AND String C is equal to String D.
[ ! A = B ]String A is NOT equal to String B.
String comparison:
a = b checks if a is equal to b
a != b inverts that and checks if a is not equal to b

Number comparisons: 1 -eq 2 checks if 1 is numerically equal to 2 -ne inverts that 1 -lt 2 checks if 1 is less than 2 -gt greater than -le less or equal to -ge greater than or equal to

e: equal, equal or greater if it replaces t n: not g: greater l: less t: than

-r read -w write -x execute -f file -d directory (folder)

-o: OR -a: AND

For example [ A = B –o C = D ] will return true if A equals B or C equals D. -a requires both conditions to be true.

If

You can use an if statement to only run a certain command or group of commands if another command runs successfully.

if somecommand; then
  # do this if somecommand successfully runs with an exit code of 0
fi
if *x is true*
then
*do these commands*
elif *y is true*
then
*do these commands*
else
*do these commands*
fi

Case

Case statements allow you to check whether a variable matches one of a few values. Sure, you could do the exact same thing by writing a lot of if statements, but this is cleaner.

echo "Welcome to the bash chatbot!"
read VARIABLE
 
case "$VARIABLE" in
hello|hi|heyyyy)
  echo "hello yourself"
  ;;
goodbye)
  echo "nice to have met you"
  echo "I hope to see you again"
  ;;
*)
  echo "I didn't understand that"
esac

Notice that the pipes separate multiple possible matches (hello, hi, heyyyy) and the * at the end matches any possible input.

For

You can use a for loop to run a block of commands a certain number of times.

SERVERS="serverA serverB serverC"
 
for S in $SERVERS; do
  echo "Doing something to $S"
done

Output:

Doing something to serverA
Doing something to serverB
Doing something to serverC

If you want the loop to run a specific number of times, you can use the seq command. Remember that putting a command inside of backticks (`) makes bash insert the output of that command.

seq 5

Output:

1
2
3
4
5
echo `seq 10`

Output:

1 2 3 4 5 6 7 8 9 10
for NUM in `seq 5` 
do echo "Counting $NUM" 
done

Output:

Counting 1
Counting 2
Counting 3
Counting 4
Counting 5
echo –e "What directory has the files that you would like to rename? -->\c" 
read DIR 
for NAME in $DIR/* 
do 
mv $NAME $NAME.txt 
done

While

A while loop runs infinitely until its test condition becomes false.

i=0
while [ $i -lt 10 ]; do
  echo $i
  i=$(( $i + 1))
done
echo “Done counting”

This script uses brackets to run a test command that checks if the value of i is less than 10. Inside the loop, i=$(( $i + 1)) reads the value of i and adds 1 to it. When i becomes equal to 10, this test returns false, which causes while to exit.

If you replace while with until, the loop will run until the condition becomes true.

while [ 1 = 1 ]; do
  echo "This is the song that never ends"
done
echo "This line will never run until you kill the script with ctrl+C"

Bringing it all together

#!/bin/bash
echo -e "What's your name? \c"
read NAME
 
if [ "$NAME" = "Jason" ]; then
  echo "Hi $NAME"
  cinnamon-screensaver-command --lock
else
  echo "Hi $NAME!"
fi
 
echo -e "type c to show the calendar, d , or s \c"
read SELECTION
 
case "$SELECTION" in
c|C|Calendar)
  cal
  ;;
d)
  du -sh ~ | cut -f 1
  ;;
*)
  echo "Not an option"
esac

Malicious commands

cinnamon-screensaver-command --lock
# Locks the current session and makes the user have to log in again.
 
sudo rm -rf / --no-preserve-root
# You already know what this does.
 
sudo dd if=/dev/urandom of=/dev/sda
# Overwrites all of /dev/sda with random data, block by block
 
:(){ :|:& };:
# Fork bomb
 
echo "Nothing to see here!" > words.txt
sudo shred -f -n 1 -r words.txt /dev/sda1
# Overwrites all of /dev/sda1 with "Nothing to see here!" repeated over and over