0% found this document useful (0 votes)
4 views28 pages

Bash Shell Script Mat

The document provides an introduction to Bash shell programming under Linux, highlighting its advantages over Windows alternatives. It covers basic shell commands, scripting, and the importance of understanding the command line interface, including file management and command execution. The document also emphasizes the artistic nature of shell programming and encourages users to explore further resources for advanced learning.

Uploaded by

aish sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views28 pages

Bash Shell Script Mat

The document provides an introduction to Bash shell programming under Linux, highlighting its advantages over Windows alternatives. It covers basic shell commands, scripting, and the importance of understanding the command line interface, including file management and command execution. The document also emphasizes the artistic nature of shell programming and encourages users to explore further resources for advanced learning.

Uploaded by

aish sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Bash what?

Okay, I grant that this page might represent a leap from the familiar to the alien
without much warning. Here are some explananatory notes:

 Under Linux, there are some powerful tools that for all practical purposes
are unavailable under Windows (I can imagine all the old Linux hands saying
"Duh!").
 One of these tools is something called "shell programming". This means
writing code that a command shell executes.
 There is something like this under Windows, but as usual, the Windows
version is a weak imitation.
 The most common Linux shell is named "Bash". The name comes from
"Bourne Again SHell," which, in turn ... (imagine a lengthy recursion
terminating in a caveman's grunt).
 There are many other shells available. Unless there is a compelling reason
not to, I recommend that people stick to the Bash shell, because this
increases the chance that your scripts will be portable between machines,
distributions, even operating systems.
 I'll be showing some very basic examples of Bash shell programming on this
page, and I want to say at the outset that shell programming is an art, not a
science. That means there is always some other way to do the same thing.
 Because shell programming is an art, please don't write to say, "Wow, that
was a really inefficient way to do such-and-such." Please do write (message
page) to report actual errors.
 If this page seems too sketchy and elementary for your taste, you can
choose from among the more advanced resources in this list.

Introduction

 Early computers had a teletype machine with a keyboard for I/O. Later,
glass terminals became the norm, but the behavior was much the same — a
keyboard, a screen, a text display. A program was responsible for mediating
the transaction between the operator and the machine, and as the years
passed this program (the command interpreter or shell) became more
sophisticated.
 At this stage the command shell has become rather too sophisticated,
typically having a dozen ways to do any particular thing. In this page I will
try to limit myself to describing a handful of useful operations, based not on
listing everything that can be done, but on solving specific problems. There
are some links at the bottom of this page for those wanting more depth.

Preliminaries

 There are two primary ways to use the shell: interactively and by writing
shell scripts.
o In the interactive mode, the user types a single command (or a short
string of commands) and the result is printed out.
o In shell scripting, the user types anything from a few lines to an
entire program into a text editor, then executes the resulting text file
as a shell script.
o It is often the case that an interactive session becomes a shell
scripting session, once things get too complicated for simple
interactive line entries, or because a specific sequence of commands
appears to be generally useful and worth preserving.
 In a modern Linux environment the user can have more than one shell open
at a time, either by moving between a sequence of independent "virtual
terminals" in a text-only environment, or by opening any number of shell
windows in the X Windows environment.
 The advantage of having more than one shell available is that one shell can
be used for testing one command at a time, while another might provide a
text editor for assembling single commands into a shell program.
 I don't want to get too distribution-specific, but if you are not hosting X
Windows and want more than one simultaneous shell session, with many
current distributions you can switch between "virtual terminals" by pressing
Ctrl+Alt+F(n), n typically between 1 and 6.
 In an environment that supports X Windows, simply open any desired
number of command shell windows and move between them.

Simple Stuff

 First, a convention. I'll list things for you to type in this format:

$ date

I will list the computer's reply like this:

Tue Dec 23 [Link] PST 2003

Notice the "$" symbol in the user entry above. This is a generic shell
prompt, and yours will almost certainly look different (but it will include a
similar symbol). I'll be using one of two prompts (this is a common
convention, worth remembering): I'll use "$" to refer to a normal user
session, and "#" to refer to a root session.

 NOTE: Avoid using root sessions and permissions unless it is


required. Misused root authority can cause very serious harm to your
system. Since this is a tutorial in which you will want to experiment with
different commands, limit the chance for harm by doing so as an ordinary
user.
 To put this another way, enter this example:

# whoami
root

If your session produced the result shown above, please — log out and
become an ordinary user.
 In shell programming, spaces matter. If you see spaces between words and
characters in these examples, be sure to include the spaces.
 In shell programming, case matters also. If you don't get the results shown
on this page, look at the case of your entries.

Where are you?

 As you may be aware, a Linux filesystem is in the form of a large tree with
many branches called "subdirectories". When you issue a shell command, it
is often necessary to know where you are in the "tree". Type this example:

$ pwd
/path/path/path

When you try this example ("pwd" means "print working directory"), your
current working directory will be printed.

 You can decide where you are in the tree. Type this example:

$ cd ~
$ pwd
/home/username

The symbol "~" is a special shortcut character that can be used to refer to
your home directory. You could have typed this —

$ cd /home/username

— and accomplished the same result, but if you think about it, the "~"
character is more portable. Later, when you are writing shell scripts, you
might want a command that moves to any user's home directory.

Listing Files

 Directories contain files, and you can list them in a simple, compact format:

$ ls
filename filename filename ...

Or you can list them in more detail:

$ ls -la
(detailed list, one file per line)

And, very important, to find out what a command's options are, use the
"man" (manual) command:

$ man ls
(manual page for "ls")
NOTE: The "man" command allows you to learn a command's options. You
still have to remember the command's name.

To find files by name:

$ find . -name '*.jpg'


(list of files with .jpg suffix in current and all child
directories)

To create a text diagram of the directory tree:

$ tree -d .
(diagram of the directory tree from the current directory)

The "tree" command is less useful now that directory trees have become so
complicated, and now that most distributions support X Windows and
sophisticated filesystem browsers.

Examining Files

 There are a number of things you can do to find out more about the files in
the list. Here are just a few:

The "file" command tries to identify files by examining their contents:

$ file tux_small.png
tux_small.png: PNG image data, 128 x 151, 8-bit/color RGB, non-
interlaced

The next example uses the obscurely named "cat" command. It prints the
contents of a file. Unfortunately if the file's contents are not readable, they
get printed anyway.

$ cat [Link]
(prints the entire contents of a file named "[Link]")

If a file is too long to be viewed on one page, you can say:

$ more [Link]
(prints file one screenful at a time)

You can also use "grep" to print only those parts of a file you are interested
in:

$ grep 10001 [Link]


(prints only those lines that have the character string "10001" in
them)

The "grep" command is very useful, unfortunately it has a difficult-to-


remember name. Be sure to:
$ man grep

There are many, many more shell commands to learn and to use. You may want to
browse the list of Useful Links for more detail.
Pipelines and Redirection

 You can use a pipeline (symbolized by "|") to make the output of one
command serve as the input to another [Link] idea can be used to
create a combination of commands to accomplish something no single
command can do.

Enter this command:

$ echo "cherry apple peach"


cherry apple peach

Okay, let's say we want to sort these words alphabetically. There is a


command "sort", but it sorts entire lines, not words, so we need to break
this single line into individual lines, one line per word.

Step one: pipe the output of "echo" into a translation (tr) command that will
replace spaces with linefeeds (represented by "\n"):

$ echo "cherry apple peach" | tr " " "\n"


cherry
apple
peach

Success: each word appears on a separate line. Now we are ready to sort.

Step two: add the sort command:

$ echo "cherry apple peach" | tr " " "\n" | sort


apple
cherry
peach

Let's try reversing the order of the sort:

$ echo "cherry apple peach" | tr " " "\n" | sort -r


peach
cherry
apple

 Remember: A pipeline ("|") takes the output of one command and makes it
the input to another command.
 Normally the output from commands is printed on the screen. But using the
symbol ">", you can redirect the output to a file:
$ date > [Link]
$ cat [Link]
Tue Dec 23 [Link] PST 2003

The above example used ">" to replace the content of any existing file
having the name "[Link]". To append new data to an existing file, use
">>" instead:

$ date >> [Link]


$ cat [Link]
Tue Dec 23 [Link] PST 2003
Tue Dec 23 [Link] PST 2003

 Remember: Use ">" to overwrite any existing file, use ">>" to append to
any existing file. In both cases, if no file exists, one is created.
 Many commands have inputs as well as outputs. The input defaults to the
keyboard, the output defaults to the screen.
 To redirect the output to a file, use ">" or ">>" as shown above.
 To make the output of a command serve as the input of another command,
use "|".
 To make the contents of a file serve as the input to a command, use "<":

$ wc < [Link]
2 12 58

As is so often the case in shell programming, there is at least one other way
to produce the above result:

$ cat [Link] | wc
2 12 58

Shell Script Basics

 A shell script is a plain-text file that contains shell commands. It can be


executed by typing its name into a shell, or by placing its name in another
shell script.
 To be executable, a shell script file must meet some conditions:
o The file must have a special first line that names an appropriate
command processor. For this tutorial, the following will work in most
cases:

#!/bin/bash

If this example doesn't work, you will need to find out where your
Bash shell executable is located and substitute that location in the
above example. Here is one way to find out:

$ whereis bash
o The file must be made executable by changing its permission bits. An
example:

$ chmod +x (shell script filename)

 A shell script file may optionally have an identifying suffix, like ".sh". This
only helps the user remember which files are which. The command
processor responsible for executing the file uses the executable bit, plus the
file's first line, to decide how to handle a shell script file.
 One normally executes a shell script this way:

$ ./[Link]

This special entry is a way to tell the command processor that the desired
script is located in the current directory. Always remember: if you cannot
get your shell script to run, remember this trick to provide its location as
well as its name.

First Shell Script

 This will get you past the details of writing and launching a simple script.
1. Choose a text editor you want to use. It can be a command-line
editor like emacs, pico or vi, or an X Windows editor if you have this
option.
2. Run your choice of editor and type the following lines:
3.
4. #!/bin/bash
5. echo "Hello, world."
6.

NOTE: Be sure to place a linefeed at the end of your


script. Forgetting a terminating linefeed is a common beginner's
error.

7. Save the file in the current working directory as "[Link]".


8. Move from the text editor to a command shell.
9. From the command shell, type this:

$ chmod +x [Link]

10. To execute the script, type this:

$ ./[Link]
Hello, world.

 These steps will become second nature soon enough.

Tests and Branching


 Bash shell scripts can perform, and act on, various kinds of tests. This will
be just the most basic introduction — see the reference material at Useful
Links for more on this rather baroque topic.
 To follow these and other examples that involve multiline shell scripts,
please set up to edit and run a test script file (let's call it "[Link]") that
you can use to enter and test various options. And remember that the
examples won't include the all-important first line of the script (see script
examples above) — it will be assumed to exist in each case.

Also, to save time, you may want to copy some of the shell code examples
from this page directly into your editor.

 Here is an example of a test and branch:



 if [ -e . ]
 then
 echo "Yes."
 else
 echo "No."
 fi

Run the test script:

$ ./[Link]
Yes.

We created a test (the part of the script between "[" and "]") which tested
whether a particular element existed ("-e"). Because the symbol "." in this
context means the current directory, the test succeeded. Try replacing the
"." with something that is not present in the current directory, example
"xyz". See how the outcome changes.

It is important to realize that "[" is an alias for the command "test". The
script could have been written as:

if test -e .
then
echo "Yes."
else
echo "No."
fi

NOTE: Be sure to read the "test" man page to find out all the different tests
that are available:

$ man test
Before we move on, there is a perversity about tests in Bash shells that I
want to discuss. It turns out, because of a historical accident that now might
as well be cast in concrete, when a test is conducted or a command returns
a result value, the numerical value for "true" is 0, and "false" is 1. Those of
you who have some programming experience will likely find this reversal of
intuition as annoying as I do.

Here is a way to get the result of the most recent logical test (and to show
the weird reversal described above):

$ test -e .
$ echo $?
0

$ test -e xyz
$ echo $?
1

Please remember this reversal, because it confounds the process of thinking


through, and constructing, logical tests. For example, you may want to write
a shortcut form for a test that only acts on one kind of result:

$ test -e . && echo "Yes."


Yes.

This sort of shorthand relies on some knowledge of logical processing — if


the left-hand part of an AND test yields "true", then the right-hand part
must also be evaluated, and so it is. But the numerical "true" value for the
left-hand test is 0, which would argue for the opposite logic.

Just to show how perverse this all is, here is an example of Bash logical
testing that comes out the opposite way:

$ echo $(( 0 && 0 ))


0

$ echo $(( 1 && 0 ))


0

$ echo $(( 0 && 1 ))


0

$ echo $(( 1 && 1 ))


1

Yes, just as you would expect. So do be on guard against this shell "gotcha",
which only affects the outcome of tests and command result values. It
probably will not surprise you to learn that no one mentions this strange
anomaly in the official Bash documentation.

A couple of rules about logical operators used as branches:


o If you write "test && command", the command will only be executed
if the test succeeds.
o If you write "test || command", the command will only be executed if
the test fails.

Run these tests:

$ true && echo "Yes."


Yes.

$ false || echo "Yes."


Yes.

Notice that the outcomes are entirely in keeping with one's intuition about
such logical comparisons, and all is well as long as you don't think about the
fact that true equals 0. :)

Here's another scheme commonly seen in shell script programming and


interactive sessions:

$ command1 && command2 && command3 && command4

This line of code will not run the next command in the sequence unless the
prior command has returned "true", meaning no errors. It is a way to avoid
running a command if a required prior outcome is not present.

Loops and Repetition

 Here are some examples of loop operators:



 for fn in *; do
 echo "$fn"
 done

In this example, the "*" is expanded by the shell to a list of all the files in
the current directory, then each filename is applied to the loop control area.
In such a construct, any whitespace-delimited list will do:

for fn in tom dick harry; do


echo "$fn"
done

$ ./[Link]
tom
dick
harry
This method will work for any list that uses spaces as delimiters. But what
happens if you must parse a list that must be delimited by linefeeds instead
of spaces, such as the case of a list of filenames or paths that contain
spaces as part of their names?

You can solve such a problem this way (there are other solutions):

ls -1 | while read fn; do


echo "$fn"
done

This example uses an option to "ls" (note: the option is "-" followed by the
numerical digit "1", not a lowercase "L") that formats file listings with one
name per line, then this list is pipelined to a routine that reads lines until
there are no more to read. This meets the requirement that linefeeds
become the delimiters between list elements, not spaces.

There is plenty more to this topic. Please refer to the list of Useful Links for
more.

Using Numbers in Scripts

 Contrary to a sometimes-expressed view, numbers can easily be


accommodated in scripts. Example:

 n=1
 while [ $n -le 6 ]; do
 echo $n
 let n++
 done

$ ./[Link]
1
2
3
4
5
6

Notice the "let" command, which treats its argument in a way meant to
accommodate numbers.

Here is a somewhat more complex example:

y=1
while [ $y -le 12 ]; do
x=1
while [ $x -le 12 ]; do
printf "% 4d" $(( $x * $y ))
let x++
done
echo ""
let y++
done

$ ./[Link]

1 2 3 4 5 6 7 8 9 10 11 12
2 4 6 8 10 12 14 16 18 20 22 24
3 6 9 12 15 18 21 24 27 30 33 36
4 8 12 16 20 24 28 32 36 40 44 48
5 10 15 20 25 30 35 40 45 50 55 60
6 12 18 24 30 36 42 48 54 60 66 72
7 14 21 28 35 42 49 56 63 70 77 84
8 16 24 32 40 48 56 64 72 80 88 96
9 18 27 36 45 54 63 72 81 90 99 108
10 20 30 40 50 60 70 80 90 100 110 120
11 22 33 44 55 66 77 88 99 110 121 132
12 24 36 48 60 72 84 96 108 120 132 144

Coping with user input

 Here is an example that relies on user input to decide what to do. It exploits
a shell feature as an easy way to create a menu of choices:

 PS3="Choose (1-5):"
 echo "Choose from the list below."
 select name in red green blue yellow magenta
 do
 break
 done
 echo "You chose $name."

When run, it looks like this:

$ ./[Link]

Choose from the list below.


1) red
2) green
3) blue
4) yellow
5) magenta
Choose (1-5):4
You chose yellow.

As written, this menu code won't catch some kinds of errors (like a number
that is out of range). In any application where the user choice must fall into
defined bounds, be sure to perform a test on the result before using it.
Example:

if [ "$name" = "" ]; then


echo "Error in entry."
exit 1
fi

An advanced example with numbers and user input

 Here is an example guessing game that ties together some of the elements
we've covered so far:

 secretNumber=$(( ((`date +%N` / 1000) % 100) +1 ))
 guess=-1
 while [ "$guess" != "$secretNumber" ]; do
 echo -n "I am thinking of a number between 1 and 100. Enter
your guess:"
 read guess
 if [ "$guess" = "" ]; then
 echo "Please enter a number."
 elif [ "$guess" = "$secretNumber" ]; then
 echo -e "\aYes! $guess is the correct answer!"
 elif [ "$secretNumber" -gt "$guess" ]; then
 echo "The secret number is larger than your guess. Try
again."
 else
 echo "The secret number is smaller than your guess. Try
again."
 fi
 done

Please study this example carefully, and refer to the reference materials
in Useful Links to understand some of the methods.
Creating and using arrays

 Shell arrays are relatively easy to construct. Example:



 array=(red green blue yellow magenta)
 len=${#array[*]}
 echo "The array has $len members. They are:"
 i=0
 while [ $i -lt $len ]; do
 echo "$i: ${array[$i]}"
 let i++
 done

Run this example:

$ ./[Link]

The array has 5 members. They are:


0: red
1: green
2: blue
3: yellow
4: magenta

Now, before you decide this is a silly, rather useless example, replace one
line of the script and run it again:

array=(`ls`)

See what difference this makes (and think of all the kinds of lists you might
create for this line).

Strings and substrings

 It's useful to be able to take strings apart and put them together in different
ways. Here is how to select a substring from a string:

 string="this is a substring test"
 substring=${string:10:9}

In this example, the variable "substring" contains the word "substring".
Remember this rule:

substring=${string_variable_name:starting_position:length}

The string starting position is zero-based.

Searching and Replacing Substrings within Strings

 In this method you can replace one or more instances of a string with
another string. Here is the basic syntax:

 alpha="This is a test string in which the word \"test\" is
replaced."
 beta="${alpha/test/replace}"

The string "beta" now contains an edited version of the original string in
which the first case of the word "test" has been replaced by "replace". To
replace all cases, not just the first, use this syntax:

beta="${alpha//test/replace}"

Note the double "//" symbol.

Here is an example in which we replace one string with another in a multi-


line block of text:

list="cricket frog cat dog"


poem="I wanna be a x\n\
A x is what I'd love to be\n\
If I became a x\n\
How happy I would be.\n"
for critter in $list; do
echo -e ${poem//x/$critter}
done

Run this example:

$ ./[Link]
I wanna be a cricket
A cricket is what I'd love to be
If I became a cricket
How happy I would be.
I wanna be a frog
A frog is what I'd love to be
If I became a frog
How happy I would be.
I wanna be a cat
A cat is what I'd love to be
If I became a cat
How happy I would be.
I wanna be a dog
A dog is what I'd love to be
If I became a dog
How happy I would be.

Silly example, huh? It should be obvious that this search & replace capability
could have many more useful purposes.

More obscure but useful string operations

 Here is a way to isolate something useful from a large, even multi-line,


string. As above, this method relies on enclosing a variable name in curly
braces, then aplying a special operator to achieve a particular result.

Here is a list of four such operators:

o Operator "#" means "delete from the left, to the first case of what
follows."
o
o $ x="This is my test string."
o $ echo ${x#* }
o
o is my test string.
o
o

o Operator "##" means "delete from the left, to the last case of what
follows."
o
o $ x="This is my test string."
o $ echo ${x##* }
o
o string.
o
o
o Operator "%" means "delete from the right, to the first case of what
follows."
o
o $ x="This is my test string."
o $ echo ${x% *}
o
o This is my test
o
o

o Operator "%%" means "delete from the right, to the last case of what
follows."
o
o $ x="This is my test string."
o $ echo ${x%% *}
o
o This
o
o

I find these operators particularly useful in parsing multi-line strings. Let's


say I want to isolate a particular IP address from the output of the "ifconfig"
command. Here's how I would proceed:

$ x=`/sbin/ifconfig`
$ echo $x

eth0 Link encap:Ethernet HWaddr [Link]


inet addr:[Link] Bcast:[Link]
Mask:[Link]
inet6 addr: fe80::20d:56ff:fe0c:8d10/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:253339 errors:0 dropped:0 overruns:0
frame:0
TX packets:423729 errors:0 dropped:0 overruns:0
carrier:0
collisions:0 txqueuelen:1000
RX bytes:36150085 (34.4 MiB) TX bytes:496768499
(473.7 MiB)
Base address:0xecc0 Memory:fe4e0000-fe500000
lo Link encap:Local Loopback
inet addr:[Link] Mask:[Link]
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:109394 errors:0 dropped:0 overruns:0
frame:0
TX packets:109394 errors:0 dropped:0 overruns:0
carrier:0
collisions:0 txqueuelen:0
RX bytes:12372380 (11.7 MiB) TX bytes:12372380
(11.7 MiB)
There's lots of information, more than we need. Let's say for the sake of
argument that I want the IP of "lo", the loopback interface. I could specify
this:

$ y=${x#*inet addr:}

But, while gobbling text from the left, this search would stop at the IP
address of eth0, not the desired interface. So I can specify it this way:

$ y=${x#*lo *inet addr:}

As a last step I'll trim off all remaining text to the right:

$ y=${y%% *}

Leaving only the desired address.

It seems the "#" and "%" operators, and their variants, are able to accept a
rather complex argument and sort through the content of large strings,
including strings with line breaks. This means I can use the shell to directly
filter content in some simple cases where I might have considered using sed
or Perl.

Bash Version 3

I have always thought the inability to test for the presence of a string or pattern
(without using grep, sed or something similar) was a conspicuous weakness in shell
programming. Bash version 3, present on must current Linux distributions, addresses
this lack by allowing regular expression matching.

Let's say we need to establish whether variable $x appears to be a social security


number:

if [[ $x =~ [0-9]{3}-[0-9]{2}-[0-9]{4} ]]
then
# process SSN
else
# print error message
fi

Notice the Perlish "=~" syntax and that the regular expression appears within double
brackets. A substantial number of regular expression metacharacters are supported,
but not some of the Perl-specific extensions like \w, \d and \s.

Another Bash 3 feature is an improved brace expansion operator:

$ echo {a..z}

a b c d e f g h i j k l m n o p q r s t u v w x y z

for n in {0..5}
do
echo $n
done

0
1
2
3
4
5

Useful Links
Well, as long-winded as it turned out to be, this page is supposed to be an introduction
to shell programming, one that just touches the highlights. The linked references below
will provide a deeper understanding of the covered topics.

 A quick guide to writing scripts using the bash shell (Rutgers)


 Advanced Bash Scripting Guide (Linux Documentation Project)
 Bash Reference Manual (GNU Project, downloadable versions)
A quick guide to writing scripts using the bash shell
A simple shell script

A shell script is little more than a list of commands that are run in sequence.
Conventionally, a shellscript should start with a line such as the following:
#!/bin/bash
THis indicates that the script should be run in the bash shell regardless of which
interactive shell the user has chosen. This is very important, since the syntax of
different shells can vary greatly.

A simple example

Here's a very simple example of a shell script. It just runs a few simple commands
#!/bin/bash
echo "hello, $USER. I wish to list some files of yours"
echo "listing files in the current directory, $PWD"
ls # list files

Firstly, notice the comment on line 4. In a bash script, anything following a pound
sign # (besides the shell name on the first line) is treated as a comment. ie the shell
ignores it. It is there for the benifit of people reading the script.

$USER and $PWD are variables. These are standard variables defined by the bash
shell itself, they needn't be defined in the script. Note that the variables
are expanded when the variable name is inside double quotes. Expanded is a very
appropriate word: the shell basically sees the string $USER and replaces it with the
variable's value then executes the command.

We continue the discussion on variables below ...

Variables

Any programming language needs variables. You define a variable as follows:


X="hello"
and refer to it as follows:
$X
More specifically, $X is used to denote the value of the variable X. Some things to
take note of regarding semantics:

 bash gets unhappy if you leave a space on either side of the = sign. For
example, the following gives an error message:
X = hello
 while I have quotes in my example, they are not always necessary. where you
need quotes is when your variable names include spaces. For example,
X=hello world # error
X="hello world" # OK

This is because the shell essentially sees the command line as a pile of commands and
command arguments seperated by spaces. foo=baris considered a command. The
problem with foo = bar is the shell sees the word foo seperated by spaces and
interprets it as a command. Likewise, the problem with the command X=hello world is
that the shell interprets X=hello as a command, and the word "world" does not make
any sense (since the assignment command doesn't take arguments).

Single Quotes versus double quotes

Basically, variable names are exapnded within double quotes, but not single quotes. If
you do not need to refer to variables, single quotes are good to use as the results are
more predictable.

An example
#!/bin/bash
echo -n '$USER=' # -n option stops echo from breaking the line
echo "$USER"
echo "\$USER=$USER" # this does the same thing as the first two lines

The output looks like this (assuming your username is elflord)


$USER=elflord

$USER=elflord
so the double quotes still have a work around. Double quotes are more flexible, but
less predictable. Given the choice between single quotes and double quotes, use single
quotes.

Using Quotes to enclose your variables

Sometimes, it is a good idea to protect variable names in double quotes. This is


usually the most important if your variables value either (a) contains spaces or (b) is
the empty string. An example is as follows:
#!/bin/bash
X=""
if [ -n $X ]; then # -n tests to see if the argument is non empty
echo "the variable X is not the empty string"
fi

This script will give the following output:


the variable X is not the empty string
Why ? because the shell expands $X to the empty string. The expression [ -n ] returns
true (since it is not provided with an argument). A better script would have been:
#!/bin/bash
X=""
if [ -n "$X" ]; then # -n tests to see if the argument is non empty
echo "the variable X is not the empty string"
fi

In this example, the expression expands to [ -n "" ] which returns false, since the
string enclosed in inverted commas is clearly empty.

Variable Expansion in action

Just to convince you that the shell really does "expand" variables in the sense I
mentioned before, here is an example:
#!/bin/bash
LS="ls"
LS_FLAGS="-al"

$LS $LS_FLAGS $HOME

This looks a little enigmatic. What happens with the last line is that it actually
executes the command
ls -al /home/elflord
(assuming that /home/elflord is your home directory). That is, the shell simply
replaces the variables with their values, and then executes the command.

Using Braces to Protect Your Variables

OK. Here's a potential problem situation. Suppose you want to echo the value of the
variable X, followed immediately by the letters "abc". Question: how do you do this ?
Let's have a try :
#!/bin/bash
X=ABC
echo "$Xabc"
THis gives no output. What went wrong ? The answer is that the shell thought that we
were asking for the variable Xabc, which is uninitialised. The way to deal with this is
to put braces around X to seperate it from the other characters. The following gives
the desired result:
#!/bin/bash
X=ABC
echo "${X}abc"

Conditionals, if/then/elif
Sometimes, it's necessary to check for certain conditions. Does a string have 0 length
? does the file "foo" exist, and is it a symbolic link , or a real file ? Firstly, we use the
if command to run a test. The syntax is as follows:
if condition
then
statement1
statement2
..........
fi
Sometimes, you may wish to specify an alternate action when the condition fails.
Here's how it's done.
if condition
then
statement1
statement2
..........
else
statement3
fi
alternatively, it is possible to test for another condition if the first "if" fails. Note that
any number of elifs can be added.
if condition1
then
statement1
statement2
..........
elif condition2
then
statement3
statement4
........
elif condition3
then
statement5
statement6
........

fi

The statements inside the block between if/elif and the next elif or fi are executed
if the corresponding condition is true. Actually, any command can go in place of the
conditions, and the block will be executed if and only if the command returns an exit
status of 0 (in other words, if the command exits "succesfully" ). However, in the
course of this document, we will be only interested in using "test" or "[ ]" to evaluate
conditions.

The Test Command and Operators


The command used in conditionals nearly all the time is the test command. Test
returns true or false (more accurately, exits with 0 or non zero status) depending
respectively on whether the test is passed or failed. It works like this:
test operand1 operator operand2
for some tests, there need be only one operand (operand2) The test command is
typically abbreviated in this form:
[ operand1 operator operand2 ]
To bring this discussion back down to earth, we give a few examples:
#!/bin/bash
X=3
Y=4
empty_string=""
if [ $X -lt $Y ] # is $X less than $Y ?
then
echo "\$X=${X}, which is smaller than \$Y=${Y}"
fi

if [ -n "$empty_string" ]; then
echo "empty string is non_empty"
fi

if [ -e "${HOME}/.fvwmrc" ]; then # test to see if


~/.fvwmrc exists
echo "you have a .fvwmrc file"
if [ -L "${HOME}/.fvwmrc" ]; then # is it a symlink ?
echo "it's a symbolic link
elif [ -f "${HOME}/.fvwmrc" ]; then # is it a regular file ?
echo "it's a regular file"
fi
else
echo "you have no .fvwmrc file"
fi

Some pitfalls to be wary of

The test command needs to be in the form


"operand1<space>operator<space>operand2" or operator<space>operand2 , in other
words you really need these spaces, since the shell considers the first block containing
no spaces to be either an operator (if it begins with a '-') or an operand (if it doesn't).
So for example; this
if [ 1=2 ]; then
echo "hello"
fi

gives exactly the "wrong" output (ie it echos "hello", since it sees an operand but no
operator.)

Another potential trap comes from not protecting variables in quotes. We have already
given an example as to why you must wrap anything you wish to use for a -n test with
quotes. However, there are a lot of good reasons for using quotes all the time, or
almost all of the time. Failing to do this when you have variables expanded inside
tests can result in very wierd bugs. Here's an example: For example,
#!/bin/bash
X="-n"
Y=""
if [ $X = $Y ] ; then
echo "X=Y"
fi

This will give misleading output since the shell expands our expression to
[ -n = ]
and the string "=" has non zero length.

A brief summary of test operators

Here's a quick list of test operators. It's by no means comprehensive, but its likely to
be all you'll need to remember (if you need anything else, you can always check the
bash manpage ... )
number of
operator produces true if...
operands

-n operand non zero length 1

-z operand has zero length 1

-d there exists a directory whose name is operand 1

-f there exists a file whose name is operand 1

-eq the operands are integers and they are equal 2

-neq the opposite of -eq 2

= the operands are equal (as strings) 2

!= opposite of = 2

operand1 is strictly less than operand2 (both operands should be


-lt 2
integers)

operand1 is strictly greater than operand2 (both operands should


-gt 2
be integers)

operand1 is greater than or equal to operand2 (both operands


-ge 2
should be integers)

-le operand1 is less than or equal to operand2 (both operands should 2


be integers)

Loops

Loops are constructions that enable one to reiterate a procedure or perform the same
procedure on several different items. There are the following kinds of loops available
in bash

 for loops
 while loops

For loops

The syntax for the for loops is best demonstrated by example.


#!/bin/bash
for X in red green blue
do
echo $X
done
THe for loop iterates the loop over the space seperated items. Note that if some of the
items have embedded spaces, you need to protect them with quotes. Here's an
example:
#!/bin/bash
colour1="red"
colour2="light blue"
colour3="dark green"
for X in "$colour1" $colour2" $colour3"
do
echo $X
done
Can you guess what would happen if we left out the quotes in the for statement ? This
indicates that variable names should be protected with quotes unless you are pretty
sure that they do not contain any spaces.

Globbing in for loops

The shell expands a string containing a * to all filenames that "match". A filename
matches if and only if it is identical to the match string after replacing the stars * with
arbitrary strings. For example, the character "*" by itself expands to a space seperated
list of all files in the working directory (excluding those that start with a dot "." ) So
echo *

lists all the files and directories in the current directory.


echo *.jpg
lists all the jpeg files.
echo ${HOME}/public_html/*.jpg
lists all jpeg files in your public_html directory.

As it happens, this turns out to be very useful for performing operations on the files in
a directory, especially used in conjunction with a for loop. For example:
#!/bin/bash
for X in *.html
do
grep -L '<UL>' "$X"
done

While Loops

While loops iterate "while" a given condition is true. An example of this:


#!/bin/bash
X=0
while [ $X -le 20 ]
do
echo $X
X=$((X+1))
done

This raises a natural question: why doesn't bash allow the C like for loops

for (X=1,X<10; X++)


As it happens, this is discouraged for a reason: bash is an interpreted language, and a
rather slow one for that matter. For this reason, heavy iteration is discouraged.

Command Substitution

Command Substitution is a very handy feature of the bash shell. It enables you to take
the output of a command and treat it as though it was written on the command line.
For example, if you want to set the variable X to the output of a command, the way
you do this is via command substitution.

There are two means of command substitution: brace expansion and backtick
expansion.

Brace expansion workls as follows: $(commands) expands to the output


of commands This permits nesting, so commands can include brace expansions

Backtick expansion expands `commands` to the output of commands

An example is given;:
#!/bin/bash
files="$(ls)"
web_files=`ls public_html`
echo "$files" # we need the quotes to preserve embedded newlines in
$files
echo "$web_files" # we need the quotes to preserve newlines
X=`expr 3 \* 2 + 4` # expr evaluate arithmatic expressions. man expr for
details.
echo "$X"

The advantage of the $() substitution method is almost self evident: it is very easy to
nest. It is supported by most of the bourne shell varients (the POSIX shell or better is
OK). However, the backtick substitution is slightly more readable, and is supported by
even the most basic shells (any #!/bin/sh version is just fine)

Note that if strings are not quote-protected in the above echo statement, new lines are
replaced by spaces in the output.

You might also like