Linux Shell Programming

Here we have a free and complete book about Shell

The thirst of the "free knowledge" is welcome.

Comandos Shell Script

Syntactic Diagram
Júlio Neves
Júlio Neves
Home | Articles Português Español

Buy the book

Changelogs

  Pub Talk 1  

  Pub Talk 2  

  Pub Talk 3  

  Pub Talk 4  

  Pub Talk 5  

Pub Talk - Part V

- YO, man! How' doin' ? How's your brain ? Burned or can you handle more Shell ?

- I do ! I'm loving it ! I liked it so much that I put more love at exercise you gave me. Did you remember you asked me to do a program to receive as parameter a file name and, when executed, it should save this file with its original name followed by a tilde (~) and open the file with vi ?

- Sure I do. Show me the money and tell me how you did it.

$ cat vira #!/bin/bash # # vira - vi backing up the previous file # == = =

# Cheking if we have one parameter if [ "$#" -ne 1 ] then echo "Error -> Usage: $0 " exit 1 fi

Arq=$1 # If the file doesn't exist, there is no copy to be saved if [ ! -f "$Arq" ] then vi $Arq exit 0 fi

# If I can save the file, why call vi ? if [ ! -w "$Arq" ] then echo "You are not allowed to write $Arq" exit 2 fi

# Everything is OK. I'll save the backup and call vi cp -f $Arq $Arq~ vi $Arq exit 0

- Yeah, right ! But tell me: why did you finish the program with an exit 0?

- Ahhh! I found the number after exit will be the program's return code (the $?, remember?), and, as everything succeded ok, it would finish with $? = 0. But, if you notice, you'll see, in case of the file name doesn't be sent or the user didn't have the write privileges in this file, the return code would be different of zero.

- Good boy, you learned cool, but is better to clarify exit 0, only exit or even don't put exit, make the same return code ($?) as zero. Now let me talk about loop instructions. But, first things first, I'll thell you about program block concept.

'Till now, we saw some program blocks. When we saw an example about a cd inside a directory like this:

cd lmb 2> /dev/null ||
    {
    mkdir lmb
    cd lmb
    }

the code inside two keys ({}) makes a command block. Also in our last exercise, in which we saved a file before edit it, there are various command blocks inside the then and fi from if.

A command block can be inside a case, or between a do and e done.

- Hold on Julio, what are these do and done that I don't remember you had tell me about ? And look, I'm really paying attention !

- Yeah, you're right. I didn't tell it because the right time wasn't arrived. All loop instructions execute the block command found between do e o done.

Loop Commands

The loop commands, or loop instructions, are for, while and until, that I'll start to explain one by one from now.

The for command

If you are a programmer, or have some programming experience, I'm sure you know the for command. But, what you don't know is the for, a built-in Shell instruction (that means the command's source code is part of Shell's source code ), is many times more powerfull than its other languages correlated command.

Lets understand its syntax, first in plain english, and then the real deal.

    for var in val1 val2 ... valn
    do
        cmd1
        cmd2
        cmdn
    done

The variable var assumes any values from the list val1 val2 ... valn and for each one of these values, the command block made by cmd1, cmd2 e cmdn is executed.

Now we saw what that means, lets see the correct syntax:

for command first syntax

    for var in val1 val2 ... valn
    do
        cmd1
        cmd2
        cmdn
    done

That is amazing ! There is no changes between the real life, our real language, and the for command syntax. Very easy to learn, then. Lets right to the samples, to correctly understand how the command works. Lets make a script to list all files in our directory separeted by colons, but first, see:

$ echo * ArqDoDOS.txt1 confuso incusu logado musexc musicas musinc muslist

That means, Shell saw the star (*) and expanded it whit the name os all files in the directory and the echo command sent them to screen separated by spaces. That understood, lets see how can we solve the proposed problem:

$ cat testefor1 #!/bin/bash # 1st. Program to understand for

for Arq in * do echo -n $Arq: # The -n option doesn't generate a new line done

Then lets run it:

$ testefor1 ArqDoDOS.txt1:confuso:incusu:logado:musexc:musicas:musinc:muslist:$

As you can see, Shell changes the star in a space-separeted list. When for saw that list, he said: "Yeah, space-separated lists... that's my groovy, baby !"

The command block to be executed was only the echo, with its -n option, listed the variable $Arq followed by colons (:), without a new-line character. The dolar ($) at the end of execution line is the prompt. that remained at the same line also because of the -n echo's option.

Another simple example (by now):

$ cat testefor2 #!/bin/bash # 2nd. program to understand for

for WORD in We are in the Pub Talk do echo $WORD done

Running, whe have:

$ testefor2 We are in the Tub Talk

This is a silly simple sample, like the other one, but serves to show for's basic behavior.

See the power of the for: we're still in the its first syntax and I'm showing new ways to use it. Back in our chat, I told for used space-separated lista, but this is not the whole truth. It was only to make understanding easy.

For real, the lists are not separated by spaces unconditionally but, before go ahead, let me show you how is the behavior of a system variable called $IFS. Take a look at its value:

$ echo "$IFS" | od -h 0000000 0920 0a0a 0000004

I sent the variable (protected from Shell execution by quotation marks) to a hexadecimal dump (od -h) and I have:

$IFS Variables Value
0a <ENTER>
  Hexa     Means  
09 <TAB>
20 <ESPACE>

Where the last 0a came from the final <ENTER> at the command. To maximize the explanation, lets see the other way:

$ echo ":$IFS:" | cat -vet : ^I$ :$

Payt attention to the following tip to understand that cat construction:

Pinguim com placa de dica At the cat command, option -e represents the <ENTER> whit a dolar ($) and the option -t represents the <TAB> as a ^I. I used colons (:) to show echo='s start and ending. This way, once again we can see that the variable =$IFS has three characters.

Now see, IFS means Inter Field Separator. Knowing this, I can prove that for command doesn't use space-separated lists, but lists separated by the $IFS value, which default are these characters we saw. To prove this, lets show a script that receives the singer name as parameter and shows the musics he plays, but first lets see how our musics file looks like:

$ cat musics album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica6 album 4^Artista7~Musica7:Artista1~Musica3 album 5^Artista9~Musica9:Artista10~Musica10

Using this layout, we wrote the following script:

$ cat listartista #!/bin/bash

if [ $# -ne 1 ] then echo You should pass a parameter exit 1 fi

IFS=" :"

for ArtMus in $(cut -f2 -d^ musicas) do echo "$ArtMus" | grep $1 && echo $ArtMus | cut -f2 -d~ done

The script, as always, starts testing if the parameters was correctly passed . After that, the IFS was setted to <ENTER> and colon (:) (as we can see in the quotes in different lines), because it is the responsible to separate the blocks Artistan~Musicam. In this way, the variable $ArtMus will receive each one of these blocks in the file ( notice that for receives the records without the album because of cut in its line). If it found the first parameter ($1) at the block, the second o segundo cut will list only the music name. Lets run it:

$ listartista Artista1 Artista1~Musica1 Musica1 Artista1~Musica3 Musica3 Artista10~Musica10 Musica10

Ups! Two undesireable things happen: the blocks and the Musica10 = were listed too. Besides, our music file is quite simple. In the real life, the music and the artist have more than one name. Supose the artist was a couple named Paris & Britney (I really don't like neither to think about it... I'm affraid it can be true one day...LOL). In this case the =$1 was Paris and the rest of this beautifull name would be ignored in the search.

To avoid this, I should pass the artist name between quotes (") or change $1 by $@ (that means all passed parameters), that is the best solution but, in this case, I should change parameters checking and grep. The new check shoud be if I passed at least one parameter, instead of if I passed a parameter. The grep, this is what we get after the substituton of $* for its parameters:

    echo "$ArtMus" | grep Paris & Britney

what would generate an error. The right way is:

    echo "$ArtMus" | grep -i "Paris & Britney"

We put the -i option to ignore the case and the quotes was inserted to the artist name be recognized as a one array of chars.

We still need to fix the list of Artista10 error. To do this, the best way is tell to grep that the array of chars is at begining of $ArtMus (the regular expression to do this is ^) followed by a tilde (~). We need to redirect the grep messages to /dev/null to avoid block listing. So lets see the final program:

$ cat listartista #!/bin/bash # Dado um artista, mostra as suas musicas # versao 2

if [ $# -eq 0 ] then echo You should pass a parameter exit 1 fi

IFS=" :"

for ArtMus in $(cut -f2 -d^ musicas) do echo "$ArtMus" | grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d~ done

Running it, we have:

$ listartista Artista1 Musica1 Musica3

for command second syntax

    for var
    do
        cmd1
        cmd2
        cmdn
    done

- What ? Whitout the in how he knows what value assumes ?

- Yeah, right ? This construction seems strange but it is quite simple. In this case, var will assume one by one each passed parameters.

Lets make some samples to better understanding. We'll do a script to receive a bunch of musics as parameters and list its authors:

$ cat listamusica #!/bin/bash # Receives parts of the music as parameter and # lists its players. If the parameter is a composite name # shall be passed between quotes. # ex. "Ups I did it again" "Four died in Ohio" # if [ $# -eq 0 ] then echo Uso: $0 musica1 [musica2] ... [musican] exit 1 fi IFS=" :" for Musica do echo $Musica Str=$(grep -i "$Musica" musicas) || { echo " not found" continue } for ArtMus in $(echo "$Str" | cut -f2 -d^) do echo " $ArtMus" | grep -i "$Musica" | cut -f1 -d~ done done

Again, we started the exercise with a parameter check. Then we did a for in which the variable $Musica will receive each one of the passed parameters, putting in $Str all albuns that contain the musics. Then, the other for search for eah block Artista~Musica in the records we have in $Str and lists each artist that plays that music.

Lets run to see if it works:

$ listamusica musica3 Musica4 "Poison" musica3 Artista3 Artista1 Musica4 Artista4 Poison Não encontrada

That's an ugly output because we still don't know how to format outputs, but any given day, when you know how to work with cursos positioning, bold, color, etc, we'll do this list again using all these "little stuff".

At this time, we must asking: "What about that traditional, other languages, for that counts from a number with an addition until reachs a condition ?"

I will answer you: "I told you, homeboy, our for is much more coolio that the others ?" There is two ways:

1 - With our first syntax, as the following examples, run straigh at the prompt:

$ for i in $(seq 9) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9

In this one, the variable i assumed the integers 1 to 9 generated by the seq command and the echo's -n option was used to avoid line break after each number.

Still using for with seq:

$ for i in $(seq 3 9) > do > echo -n "$i " > done 4 5 6 7 8 9

Or with the more complete seq form:

$ for i in $(seq 0 3 9) > do > echo -n "$i " > done 0 3 6 9

2 - The other way is to do the same with a C-like syntax, showed as follow.

for command third syntax

    for ((var=ini; cond; incr))
    do
        cmd1
        cmd2
        cmdn
    done

Onde:

var=ini - means the variable var starts with an initial value ini cond - Means that the for loop will be executed while var doesn't reach the condition cond incr - Means the addition variable var will have in each loop iteration.

Examples, examples, examples, to clarify the things:

$ for ((i=1; i<=9; i++)) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9

In this case the variable i started from 1; the command block (the echo) will be executed while the variable i is less or equal than (<=) 9 and the i increment will be 1 in each loop iteration.

Notice that at the for command I didn't put a dolar ($) before i, and the increment notation (i++) is different from what we came seeing until now. This is possible because the double parentesis (or the let command) calls the shell arithmetic interpreter, that is more tolerant.

And, as I told about the let command, just to illustrate how it works and how versatile the for command can be, lets do the same, but with the last for part ommited, sending it to the command block.

$ for ((; i<=9;)) > do > let i++ > echo -n "$i " > done 1 2 3 4 5 6 7 8 9

The increment was tooked off the for body and sent to command block. When I used the let, I even need to initialize the $i variable. See the following commands, in the prompt:

$ echo $j

$ let j++ $ echo $j 1

That means, the variable $j even exist and at the first let it assumed the zero value to, after the increment, get the value 1.

See how simple the things can be:

$ for arq in * > do > let i++ > echo "$i -> $Arq" > done 1 -> ArqDoDOS.txt1 2 -> confuso 3 -> incusu 4 -> listamusica 5 -> listartista 6 -> logado 7 -> musexc 8 -> musicas 9 -> musinc 10 -> muslist 11 -> testefor1 12 -> testefor2

- That's it mate. I'm pretty sure you are full of for command. That's enought for today. In the next time we talk about another loop instructions. I wish you to do a little script to count the words in a text file, passing its name as parameter. This counting must be done using for command to know it. You can't use wc -w.

- Hey Chico! Bring me the last one.

Creative Commons license - Attribution and Non-Commercial (CC) 2009 By Visitors of Júlio Neves´s Pub.
All content of this page may be used under the terms of the Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.