\

Aqui temos um livro livre e completo sobre Shell

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

Você está aqui: TWikiBar > TWikiBarTalk008
Controles: EDITAR ANEXAR MAIS MAIS ALTERACOES IMPRIMIR - Última Atualização: [18 Mar 2014 - V.16]

Pub Talk Part VIII



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.

     - Hello dude! Is everything okay??

     - Cool! I wanted to show you what I did but I know you will want to go fast to what matters right?

     - Just to counteract, today I'll leave you first show your work. Show me what you did.

     - Ahh ... The exercise you gave is very large. See how I solved:

$ cat musinc5 #!/bin/bash # Registers CDs (version 5) # clear LineMesg=$((`tput lines` - 3)) # Row that defines how many messages will be given to operator TotCols=$(tput cols) # Number of columns of screen to frame messages echo "   Inclusion of Musics     ========= == ======       Album Title:   | This field was   Track: < created only for   | guide the fill   Music Title:     Interpreter:" # Screen mounted with a single echo while true do tput cup 5 38; tput el # Positions and clean row read Album [ ! "$Album" ] && # Operator gave <ENTER> { Msg="Do you wish to finish? (Y/n)"         TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" tput cup $LineMesg $((Col + TamMsg + 1)) read -n1 SN tput cup $LineMesg $Col; tput el # Deletes the message screen [ $SN = "N" -o $SN = "n" ] && continue # $SN is equal to N or (-o) n? clear; exit # End of execution } grep "^$Album\^" musics > /dev/null && { Msg="This album is already registered"         TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" read -n1 tput cup $LineMesg $Col; tput el # Deletes the message screen continue # Back to read another album } Reg="$Album^" # $Reg will receive the data for recording theArtist= # Variable that saves the previous artist while true do ((Track++)) tput cup 7 38 echo $Track tput cup 9 38 # Positions to read music read Music [ "$Music" ] || # If the operator has given <ENTER>... { Msg="End of Album? (Y/n)"             TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" tput cup $LineMesg $((Col + TamMsg + 1) read -n1 SN tput cup $LineMesg $Col; tput el # Deletes the message screen [ "$SN" = N -o "$SN" = n ] && continue # $SN is equal to N or (-o) n? break # Exits the loop to record } tput cup 11 38 # Positions to read Artist [ "$theArtist" ]&& echo -n "($theArtist) " # Previous Artist is default read Artist [ "$Artist" ] && theArtist="$Artist" Reg="$Reg$theArtist~$Music:" # Mounting registry tput cup 9 38; tput el # Deletes the Music of the screen tput cup 11 38; tput el # Deletes Artist of the screen done echo "$Reg" >> musics # Writes registry in end of file sort musics -0 musics # Sorts the file done

     - The program is cool, it's structured, but I will mention a few comments about what you did:

  • Just to remind, the following constructs: [ ! $Album] && and [ $Music ] || represent the same thing, that is, in the first case, we tested if the variable $Album not (!) have nothing inside, then (&&) ... In the second, we tested if $Music has given, if not (||) ...
  • If you complained about the size of it, it's because I still did not give some tips. Note that most of the script is to provide messages centered on the penultimate row of the screen. Notice also that some messages asking for a Y or an N and others are only warning. This would be the typical case of the use of functions, which would be written only once and called the execution of several points in the script. I'll mount two functions to solve these cases and we will incorporate them into your program to see the end result.

Functions

     - Waiter! Gimme two beers to give me inspiration, but please, not too much head.

   Question ()
        {
        #  The function takes 3 parameters in the following order:
        #  $1 - Message to be given on the screen
        #  $2 - Value to be accepted with default response
        #  $3 - The other accepted value
        #  Supposing that $1=Accept?, $2=y e $3=n, the row,
        #  then put the value in message "Accept? (Y/n)"
        local Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)"
        local TamMsg=${#Msg}
        local Col=$(((TotCols - TamMsg) / 2))  # Centralizes message on row
        tput cup $LineMesg $Col
        echo "$Msg"
        tput cup $LineMesg $((Col + TamMsg + 1))
        read -n1 SN
        [ ! $SN ] && SN=$2                     # If empty puts default on SN
        echo $SN | tr A-Z a-z                  # The output of SN will be in lowercase
        tput cup $LineMesg $Col; tput el      # Deletes the message screen
        return                                 # Exits the function.
        }

As we can see, a function is defined when we make function_name () and your whole body is in braces ({}). Just as we talked in the pub about parameter passing, the functions receive the same way, i.e. are positional parameters ($1, $2, ..., $n) and all the rules applying to passing parameters for programs, also apply to functions, but it is very important to note that the parameters passed for a program should not be confused with those who the program passed to their functions. This means, for example, that $1 of a script it's different from $1 one of it's functions.

Note that the variables $Msg, $TamMsg and $Col are restricted to use this routine, and so were created as local. The purpose of this is simply to save memory, because when exiting the routine, they will be properly detonated of the partition and if it had not used this gimmick, residents remain.

The line of code that creates local Msg concatenates the received text ($1) a left parentheses, the default response ($2) in uppercase, one bar, the other answer ($3) in lowercase and finalizes with the closing parenthesis. I use this convention, at the same time, to show the available options and highlight the response offered by default.

Almost at the end of the routine, the answer received ($SN) is passed to lower case so that the body of the program doesn't need to do this test.

Watch how the function would be to give a message on the screen:

    function SendMsg
        {
        # The function takes only one parameter
        # with the message that you want to display,
        # to not force the programmer to pass
        # the message in quotes, we'll use $* (all
        # parameters, remember?) and not $1.
        local Msg="$*"
        local TamMsg=${#Msg}
        local Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row
        tput cup $LineMesg $Col
        echo "$Msg"
        read -n1
        tput cup $LineMesg $Col; tput el     # Deletes the message screen
        return                                # Exits the function.
        }

This is another way to define a function: not to call as in the previous example using a construction with the syntax function_name (), but as function function_name. For the rest, nothing differs from the previous one, except that, as stated in the comments, use the variable $* which as we know is the set of all of the parameters, so that the programmer does not need to use quotation marks surrounding the message you want pass to the function.

To finish with this blah blah blah then we'll see the changes that the program needs when we use the concept of functions:

$ cat musinc6 #!/bin/bash # Registers CDs (version 6) #

# Area of ​​global variables LineMesg=$((`tput lines` - 3)) # Line messages will be given to operator TotCols=$(tput cols) # Number of columns of screen to frame messages

# Function Area Question () { # The function takes 3 parameters in the following order: # $1 - Message to be given on the screen # $2 - Value to be accepted with default response # $3 - The other accepted value # Supposing that $1=Accept?, $2=y e $3=n, the row # then put the value in message "Accept? (Y/n)" local Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" local TamMsg=${#Msg} local Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" tput cup $LineMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # If empty puts default on SN echo $SN | tr A-Z a-z # The output of SN will be in lowercase tput cup $LinhaMesg $Col; tput el # Deletes the message screen return # Exits the function } function SendMsg { # The function takes only one parameter # with the message that you want to display, # to not force the programmer to pass # the message in quotes, we'll use $* (all # parameters, remember?) and not $1. local Msg="$*" local TamMsg=${#Msg} local Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" read -n1 tput cup $LineMesg $Col; tput el # Deletes the message screen return # Exits the function }

# The body of the actual program starts here clear echo " Inclusion of Musics     ========= == ======       Album Title:   | This field was   Track: < created only for   | guide the fill   Music Title:     Interpreter:" # Screen mounted with a single echo while true do tput cup 5 38; tput el # Positions and clean row read Album [ ! "$Album" ] && # Operator has hit <ENTER> { Question "Do you wish to finish" y n [ $SN = "n" ] && continue # Now only text to lowercase clear; exit # End of execution } grep -iq "^$Album\^" musics 2> /dev/null && {     SendMsg This album is already registered continue # Back to read another album } Reg="$Album^" # $Reg will receive the data recording theArtist= # Will guard previous artist while true do ((Track++)) tput cup 7 38 echo $Track tput cup 9 38 # Positions to read music read Music [ "$Music" ] || # If the operator has hit <ENTER>... { Question "End of Album?" y n [ "$SN" = n ] && continue # Now only text to lowercase break # Exit the loop to write data } tput cup 11 38 # Positions to read Artist [ "$theArtist" ]&& echo -n "($theArtist) " # Previous Artist is default read Artist [ "$Artist" ] && theArtist="$Artist" Reg="$Reg$theArtist~$Music:" # Mounting registry tput cup 9 38; tput el # Deletes the Music of the screen tput cup 11 38; tput el # Deletes the Artist of the screen done echo "$Reg" >> musics # Writes registry in end of file sort musics -o musics # Sorts the file done

Note that the structure of the script is shown in the graph below:

  Body of Program  
Global Variables
Functions

This structure is due to Shell being an interpreted language and thus the program is read from left to right and from top to bottom and a variable to be viewed simultaneously by script and its functions should be declared (and initialized) before anything. The functions in turn must be declared before the body of the program itself because at the point where the programmer mentioned his name, the interpreter Shell had already located and registered before it was a function.

One nice thing is the use of functions to make them as generic as possible so that they serve to other applications without the need to be rewritten. These two we just have to see generalized use because it's difficult for a script that has a data entry keyboard that doesn't use a routine type of Sendmsg or doesn't interact with the operator for something similar to Question.

Friendly advice: create a file and each new function you create, attach it to this file. At the end of a time you will have a nice library of functions which will save you a lot of time programming.

The source command

See if you notice something different in output ls below:

$ ls -la .bash_profile -rw-r--r-- 1 Julio unknown 4511 Mar 18 17:45 .bash_profile

Don't look at the answer, re-watch! Well, since you're no mood to think and prefer to read the answer, I'll give you a hint: think you know the .bash_profile is one of the programs that are automatically "executed" when you log (ARRGGHH! I hate this term).. Now that I gave you this tip look again at the output of ls and tell me what's different about her.

As I said the .bash_profile is "executed" in logon time and note that it has no right to run. This happens because if you execute like any other script grimace, when finished it's run around the environment generated by it die along with Shell under which it was executed (remember that all scripts are executed in subshells, right ?).

Well, it's for things like which exists the source command, also known as . (point). This command makes it not created a new Shell (a subshell) to run the program that it's passed as a parameter.

Better an example that 453 words. Watch this short script below:

$ cat short_script cd .. ls

He should just go to the directory above the current directory. Let's run a few commands involving short_script and we will analyze the results:

$ pwd /home/jneves $ short_script jneves juliana paula silvie $ pwd /home/jneves

If I sent it go up a directory, why not come up? Went up yes! The subshell which was created to run the script so much climbed which listed the directories of four users below /home, only once the script finished, the subshell was lost and with it the whole environment created. Now it looks like the thing changes:

$ source short_script jneves juliana paula silvie $ pwd /home $ cd - /home/jneves $ . short_script jneves juliana paula silvie $ pwd /home

Ahh! Now yes! Being passed as a parameter of source command or . (point), the script has been executed in the current shell leaving this, whole environment created. Now we give a rewind to the beginning of the explanation of this command. There we talked about .bash_profile, and by now you should already know that their duty is, right after login, prepared to leave the working environment for the user, and now we understand that is so even though it runs using this trick.

And now you must be asking yourself if this command does just that, and I tell you yes but it brings a lot of advantages and one of the most used is to treat functions as external routines. See another way to make our program to include CDs in file musics:

$ cat musinc7 #!/bin/bash # Registers CDs (version 7) #

# Area of ​​global variables LineMesg=$((`tput lines` - 3)) # Line messages will be given to operator TotCols=$(tput cols) # Number of columns of screen to frame messages

# The body of the actual program starts here clear echo " Adding Musics     ====== ======       Album Title:   | This field was   Track: < created only for   | guide the fill   Music Title:     Interpreter:" # Screen mounted with a single echo while true do tput cup 5 38; tput el # Positions and clean row read Album [ ! "$Album" ] && # Operator gave { source question.func "Do you wish to finish" y n [ $SN = "n" ] && continue # Now only text to lowercase clear; exit # End of execution } grep -iq "^$Album\^" musics 2> /dev/null && { . sendmsg.func This album is already registered continue # Back to read another album } Reg="$Album^" # $Reg will receive the data recording theArtist= # Will guard previous artist while true do ((Track++)) tput cup 7 38 echo $Track tput cup 9 38 # Positions to read music read Music [ "$Music" ] || # If the operator has given ... { . question.func "End of Album?" y n [ "$SN" = n ] && continue # Now only text to lowercase break # Exit the loop to write data } tput cup 11 38 # Positions to read Artist [ "$theArtist" ] && echo -n "($theArtist) " # Previous Artist is default read Artist [ "$Artist" ] && theArtist="$Artist" Reg="$Reg$theArtist~$Music:" # Mounting registry tput cup 9 38; tput el # Deletes the Music of the screen tput cup 11 38; tput el # Deletes the Artist of the screen done echo "$Reg" >> musics # Writes registry in end of file sort musics -o musics # Sorts the file done

Now the program became smaller and function calls were exchanged for external files called question.func and sendmsg.func, which can thus be called by any other program, thus reusing your code.

For didactic reasons the executions of question.func and sendmsg.func are being commanded by source and . (point) indiscriminately, but prefers the source to be more visible thus giving greater legibility code and facilitating their subsequent maintenance.

Watch how were these two files:

$ cat question.func # The function takes 3 parameters in the following order: # $1 - Message to be given on the screen # $2 - Value to be accepted with default response # $3 - The other accepted value # Supposing that $1=Accept?, $2=y e $3=n, the row # then put the value in message "Accept? (Y/n)" Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" tput cup $LineMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # If empty puts default on SN echo $SN | tr A-Z a-z # The output of SN will be in lowercase tput cup $LineMesg $Col; tput el #Deletes the message screen $ cat sendmsg.func # The function takes only one parameter # with the message that you want to display, # to not force the programmer to pass # the message in quotes, we'll use $* (all # parameters, remember?) and not $1. Msg="$*" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centralizes message on row tput cup $LineMesg $Col echo "$Msg" read -n1 tput cup $LineMesg $Col; tput el # Deletes the message screen

In both files, I only made two changes that we will see in the comments below, but I have three more to do:

  1. The variables aren't more being declared as local, because it is a directive that can only be used in body functions and therefore these variables remain in the Shell environment, polluting it;
  2. The return command isn't longer present but could be without altering the logic, since it would only serve to indicate any error via a return code
predetermined (e.g. return 1, return 2, ...), where the return and return 0 are identical and represent routine executed without error;
  1. The command that we're accustomed to use to generate the return code is the exit, but the output of an external routine can not be done this way because to be running on the same Shell that the caller script, the exit simply would end this Shell, ending the execution of the entire script;
  2. Where did the LineMesg variable? It came from musinc7 because it had been declared before the call routines (never forgetting that Shell is interpreting the script and these routines is the same);
  3. If you decide to use external routines, do not be intimidated, concentrate in the comments (mostly about passing parameters) to facilitate the maintenance and use by other programs in the future.

     - Well, now you have a lot more of new scripts to improve what we did. Do you remember the program listartist in which you passed the name of an artist as a parameter and he returned their music? It was like:

$ cat listartist #!/bin/bash # Entered an artist, shows his musics # version 2

if [ $# -eq 0 ] then echo You should have passed at least one parameter exit 1 fi

IFS=" :" for ArtMus in $(cut -f2 -d^ musics) do echo "$ArtMus" | grep -i "^$*~" > /dev/null && echo $ArtMus | cut -f2 -d~ done

     - Of course I remember!...

     - So to establish the concepts that I gave you, do it with the formatted screen, in loop, such that it only ends when you receive a pure <ENTER> the artist's name. Ahhh! When the list reaches the penultimate line of the screen, the program should take a break for operator to can read them, that is, suppose that the screen has 25 lines. Every 22 songs listed (number of rows minus 3) the program waits for the operator to press something then proceed. Any error messages should be passed to routine sendmsg.func we just developed.

     - Waiter, send two, mine is low foam ...

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

Thanks!


Licença Creative Commons - Atribuição e Não Comercial (CC) 2017 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.