Learning Bash

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!

wall

Told ya it was on my wall :-P


12 Comments

  1. Unix Links says:

    White Text on Black backgroung is hard to read…

    Remember users with poor eyesight.#

    Please change this

  2. rdsears says:

    Hey, I’m sorry you’re having trouble reading it…What color would you suggest?

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

  4. neur0 says:

    Good stuff.
    The only color I’m having trouble with is that Comment Blue which IMO should be lighter on this background.

  5. Fred says:

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

  6. Satya says:

    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!! :-)

  7. Privatik says:

    Blog liked, but a lot of unnecessary comments.

    • rdsears says:

      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!

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*