I know there are like 7 million of these already out there, but here are my most helpful things to keep in mind when it comes to scripting in bash. They are also stapled to my wall.
Please note: this guide assumes you know SOME programming concepts and fundamentals (such as how to use conditionals to accomplish a task)
Always, always, always start your script with one of these
#!/bin/bash
#!/bin/sh
#!/usr/bin/env bash
(the space (or lack thereof one) after the hashbang has never mattered, but there were rumors back in the day that it did)
It was also mentioned that the env bash method is used for portability, as not all unix based operating systems locate bash in the /bin directory.
The first time I saw this I figured it was just for formatting, but in reality linux uses the first 2 bytes of a file to determine what to do with it. The #! basically states “This file is a script, run it with the binary specified” followed by (of course) said binary.
The formatting on wordpress is a bit weird for direct copying scripts, so I will include all of them in a link at the bottom (eventually).
Part 1.) The concept of quotes
First of all we need to have an understanding about quotes, and what the differences are between the different types are. You have to think like a computer does, and realize the fact that certain characters do different things (such as $ to denominate the use of a variable)
Single character quote :
In order for us to display a string such as “That will be $7.50″ we need to use a \ to ‘escape’ the evaluation of the $ symbol.
The right way:
echo That will be \$7.50
Example:
[rdsears@walter.melon ~]# echo That will be \$7.50
That will be $7.50
The wrong way:
echo That will be $7.50
Example:
[rdsears@walter.melon ~]# echo That will be $7.50
That will be .50
This is because of the fact that the shell interprets the $7 as a variable, which is the “7th argument” variable covered later in this paper.
Single quotes:
Single quotes (sometimes called hard quotes) can be used to separate white space, much like double quotes, but the main difference is the fact that single quotes don’t allow for variable substitution, which in turn means there is no need for an escape character. In this example i show how to do variable substitution as well as escaped quotations in a single line.
The right way
echo ‘ The Number’ $RANDOM ‘is a “random number” ‘
Example:
[rdsears@walter.melon ~]# echo ‘ The number’ $RANDOM ‘is a “random number” ‘
The number 11467 is a “random number”
The wrong way
echo ‘ The Number $RANDOM is a “random number” ‘
Example
[rdsears@walter.melon ~]# echo ‘ The number $RANDOM is a “random number” ‘
The number $RANDOM is a “random number”
Double quotes
Double quotes (sometimes called soft quotes) are different in the sense that they DO accept both escape characters (\) as well as variable substitution. There really isn’t a wrong way to use double quotes, but they’re very handy.
Part 2.) Working with arguments & variables:
Its been brought to my attention that when declaring variables, its a good practice to use a lowercase declaration, such as using $variable1 instead of $VARIABLE1. I got in the practice of using all caps personally because I think its a bit easier to read, but
The “$#” variable
This variable specifies the number of arguments that are pushed to your script, normally used in a loop to process your args (although as with everything in scripting there’s a hundred ways to do anything)
Example :
#!/bin/bash
# This script determines if no arguments have been specified using the $# variable
if [ "$#" == "0" ]then
echo There are NO arguments specified
else
echo There are some arguments
fi
The “$@” variable
This variable stores the arguments (sometimes called positional parameters) passed to your script in a string.
Example :
#!/bin/bash
echo ARG1 = ${args[0]}
echo ARG2 = ${args[1]}
echo ARG3 = ${args[2]}
echo AllArgs = ${args[*]}# This script demonstrates how to parse arguments using the $@ variable
echo \$@ = “$@“
# use \ as an escape char so we can display $@
args=(“$@“)
# set a variable ‘args’ to the contents of $@
Output:
[rdsears@walter.melon ~]# sh /home/rdsears/argcat.sh Variable1 Variable2 Variable3
$@ = Variable1 Variable2 Variable3
ARG1 = Variable1
ARG2 = Variable2
ARG3 = Variable3
AllArgs = Variable1 Variable2 Variable3
The $0 – ${N} set of variables
Stores the arguments passed to your script, while $0 is the absolute path + script name. If more than 9 arguments are specified, you can use the ${} convention to declare them. This defeats the method used above to extract arguments, although this goes back to the whole ‘hundred ways to do anything’ thing.
Example:
#!/bin/bash
# This script demonstrates the use of individual argument variables – a very handy feature of the bash shell
echo \$@ = $@ # use \ as an escape char so we can display $@
echo ARG1 = $1
echo ARG2 = $2
echo ARG3 = $3
echo AllArgs = $@ #There is no numeric variable that holds this – you gotta use $@
The output is going to be the exact same as above, the only difference is we didn’t have to copy our info to a variable first.
The $! variable
This variable stores the last job’s PID, not the most useful thing ever, but I DO occasionally use it.
Example:
[rdsears@walter.melon ~]# firefox &
[1] 4427
[rdsears@walter.melon ~]# echo $!
4427
Part 3.) Other useful bash variables:
The $UID variable
This variable gives you the current user’s UID (not username)
The $USER variable
This variable gives you the current user’s user name
The $PATH variable
Stores the path in which your executables are located, so you can use things like tab completion and the ability to just run programs like ‘cat’ or ‘date’ without having to be in the directory they’re actually located in.
The $PWD variable
Holds the current working directory, minus the script name (essentially the $0 variable without the name on it)
The $RANDOM variable
When invoked, this variable will generate a number between 0 and 32767
The $SECONDS variable
This variable holds the number of seconds since the script was started, very handy for timing the longevity of script
The $(</path/to/a/file) #Thanks Croooow
This isn’t exactly a variable, more a method of reading files, but it is optimized beyond the cat command to work with bash scripts
Part 4.) Conditional tests!
In order for our scripts to actually DO something, we need to have it make decisions based on criteria that we specify. Part 4 will cover the types of loops and selection statements we can make, but for now we focus on the actual conditions you can use with the “[" command. The "[" command is simply a bash alias to the "test" function, used as a fundamental part of loops.
All the flags we will be dealing with are in the form
if [ $VAR1 -ourflagshere $VAR2 ]; then
#Please note though, you don’t need the end of line character (;) if you put the then under the if statement
echo “Do something“
else
echo “Do something else instead“
fi #used to close the selection statement
Again I will cover the actual looping conventions in part 4, this is just so you have a grasp of where to put the flags themselves.
File tests:
The -e flag
Evaluates true if the file (of any type) exists
The -f flag
True if it is an ordinary file (not character, block, etc)
The -d flag
True if the directory exists
The -r flag
True if readable (same for -x and -w, execute and write)
The -s flag
Tests is the file has a greater than zero size
String tests:
The -n flag
Tests to see the string is non-zero in length (greater than zero)
The -z flag
Tests if string length = 0
Arithmetic tests:
The -eq flag
True if value1 is equal to value 2 ( == )
The -ne flag
True if value1 is not equal to value 2 ( != )
The -le flag
True if value1 is less then or equal to to value 2 ( <= )
The -lt flag
True if value1 is less then value2 ( < )
The -ge flag
True if value1 is greater than or equal to value 2 ( >= )
The -gt flag
True if value1 is greater then value 2 ( > )
Misc test flags
The -ef flag
Tests if the files are the same ( if file1 is the exact same file as file2)
The -G flag
Tests to see if your group is the same
The -nt flag
Tests to see if the first file is older then the second file
The -O flag
Tests if you own the file
Part 5.) Loops
The if test: #Note: this is a test, not a loop – Thanks str1442
#!/bin/bash
# This script uses the -r flag to see if a file is readable and prints an error if it isn’t
if [ -r $MYFILE ]
then
cat $MYFILE
else
echo “Can not read file“
fi
The while loop:
#!/bin/bash
#This script uses while to count to 10
count = 0
max = 10
while [ $count -lt $max ]
do
echo $count
count = $((count + 1))
#This is how you do command line arithmetic in bash, I’ll cover that in a different section though
done
The for loop
(Standard context)
#!/bin/bash
#This script uses a for loop in the same convention as c/c++
for (( c=1; c<=5; c++ ))
# accomplished by taking advantage of arithmetic in bash, as with the other count
do
echo Counting….$i of 5
done
(Scripting context)
#!/bin/bash
#This script uses a for statement to scan the /root/ folder for any .sh files, then makes a copy of them and backs them up in a NAME.DATE covention
scripts=/root/*.sh
for s in “$scripts”
#The s can be any open variable
do
cp “$s” /mnt/sda2/backup/`basename “$s“`.`date`
done
Case statements:
#!/bin/bash
# This script classifies a file based on the file extension
file=SomeBinary.bin
case “$file” in
*.exe)
echo “This file is an executable“;;
*.mag)
echo “This is a magic file“;;
*.bin)
echo “This is a binary“;;
*)
echo “Unknown filetype“;;
esac
Part 6.) Misc. Stuff to know
Calling another script from within a script
. /path/to/scipt.sh
You can use functions much like in any other programming language
#/bin/bash
# This script just defines the function foo
function foo {
echo “bar”
exit }
Now everytime we call foo, it displays “bar” to stdout
Flow control
The && operator
command1 && command2
If command1 evaluates as true (returns 0), then move onto command2
The & operator
updatedb &
This starts the updatedb program in the background (instead of a ctrl+z followed by a bg)
The || operator
If command1 evaluates as false (returns something other than 0) move on to command2
command1 || command2
Pipes : Used to redirect the output of one command to the input of another
cat $file | grep “open”
This prints $file to stdout, which is then redirected through the grep ‘filter’ to find results that contain the string “open”
The “True Loop”:
while :; do
SomethingForever
done
This will repeat SomethingForever until it is unable to do anymore (or until it gets a ctrl+c). This is because “:” is a bash built-in for “true”
Using tee:
[rdsears@walter.melon ~]# somescript.sh | tee /path/to/file
Tee allows us to copy the stdout stream to a file, while still printing to the console saving to /path/to/file
Variable stuff:
${#BLAH}
This counts the number of characters in the variable $BLAH
i =$(( i+1 ))
Used as an incremental (mostly used in while loops)
Part 7.) Bash Arithmetic! (finally)
The standard convention for bash arithmetic is:
$(( $x + $y ))
This adds x to y and stores it into whatever variable you want to store it to.
The operators are as follows:
*
Multiplication -> $(( 5 * 2 )) == 10
/
Division -> $(( 5 / 2 )) == 2
%
Remainder -> $(( 5 % 2 )) == 1
+
Addition -> $(( 5 + 2 )) == 7
-
Subtraction -> $(( 5 – 2 )) == 3
&
Bitwise AND -> $(( 5 & 2 )) == 0
^
Bitwise XOR -> $(( 5 ^ 2 )) == 7
|
Bitwise OR -> $(( 5 | 2 )) == 7
A special thank you to all the reddit contributors that have helped me expand this article and point out its faults!

Told ya it was on my wall


12 Comments
White Text on Black backgroung is hard to read…
Remember users with poor eyesight.#
Please change this
Hey, I’m sorry you’re having trouble reading it…What color would you suggest?
I too wanted to comment on text colors. Right at the top you have the darker blue text on a gray background. There is not enough contrast between those two to make it readable. There are other spots in your post where you use that same blue on a darker gray which are at least better. I find that green on black is easier to read than white on black.
My suggestion would be to do something more like book publishers. Use different fonts to differentiate the commands being entered from the messages that are returned.
Your posting is good, just a bit awkward to read through. Thanks for sharing.
Ken S.
Good stuff.
The only color I’m having trouble with is that Comment Blue which IMO should be lighter on this background.
> (the space (or lack thereof one) after the hashbang has never
> mattered, but there were rumors back in the day that it did)
Yes it does, I just had to deal with such a broken shebang. It doesn’t matter when you call your script from the shell (it doesn’t even look at the shebang!) but you can’t exec(2) such a script. Demonstration:
frlanw0f03384:/tmp$ rm example launcher
frlanw0f03384:/tmp$ cat – > example
# !/bin/bash
echo Hello, this is example
frlanw0f03384:/tmp$ cat – > launcher
#!/usr/bin/python
from subprocess import call
call(["/tmp/example"])
frlanw0f03384:/tmp$ chmod +x launcher example
frlanw0f03384:/tmp$ ./launcher
Traceback (most recent call last):
File “./launcher”, line 3, in ?
call(["/tmp/example"])
File “/usr/lib/python2.4/subprocess.py”, line 412, in call
return Popen(*args, **kwargs).wait()
File “/usr/lib/python2.4/subprocess.py”, line 542, in __init__
errread, errwrite)
File “/usr/lib/python2.4/subprocess.py”, line 975, in _execute_child
raise child_exception
OSError: [Errno 8] Exec format error
frlanw0f03384:/tmp$ ./example
Hello, this is example
frlanw0f03384:/tmp$ cat – > example
#! /bin/bash
echo Hello, this is example –reloaded
frlanw0f03384:/tmp$ chmod +x example
frlanw0f03384:/tmp$ ./launcher
Traceback (most recent call last):
File “./launcher”, line 3, in ?
call(["/tmp/example"])
File “/usr/lib/python2.4/subprocess.py”, line 412, in call
return Popen(*args, **kwargs).wait()
File “/usr/lib/python2.4/subprocess.py”, line 542, in __init__
errread, errwrite)
File “/usr/lib/python2.4/subprocess.py”, line 975, in _execute_child
raise child_exception
OSError: [Errno 8] Exec format error
frlanw0f03384:/tmp$ cat ->example
#!/bin/bash
echo Hello, this is example –corrected.
frlanw0f03384:/tmp$ chmod +x example
frlanw0f03384:/tmp$ ./launcher
Hello, this is example –corrected.
So, now I have to do something like:
call(["/bin/bash", "-c", "/path/to/script"])
because of people who can’t write shebangs.
Hey,
Thanks for that. I was defiantly unaware of this, but that’s why I started this in the first place! To share and learn
Thanks for this nice and short tutorial!
Those who have hard time reading this web page may select Print Preview from Firefox. That makes it pretty clean and readable!!
Blog liked, but a lot of unnecessary comments.
Hi there,
I’m glad you like my blog, but what do you mean by unnecessary comments? My blog section will of course be populated with my thoughts, but the technical stuff I’ve segregated into individual pages. The comments in there are either meant to be humorous or helpful. I’ll keep that in mind though!