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:

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.

Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki-SL?
Send feedback