Register
It is currently Mon Sep 22, 2014 6:16 pm

Intermediate Bash Tutorial - by jbsnake


All times are UTC - 6 hours


Post new topic Reply to topic  [ 13 posts ] 
Author Message
 PostPosted: Mon May 16, 2005 8:49 pm   
Site Admin
User avatar

Joined: Sun May 15, 2005 9:36 pm
Posts: 667
Location: Des Moines, Iowa
jbsnake's Intermediate Tutorial
written by jbsnake, reposted by crouse.
----------------------------------------------

If you havn't read the beginner tutorial, I suggest you stop now and read it first. If you have read it, congratulations! You can officially write what I call a "dumb" script. I say it's a dumb scrip because it contains no logic. It simply goes from the top of the script to the bottom executing code as it goes.
Logic is pretty easy to implement once you know the key words. The most basic logical expression is if. Logical expressions help direct the flow of logic within a script. A good example of that is at the beginning of this tutorial. <b>If</b> you havn't read the beginner tutorial... In a shell script that would look something like this:
Code:
   if [[ ! $readBeginningTutorial ]]
   then   
      exit
   fi
   

An if statement's syntax is pretty easy, it <u><b>must</b></u> start with the word if, followed by a condition ($a = $b), on the next line it <u><b>must</b></u> have the word then. The next few lines are commands to execute if the above mentioned condition is met. The if statement is then closed using the word fi (if backwards). The brackets ([[ ]]) surrounding the condition are not always needed, it depends on the condition. I personally always use the brackets just to help me keep things uniform. That way I can easily spot al my conditions in my scripts. I think thats enough babbling about the if statement. Let's actually write some if statements so you can see what all the stuff I said above is actually doing.
For out first example, let's do something easy. Don't forget the needed stuff at the beginning of your script!
Code:
   #!/bin/bash
   
   if [[  1 == 2 ]]
   then
      echo "You will never see this while executing this script!";
   fi
   

The above example checked to see if 1 was equal to 2. Since that condition can never be met, the echo command will never execute. Here's another example:
Code:
   #!/bin/bash
   
   if [[ 1 == 2 ]]
   then
      echo "This will never execute";
   fi
   echo "This will execute no matter what the condition is"
   echo "because it is outside of the if statement."
   

I know what you are thinking. "That's great and all, but how can I check to see if it's either true or false?". Answer: elif. Elif is bash's version of Else If. A quick example of that:
Code:
   #!/bin/bash
   
   if [[ 1 == 2 ]]
   then
      echo "Not Possible";
   elif [[ 1 != 2 ]]
   then
      echo "Always Prints";
   fi
   

<u><b>Note</b></u>: Spacing is very very important. [[<sp>condition1<sp>expression<sp>condition2<sp>]]

Notice elif took a condition. It <u><b>must</b></u> have a conditional expression just like if does. You can also use else instead of =elif, or in addition to. Example:
Code:
   if [[ 1 == 2 ]]
   then
      echo "yea, right";
   else
      echo "always prints";
   fi
   

<u><b>Note</b></u>: I'm not going to keep writing an entire script for each example, just the part I'm focussing on.

In the above example, you'll notice else doesn't take a condition like if and elif. That's because else executes if none of the other conditions within the if statement are met. Example:
Code:
   if [[ 1 == 2 ]]
   then
      echo "condition can't be met";
   elif [[ 1 > 2 ]]
   then
      echo "condition can't be met";
   else
      echo "only thing that will print";
   fi
   

Notice only the commands in the else portion execute. An else and/or an elif are skipped if the if condition is met. Example:
Code:
   if [[ 1 == 1 ]]
   then
      echo "This is all that executes"
      echo "Well, besides this"
      echo "But that is because it's all in the"
      echo "Same block of code"
   elif [[ 2 == 2 ]]
   then
      echo "Even though is condition is met"
      echo "None of this will print because"
      echo "The if condition was already met"
   else
      echo "This is skipped"
   fi
   

I personally don't care much for elif. If I have more than true or false for a condition, I'll use a case statement. A case statement checks one variable for multiple possible conditions. Example:
Code:
   bob="one"
   case $bob in
      "one")   echo "one";
         bob="two";
         ;;
      "two")   echo "two";
         bob="three";
         ;;
      "three")echo "three";
         bob="four";
         ;;
      *)   echo "anything else";
         ;;
   esac
   

After reading this tutorial this far, you're probably saying "So?". If you already knew the enclosed information, you have probably written a few programs in the past. I wrote this tutorial for someone with no background experience in programming in mind. For the programmers that read this, I would just pay attention to the syntax.
Now let's put this tutorial to use. I think we should write a little menu driven script. We will use if-then-else and case. Along with some bonus material (a loop) thrown in for kicks. Open a terminal window and type:
Code:
   cd
   echo "#!/bin/bash" > menuScript
   

Now that the first (not to mention most important) line of your script is written, open it in your favorite editor (fyi: the script is named "menuScript"). I usually use vi, but kate is my favorite GUI editor. Under the first line let's make some variables.
Code:
   #!/bin/bash
   
   ### start variables ###
   ans=""
   choice=""
   ### end variables ###
   

After the variables, I suggest writing the functions we'll use.
Code:
   ### start functions ###
   function yesno
   {
      passedVar=$1

####################################################
# use part of the variable passed. start with the 0 position and the
# length is one (first letter).
####################################################
      ans=${passedVar:0:1}

###################################################
# pipe the varible to tr and make all uppercase letters lowercase
###################################################
      ans=`echo $ans | tr [:upper:] [:lower:]`

      if [[ $ans -eq "y" ]]
      then
         return 0;
      elif [[ $ans -eq "n" ]]
      then
         return 1;
      else
         echo "*** Yes or No only!! ***;
      fi
   }
   ### end functions ###
   

Now you can start the main body of the script. Keep in mind you can obviously create more functions, thus writing less in your main body, not to mention cutting down on repeating alot of the same code. This is called modularization (this essence of object oriented coding). If you write your functions well, you can usually just copy and paste the same functions from one script to another (something I love to do). Or you can create a function library, I may get into that in another tutorial).
Back on track, let's write our main body:
Code:
   ### start main ###
   echo "Please choose one of the following:"
   echo "1) Echo the current directory"
   echo "2) List the contents of current directory"
   echo "3) Echo who is logged onto your computer"
   echo "4) Echo who is running this script"
   echo "0) Exit"
   read choice
      
   while [[ $choice != 0 ]]
   do
      case $choice in
         1)   clear;
            pwd;
            ;;
         2)   clear;
            ls;
            ;;
         3)   clear;
            who;
            ;;
         4)   clear;
            whoami;
            ;;
         0)   clear;
            echo "Hope you enjoyed writting/running this!";
            exit;
            ;;
         *)   clear;
            echo "Not a valid selection!";
            ;;
      esac
      echo "Please choose one of the following:"
      echo "1) Echo the current directory"
      echo "2) List the contents of current directory"
      echo "3) Echo who is logged onto your computer"
      echo "4) Echo who is running this script"
      echo "0) Exit"
      read choice
   done
   

Ofcourse there is so very much more you could do with the example I gave you. That is up to you. Notice I didn't even use the function I wrote, that also is for you. See if you can't come up with a way to incorporate that function into this script, or another script you may write. Hope you have enjoyed reading this. Keep an eye out for the next installment of my bash tutorials (there only going to get harder :) )


Top
 Profile WWW  
 PostPosted: Sun Aug 16, 2009 9:28 am   
User avatar

Joined: Sun Aug 02, 2009 5:33 am
Posts: 2
Location: Thessaloniki [Greece]
Code:
       ### start main ###
       echo "Please choose one of the following:"
       echo "1) Echo the current directory"
       echo "2) List the contents of current directory"
       echo "3) Echo who is logged onto your computer"
       echo "4) Echo who is running this script"
       echo "0) Exit"
       read choice
         
       while [[ $choice != 0 ]]
       do
          case $choice in
             1)   clear;
                pwd;
                ;;
             2)   clear;
                ls;
                ;;
             3)   clear;
                who;
                ;;
             4)   clear;
                whoami;
                ;;
             0)   clear;
                echo "Hope you enjoyed writting/running this!";
                exit;
                ;;
             *)   clear;
                echo "Not a valid selection!";
                ;;
          esac
          echo "Please choose one of the following:"
          echo "1) Echo the current directory"
          echo "2) List the contents of current directory"
          echo "3) Echo who is logged onto your computer"
          echo "4) Echo who is running this script"
          echo "0) Exit"
          read choice
       done


Why do you use --> ; these, after the commands ?

for example: clear;
for example: clear

I mean, what's the difference ?


Top
 Profile WWW  
 PostPosted: Wed Aug 18, 2010 4:48 pm   
Site Admin

Joined: Tue May 17, 2005 7:31 pm
Posts: 251
Location: Georgia
There isn't really a difference, however, I do it to keep myself straight with where I am in the case statement.
Since the separate "cases" are separated by ;;

You can totally get away with NOT using the ;
But as I understand it, it's best practice to use them. Though it will work either way.

Kind of like the difference of:

if [[ "$something" == "this" ]]; then

Versus:

if [[ "$something" == "this" ]]
then

Both works, but some prefer to layer the 'then' to help keep track of where they are in the script.


Top
 Profile  
 PostPosted: Wed Aug 18, 2010 10:43 pm   
User avatar

Joined: Sun Jun 27, 2010 12:57 am
Posts: 192
RestlessKing wrote:
Why do you use --> ; these, after the commands ?

for example: clear;
for example: clear

I mean, what's the difference ?


As you might or might not know, in shell you can separate commands either by a newline or a semicolon character.
So you can write either:
Code:
clear; echo
or
Code:
clear
echo

Putting a semicolon at the end of a line and then putting the next command on a new line, would be like putting two new lines between command.
Code:
clear;
echo;
would be like
Code:
clear

echo


It doesn't matter to bash what you use or how many you use, just as long as you use them to separate commands.


Top
 Profile  
 PostPosted: Thu Feb 10, 2011 5:31 pm   

Joined: Thu Feb 10, 2011 5:14 pm
Posts: 2
I'm new here i think best is practices standard programing

i love these tutorial
since i start bash a few days ago i think i can learn a lot on this forum

Help ever and hurt never


Top
 Profile  
 PostPosted: Thu Feb 10, 2011 7:43 pm   

Joined: Wed May 30, 2007 9:22 pm
Posts: 39
Location: California
Hey jbsnake I was browsing around the PCLinux forums today and came across a sticky that linked to a list of useful bashscript tutorials and guides. Check out number 10!

http://www.techremedy.net/blog/17-amazi ... h-scripts/


Top
 Profile  
 PostPosted: Tue Feb 15, 2011 10:27 pm   
Site Admin
User avatar

Joined: Sun May 15, 2005 9:36 pm
Posts: 667
Location: Des Moines, Iowa
That's nothing... he also wrote number 9 :D


Top
 Profile WWW  
 PostPosted: Fri Oct 28, 2011 11:50 am   
Site Admin

Joined: Tue May 17, 2005 7:31 pm
Posts: 251
Location: Georgia
heh... i almost feel famous ;)


Top
 Profile  
 PostPosted: Sat Nov 05, 2011 8:20 pm   
User avatar

Joined: Wed Jun 08, 2011 8:27 am
Posts: 189
Location: outer Shpongolia
jbsnake wrote:
[...]
Code:
#!/bin/bash
   
if [[  1 == 2 ]]
then
   echo "You will never see this while executing this script!";
fi

The above example checked to see if 1 was equal to 2. [...]


That's actually wrong. You are checking if the character « 1 » is equivalent to the character « 2 ».
You're not performing an algebraic comparison.

With the [[ ... ]] syntax, you would use:
Code:
if [[ 1 -eq 2 ]]; then


But, in bash(1), we use the (( ... )) syntax for arithmetic, so the correct code is:
Code:
if ((1 == 2)); then


----

Code:
#!/bin/bash
   
if [[ 1 == 2 ]]
then
   echo "This will never execute";
fi
echo "This will execute no matter what the condition is"
echo "because it is outside of the if statement."


Same remark here.

----

Code:
#!/bin/bash
   
if [[ 1 == 2 ]]
then
   echo "Not Possible";
elif [[ 1 != 2 ]]
then
   echo "Always Prints";
fi


Same here. Characters comparison.

----

Code:
if [[ 1 == 2 ]]
then
   echo "yea, right";
else
   echo "always prints";
fi


Here too.

----

Code:
if [[ 1 == 2 ]]
then
   echo "condition can't be met";
elif [[ 1 > 2 ]]
then
   echo "condition can't be met";
else
   echo "only thing that will print";
fi


Also here.

And there is another mistake:
You're comparing the character « 1 » with the character « 2 », lexicographically, not algebraically.

Code:
$ help test
[...]
STRING1 > STRING2
                     True if STRING1 sorts after STRING2 lexicographically.
[...]


So, say I want to check if 8 is greater than 42 with this syntax:
Code:
$ [[ 8 > 42 ]] && echo '8 is greater than 42' || echo '42 is greater than 8'
8 is greater than 42


See, it fails, because 8 is greater than 4.

Again, the correct syntax for this type of comparison is:
Code:
if ((1 > 2)); then


----

Code:
if [[ 1 == 1 ]]
then
   echo "This is all that executes"
   echo "Well, besides this"
   echo "But that is because it's all in the"
   echo "Same block of code"
elif [[ 2 == 2 ]]
then
   echo "Even though is condition is met"
   echo "None of this will print because"
   echo "The if condition was already met"
else
   echo "This is skipped"
fi


First observation applies there.

----

Code:
### start functions ###
function yesno
{
   passedVar=$1

####################################################
# use part of the variable passed. start with the 0 position and the
# length is one (first letter).
####################################################
   ans=${passedVar:0:1}

###################################################
# pipe the varible to tr and make all uppercase letters lowercase
###################################################
   ans=`echo $ans | tr [:upper:] [:lower:]`

   if [[ $ans -eq "y" ]]
   then
      return 0;
   elif [[ $ans -eq "n" ]]
   then
      return 1;
   else
      echo "*** Yes or No only!! ***;
   fi
}
### end functions ###


  1. Don't use the keyword « function », it's useless and makes your script non-POSIX compliant. Write this instead:
    Code:
    yesno()
    {
        [...]
    }



  2. Let's talk about this line:
    Code:
    ans=`echo $ans | tr [:upper:] [:lower:]`


    1. You're using backticks, i.e. ``, which is deprecated, and shouldn't be used in bash(1). We use $(...) instead. (it's POSIX compliant too)

    2. You should quote the expansion of ans, since you don't know what crap can be entered. (what if it's an asterisk?)


    3. You're piping echo(0) to tr(1), so a string to tr(1). It runs tr(1) in a subshell, and this behavior can be avoided
      thanks to the <<< redirection.

      In other words, this command:
      Code:
      echo "$ans" | tr '[:upper:]' '[:lower:]'

      is equivalent to:
      Code:
      tr '[:upper:]' '[:lower:]' <<< "$ans"


      So your line finally looks like that:
      Code:
      ans=$(tr '[:upper:]' '[:lower:]' <<< "$ans")


      But this case change is useless. I'll explain how not to do it in 3).



  3. Now, about these ones:
    Code:
    if [[ $ans -eq "y" ]]
    then
       return 0;
    elif [[ $ans -eq "n" ]]
    then
       return 1;
    else
       echo "*** Yes or No only!! ***;
    fi


    Here, you are comparing characters thanks to -eq which is meant to be used for integers.
    Thus, your both conditions are totally wrong. Let's do a little test to demonstrate:
    Code:
    $ [[ y -eq w ]] && echo 'y is equal w' || echo 'y is not equal w'
    y is equal w


    The correct syntax is:
    Code:
    if [[ $ans = y ]]; then
        [...]
    elif [[ $ans = n ]]; then
        [...]
    else
        [...]
    fi


    And you want the conditions to be true even if Y or N is entered by the user, so you use:
    Code:
    if [[ $ans = [yY] ]]; then

    and
    Code:
    elif [[ $ans = [nN] ]]; then

    instead of changing the case of ans.

----

Code:
while [[ $choice != 0 ]]
do


... you know what's wrong.

Correction:
Code:
while ((choice)); do


Top
 Profile  
 PostPosted: Sun Nov 06, 2011 1:37 pm   
Site Admin
User avatar

Joined: Sun May 15, 2005 9:36 pm
Posts: 667
Location: Des Moines, Iowa
jsz wrote:

That's actually wrong. You are checking if the character « 1 » is equivalent to the character « 2 ».
You're not performing an algebraic comparison.

With the [[ ... ]] syntax, you would use:
Code:
if [[ 1 -eq 2 ]]; then


But, in bash(1), we use the (( ... )) syntax for arithmetic, so the correct code is:
Code:
if ((1 == 2)); then




Meh, I think that's NOT entirely correct..... EITHER way is correct.
Personally I ALWAYS use the [[ when doing a TEST, as I'm scanning my code it's very quick to see what is a test, and what is actually calculations.
And if i'm using an "if" then I'm probably "testing" something .... NOT actually doing math calculations per se. In bash, as in perl , there is more than one way to do things.

I realize you quote and use http://mywiki.wooledge.org as the "definitive" resource for bash, however, I do not always agree with it.
EX:
http://mywiki.wooledge.org/BashPitfalls
Quote:
The double brackets support this syntax too:
[[ $foo -gt 7 ]] # Also right!

But why use that when you could use ((...)) instead?


Why ?? Because I'm doing a "comparision TEST", not perfoming a "calculation" that I will assign to a variable for use later. So my question is why would I want to start using two different styles of a "test" ?? That's just plain confusing when it doesn't need to be.


.........also in response to your other comments....
Quote:
1. Don't use the keyword « function », it's useless and makes your script non-POSIX compliant. Write this instead:

2. Let's talk about this line:
a. You're using backticks, i.e. ``, which is deprecated, and shouldn't be used in bash(1). We use $(...) instead. (it's POSIX compliant too)
b. You should quote the expansion of ans, since you don't know what crap can be entered. (what if it's an asterisk?)
c. You're piping echo(0) to tr(1), so a string to tr(1). It runs tr(1) in a subshell, and this behavior can be avoided
thanks to the <<< redirection.


As for #1 ... if you don't care if it's posix compliant (IE: your writing your script for a specific machine) ... it DOESNT MATTER.
Some people like to be able to find their functions very quickly, and oddly enough, the word "function" makes it stand out pretty well.
(I personally don't use it, but it's not like it breaks some cardinal rule if you do use it.)

#2
a. ------- personally, I STILL use backticks, it shows me quickly that it is command substitution and not a mathamatical expression..my own personal preference, but ALSO if you noted when this was written.......... 2005, that bash 3.0 was out, and the $(....) didn't work correctly until 3.2
b. probably would be best yes.
c. Again....... EITHER way works, jbsnakes way is much clearer to those NEW to bash, your way might be faster.......but lets face it, does that REALLY matter ? If it does, your probably using the wrong language for the job, as bash never laid any claims to being the fastest that I recall ;)


Top
 Profile WWW  
 PostPosted: Sun Nov 06, 2011 6:10 pm   
User avatar

Joined: Wed Jun 08, 2011 8:27 am
Posts: 189
Location: outer Shpongolia
crouse wrote:
Why ?? Because I'm doing a "comparision TEST", not perfoming a "calculation" that I will assign to a variable for use later.
So my question is why would I want to start using two different styles of a "test" ?? That's just plain confusing when it doesn't need to be.


Indeed, you can assign variables directly within the (( ... )) syntax, but I just did a test there, not an assignment,
and I don't think that [[ 1 -gt 2 ]] is clearer than ((1 > 2)).

But it's your choice not to use it.

----

crouse wrote:
a. ------- personally, I STILL use backticks, it shows me quickly that it is command substitution and not a mathamatical expression..my own personal preference,
but ALSO if you noted when this was written.......... 2005, that bash 3.0 was out, and the $(....) didn't work correctly until 3.2


I didn't find any information about this in 3.2's changelog,
but something about `` being fixed in 4.0, unfortunately.

Quote:
933 This document details the changes between this version, bash-4.0-alpha,
934 and the previous version, bash-3.2-release.
[...]
938 a. Fixed several bugs in old-style `` command substitution parsing, including
939 comment parsing and quoted string handling


Anyway, it's a good habit to use it, and I'm not forcing you.

----

crouse wrote:
c. Again....... EITHER way works, jbsnakes way is much clearer to those NEW to bash, your way might be faster.......but lets face it, does that REALLY matter ?
If it does, your probably using the wrong language for the job, as bash never laid any claims to being the fastest that I recall ;)


Don't take this so seriously, I just give advices.
You follow them or not. After understanding why you should, of course.

I just know that now bash(1) beginners who are reading this tutorial
know about this useful redirection, and that's cool for them.


Top
 Profile  
 PostPosted: Mon Nov 07, 2011 2:40 pm   
Site Admin
User avatar

Joined: Sun May 15, 2005 9:36 pm
Posts: 667
Location: Des Moines, Iowa
I just didn't wan't people to think that there is a set in stone way to do things, as there is not, and one persons view of how things should be done isn't always the "right" way.
Sometimes it is just a matter of personal preference.


Top
 Profile WWW  
 PostPosted: Sat Jan 21, 2012 1:35 pm   
Site Admin

Joined: Tue May 17, 2005 7:31 pm
Posts: 251
Location: Georgia
I had written a rant... read it over a few times... decided to say "forget it".

I'm over it. You missed the whole point of this tutorial.

Whatever... I'm done. You officially made this tutorial so littered with crap after the fact it's pointless to even have here anymore.


Top
 Profile  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  


BashScripts | Promote Your Page Too
Powered by phpBB © 2011 phpBB Group
© 2003 - 2011 USA LINUX USERS GROUP