Aqui temos um livro livre e completo sobre Shell

Os sedentos do "saber livre" são muito benvindos.

Você está aqui: TWikiBar > TWikiBarTalk006
Controles: EDITAR ANEXAR MAIS MAIS ALTERACOES IMPRIMIR - Última Atualização: [25 Dec 2017 - V.20]

Pub Talk Part VI

This is a very new translation from Portuguese to English. Please contribute to the development of this site indicating errors and and/or suggesting corrections and materials for this person.

Loop commands (Continuing)

     - Hey dude! What's up? Are you all familiar with the for command yet? I gave you some homework for you to get acquainted with it. Did you do it?

     - Off course, man! I'm all excited about this new language and I did just what you asked me. I mean, without using the command wc, or it would be just too easy. Take lo...

     - Hold your horses, pal. You may be hooked to that language, but I'm really thirst! I need a beer. Hey, waiter... bring me two, please!

     - Anyway, like I was saying... take a look how I did it. Really easy...

$ cat contpal.sh #!/bin/bash # Example script # It's going to count the number of words # inside a file. We assume the words # are separated by spaces, <TAB> ou <ENTER>. # if [ $# -ne 1 ] then echo usage: $0 /file/path exit 2 fi Cont=0 for Word in $(cat $1) do Cont=$((Cont+1)) done echo File $1 contains $Cont words.

That means the program will always start by checking if the parameters are correct, then will use the for command to count every word (remeber that the standard $IFS is space, <TAB> and <ENTER>, which is exactly how our words are separated by), adding to the $Cont variable.

Lets check again the file ArqDoDOS.txt.

$ cat ArqDoDOS.txt This file was created by DOS/Windows and downloaded by a badly implemented ftp.

Let's try our program again, having this file as parameter:

$ contpal.sh ArqDoDOS.txt File ArqDoDOS.txt contains 13 words.

     - Nice! Works like a charm.

A little bit more of for and Math

Back to business. Last time we were here we finished our talk showing the following for loop:

    for ((; i<=9;))
        let i++
        echo -n "$i "

Once we get here, I think I should let you know that the Shell has the Arithmetic Expansion concept, which I'll give just a brief overview, since you can find an in depth coverage on the appetizer section.

The arithmetic expansion will be triggered by a construction like:



     let expression

On the last for we talked about I used both forms of it, but we can't go ahead without knowing that the expression can be one of the following:

Arithmetic Expansion
||   bitwise OR
  Expression     Result  
id++ id--   variable post-increment and post-decrement
++id -–id   variable pre-increment and pre-decrement
**   Exponentiation
* / %  multiplication, division, remainder
+ -   unary minus and plus
<= >= < >   comparison operators
== !=   equality and inequality
&&   bitwise AND

     - Wait. You think that our talk about loop is over with the for command? Silly mistake, buddy. We still have two others to check out.

The while command

Every programmer knows this command, since it's found on every language and usually a bunch of commands are executed while a certain condition is true. Therefore the name. Meeh... That only happens on those boring languages. On Shell programming a bunch of commands are executed while a command is true. And, of course, if you want to check if a condition is true you can do it by using while with test, exactly how you learnt back during our if talk. Remember?

So, the syntax will look like this:

    while command

So the command block made witht he instructions cmd1, cmd2,... and cmdn will run while the execution of the command is successful.

Think about the following scenario: I have this hot babe waiting for me, but I can't leave work since my prick boss (kinda redundant, I know) is still working and his office is on my way out.

I think he got suspicious after the fifth time I walked to the restroom to check if he was still there... So I had to go back to my desk, connect to the server and create the following script:

$ cat logout.sh #!/bin/bash

while who | grep boss do sleep 30 done echo The prick is gonne. Don't hesitate, exit.

This tiny script uses the while command to test the pipeline returned by the who and grep commands, which will be true while grep is able to locate the word boss on who 's return. So the script will sleep for 30 seconds while the boss is still there (Argh!). As soon as he disconnects from the server, the script flow will leave the loop and yield the so much wanted freedom message.

So, when I ran it, guess what happened?

$ logout.sh boss pts/0 Jan 4 08:46 ( boss pts/0 Jan 4 08:47 ( ... boss pts/0 Jan 4 08:52 (

That means every 30 seconds it would send on my screen the grep command result, which was not nice, since it would pollute my screen and the expected freedom message could go unnoticed. Fortunately we know how to avoid it by redirecting the pipeline message to /dev/null (another way to do the same, is using the -q (quiet) grep option. But this works only in GNU grep, not in UNIX).

$ cat logout.sh #!/bin/bash

while who | grep boss > /dev/null do sleep 30 done echo The prick is gonne. Don't hesitate, exit.

Now I want to put together a script that can take the name (and any other needed parameter) of a program which will run in background and let me know when it's finished. But before that, so you undestand the examples, I have to show you a new system variable. Take a look at this commands from the prompt:

$ sleep 10& [1] 16317 $ echo $! 16317 [1]+ Done sleep 10 $ echo $! 16317

It means I created a background process which would sleep 10 seconds so I could show you that the variable $! saves the PID (Process IDentification) of the last background running process. But notice that after the line done that the variable kept it's value even after the process is finished.

Well, with that in mind it's easier to monitor any background process. Take a look how:

$ cat monbg.sh #!/bin/bash

# Run and monitors a # background process

$1 & # Puts in backgroud while ps | grep -q $! do sleep 5 done echo Process $1 is finished

This script is very similar to the previous ones, with a couple extra tricks: It must be run in background so it won't hold the prompt, but the $! will contain the PID of the program parsed as a parameter, since it was put in background after the monbg.sh itselfs. You may also notice that we are running grep with the option -q (quiet). You could obtain the same result running while ps|grep $! >/dev/null, as we've been using until now.

Pinguim com placa de dica (em inglês)  

Don't forget: Bash provides you the system variable $! which contains the PID (Process IDentification) of the last background running process.

Let's improve the musinc, which is our program to include records in the file music, but first I need to teach you to catch a data of the screen, and I'm warning: I'll just give a little hint of the read command (which is who gets the data of the screen) it's enough to solve this our problem. In another round of beer I'll teach you all about it, including how to format screen, but today we are talking about loops.

The command syntax to read what interests us today is:

$ read -p "prompt of read" var

Where prompt of read is the text you want to appear written on the screen, and when the operator keying the data, it will go to the variable var. For example:

$ read -p "Album Title: " Tit

Well, once you understand this, let the specification of our problem: we do a program that initially read the album name and then will make a loop reading, catching the music and the artist. This loop terminates when an empty music is informed, this is, when asked to typing of music, the operator gives a simple <ENTER>. To make life easier for the operator, we offer as default the same artist name of previous music (since it is normal that the album is all the same artist) until he wishes to change it. Let's see how it fits:

$ cat musinc #!/bin/bash # Registers CDs (version 4) # clear read -p "Album Title: " Tit [ "$Tit" ] || exit 1 # End of execution if empty title if grep "^$Tit\^" musics > /dev/null then echo This album is already registered exit 1 fi Reg="$Tit^" Cont=1 oArt= while true do echo Track data $Cont: read -p "Music: " Mus [ "$Mus" ] || break # Exit if empty read -p "Artist: $oArt // " Art [ "$Art" ] && oArt="$Art" # If empty Art previous Reg="$Reg$oArt~$Mus:" # Mounting registry Cont=$((Cont + 1)) # The above line could also be ((Cont++)) or (better) let Cont++ done echo "$Reg" >> musics sort musics -o musics

This example starts with reading the album title, that is not informed, terminate program execution. Then one grep demand in the beginning (^) of each record musics, the title entered followed by the separator (^) (which is preceded by a backslash (\) to protect it from interpretation of Shell).

To read the names of the artists and the musics of the album, it set up a loop of while simple, whose only highlight is the fact of storing the artist of the previous song in the variable $oArt which will only its content changed, when some data is entered for the variable $Art, this is, when not typed up a simple <ENTER> to maintain the previous artist.

What has been seen so far on the while was very little. This command is very used mainly for reading files, but we lack luggage to proceed. After learning to read, we'll see this statement further.

Pinguim com placa de dica (em inglês)  

File reading means reading one-on-one all records, which is always a slow operation. Be careful not to use the while when its use is unnecessary. The Shell have tools like sed and family grep which scour files optimally that crawl files optimally without requiring the use of commands loop to do it record by record (or even word by word).

The until command

The until command works exactly the same as while, however unlike. He said all but said nothing, right? It is this: both test commands, both have the same syntax and both act in loop, but while while executes the block of instructions loop while a command is successful, the until executes the block of loop until the command to be successful. Seems little but the difference is fundamental.

The command syntax is almost identical to the while. See:

    until command

And so the command block formed by instructions cmd1, cmd2,... and cmdn runs until the instruction execution command be successful.

As I told you, while and until function antagonistically and this is very easy to demonstrate: in a war where you invent a gun, the enemy seeks a solution to neutralize it. Based on this principle bellicose my boss developed on the same server I ran the logaute.sh an script to control my arrival time.

One day the network has a problem, he asked me to take a look at his micro and left me alone in his room. I immediately began snooping around your files - because war is war - and look what I found:

$cat arrival.sh #!/bin/bash

until who | grep julio do sleep 30 done echo $(date "+ Em %d/%m às %H:%Mh") >> relapse.log

Look at that bastard! The guy was riding a log with the times I came, and on top called the file that monitored me relapse.log! What does he mean by that?

In this script the pipeline who | grep julio, will be successful only when julio is found in the command who, this is when I "log" on the server. Until that happens, the command sleep that forms the block of instructions until put the program on hold for 30 seconds. When this loop close, will be given a message to relapse.log (ARGHH!). Assuming that on 20/01 I logged in at 11:23 hours, the message would be as follows:

     On Jan 20 at 11:23 h

When we registering musics, the ideal would be that we could register several CDs. In the last version we did of "= musinc =" this does not happen, every CD that register the program ends. Let's see how to improve it:

$ cat musinc #!/bin/bash # Register CDs (version 5) # For= until [ "$For" ] do clear read -p "Album Title: " Tit if [ ! "$Tit" ] # If empty title... then For=1 # I turned on flag output else if grep "^$Tit\^" musics > /dev/null then echo This album is already registered exit 1 fi Reg="$Tit^" Cont=1 oArt= while [ "$Tit" ] do echo Track data $Cont: read -p "Music: " Mus [ "$Mus" ] || break # Exit if empty read -p "Artist: $oArt // " Art [ "$Art" ] && oArt="$Art" # If empty Art previous Reg="$Reg$oArt~$Mus:" # Mounting registry Cont=$((Cont + 1)) # The above line could also be ((Cont++)) done echo "$Reg" >> musics sort musics -o musics fi done

In this version, a larger loop was added before reading the title, which will end only when the variable $For stop being empty. If the album title is not given, the variable $For will receive value (in case I put 1 but could have put anything. Importantly, it is not empty) to exit this loop ending this way the program. In the rest, the script is identical to its earlier version.

Shortcuts in the loop

Not always a program cycle, between a do and done, comes out the front door. On some occasions, we have put a command to abort a controlled manner this loop. Inversely, sometimes we want the flow of program execution back before getting to done. For this, we respectively commands break (we've seen rapidly the examples of comado while) and continue, which work as follows:

What I had not previously mentioned is that in its generic syntax they appear as follows:

     break [qtd loop]


     continue [qtd loop]

Where qtd loop represents the amount of the internal loops on which the commands will act. Your default value is 1.


I doubt you've ever deleted a file and soon after took a slap on the forehead cursing because it should not have removed it. Well, the tenth time I did this bullshit, I created a script to simulate a recycling bin, this is when I send remove one (or more) files, the program "pretends" that removed it, but actually it did was send it to /tmp/LoginName directory. I called this program erreeme and in /etc/profile I put the following line:

     alias rm=erreeme

The program was well:

$ cat erreeme #/bin/bash # # Saving file copy before removing it #

if [ $# -eq 0 ] # You must have one or more files to remove then echo "Error -> Use: erreeme arq [arq] ... [arq]" echo " The use of metacharacters is allowed. Ex. erreeme arq*" exit 1 fi

MyDir="/tmp/$LOGNAME" # Variable system. Contains the user name. if [ ! -d $MyDir ] # If my directory does not exist under the /tmp... then mkdir $MyDir # I will create it fi

if [ ! -w $MyDir ] # If I can not write to the directory... then echo Impossible to save files in $MyDir. Change permission... exit 2 fi

Error=0 # Variable to indicate the return code of the program for Arq # For without the "in" receives past parameters do if [ ! -f $Arq ] # If this file does not exist... then echo $Arq doesn't exist. Error=3 continue # Back to the for command fi

DirOrig=`dirname $Arq` # Cmd. dirname informs the directory name of $Arq if [ ! -w $DirOrig ] # Checks write permission in the directory then echo No permission to remove the directory of $Arq Error=4 continue # Back to the for command fi

if [ "$DirOrig" = "$MyDir" ] # If I am "emptying the recycle bin"... then echo $Arq will be without backup rm -i $Arq # Question before removing [ -f $Arq ] || echo $Arq removed # Does the user removed? continue fi

cd $DirOrig # I save at end of file your directory pwd >> $Arq # original to use it in a script undelete mv $Arq $MyDir # saved and remove echo $Arq removed done exit $Error # Step eventual error number for the return code

As you can see, most of the script is formed by small criticisms to parameters informed, but as script may have received multiple files to remove, every file that does not fit within the specified, there is a continue to the sequence back to the loop of for in order to receive other files.

When you are in Windows (excuse the bad word) and try to remove that pile of garbage with weird names like HD04TG.TMP if error happen in one of them, others are not removed, does not it? So continue was used to prevent such an oath to occur, this is, even if it gives error in the removal of a file, the program will continue removing others that were passed.

     - I think by now you must be curious to see the program that restores the file removed, isn't it? For then there will go a challenge: make it at home and bring me to discuss at our next meeting here at the pub .

     - Damn, but this I think I'll dance, do not even know how to start ...

     - Dude, this program is like everything we do at Shell, extremely easy, is to be done in at most 10 lines. Don't forget that the file is saved in /tmp/$LOGNAME and its last line is the directory in which it resided before being "removed". Also be sure to criticize it was the last name of the file to be removed.

     - Yeah I'll try, but don't know ...

     - Have faith brother, I'm telling you it's easy! Any questions just give me an e-mail julio.neves@gmail.com. Now comes the chat I'm already dry throats so much talk. Join me in the next beer or will now rush out to do the script that I spent?

     - Let me think a little ...

     - Waiter, bring another beer while he thinks!

Any doubt or lack of companionship for a beer or even to speak ill of politicians just send an email to me.



-- PauloSantana - 25 Dec 2017

Licença Creative Commons - Atribuição e Não Comercial (CC) 2018 Pelos Frequentadores do Bar do Júlio Neves.
Todo o conteúdo desta página pode ser utilizado segundo os termos da Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.