Aqui temos um livro livre e completo sobre Shell

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

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

Pub Talk Part X

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.

     - Hey friend, I gave you a lot of easy, right? A simple exercise ...

     - But the tests I did and in accordance with what you taught about parameter substitution, I thought I should make other changes in the functions developed to make them for general use, as you told me that all functions should be, would you like to see?

     - Of course! If I asked you to do is because I'm so to see you learn, but wait, gimme a break!

     - Waiter! Sends two, one without foam!

     - Now show me what you did.

     - Well, beyond what you asked, I noticed that the program that called the function, would have previously defined the row that would be given the message and the number of columns. What I did was add two rows - in which employed parameter substitution - that if one of these variables were not informed, assign the function itself. The message row would be three rows above the end of the screen and the total of columns would be obtained by tput cols command. See how it fits:

$ cat question.func # The function receives 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 # put down in Message the value "Accept? (Y/n)" TotCols=${TotCols:-$(tput cols)} # If it was not defined, now it is LineMesg=${LineMesg:-$(($(tput lines)-3))} # Idem Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # To centralize Msg on row tput cup $LineMesg $Col read -n1 -p "$Msg " SN SN=${SN:-$2} # If empty puts default on SN 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

     - I liked, you already anticipated what I was going to ask. Just for we end this talk of substitution of parameters, note that the readability is awful, but the performance, this is, execution speed, is good. How functions are very personal things, as each uses their, and hardly give maintenance, I always opt for the performance.

     - Today we get out of that boredom that was our last talk and we will return to logic out of memorization, but I will remind you, all that I showed you last time here in the Pub is valid and helps, save those napkins we scribble that sooner or later, because will be very useful for you.

The eval command

     - I'll give you a problem that I doubt you solve:

$ var1=3 $ var2=var1

     - I gave you these two variables, and want you to tell me how I can, only referring to $var2, list the value of $var1 (3).

     - This is so easy, just do:

    echo $`echo $var2`

     - Notice that I put the echo $var2 between backticks (`), which thus has priority in execution and result var1, mounting echo$var1 will produce 3 ...

     - Will be? Then run to see if it is correct.

$ echo $`echo $var2` $var1

     - Huh! What happened? My reasoning seemed logical enough...

     - Your reasoning was logical really, the problem is that you forgot one of the first things I told you in this pub and I will repeat. The Shell uses the following order to solve a command line:

  • Resolve redirects;
  • Replaces the variables by their values;
  • Resolves and replaces the metacharacters;
  • Pass the already scrutinized every line for execution.

Thus, when we arrived in the resolution phase of variables, which is as I said before the execution, the only variable existing was $var2 and therefore your solution produced as output $var1. The command echo identified it like a chain and not a variable.

Problems of this type are relatively frequent and would be insoluble if not exist the instruction eval, whose syntax is:

    eval cmd

Where cmd is a command line any that you could even run direct at a terminal prompt. When you put the eval ahead, however, what happens is that Shell works the cmd as if their data were parameters of eval and then the eval executes the received line, submitting to the Shell, then giving in practice two passes in cmd.

This way if we ran the command you suggested putting the eval ahead, we would have expected output, see:

$ eval echo $`echo $var2` 3

This example could also have been done as follows:

$ eval echo \$$var2 3

In the first pass the backslash (\) would be removed and $var2 would be solved producing var1, for the second pass left would echo $var1, which would produce the expected result.

Now I put a command within of var2:

$ var2=ls

I'll run:

$ $var2 10porpag1.sh alo2.sh listmusic logaute.sh 10porpag2.sh confuso listartist sendmsg.func 10porpag3.sh contpal.sh listartist3 monbg.sh alo1.sh incusu logged

Now we put in var2 the following: ls$var1, and var1 we put l*, see:

$ var2='ls $var1' $ var1='l*' $ $var2 ls: $var1: No such file or directory $ eval $var2 listmusic listartist listartist3 logged logaute.sh

Again, at the time of replacement of variables, $var1 was not yet presented to the Shell to be resolved, in this way we can only run the command eval to give the two passed required.

Once a colleague of a excellent list on Shell Script, put a question: I wanted to make a menu that had numbered and list all files with extension .sh and when the operator chooses an option, the corresponding program would run. My proposal is as follows:

$ cat domenu #!/bin/bash # # List numbering programs with .sh in # current directory and executes the operator chooses # clear; i=1 printf "%11s\t%s\n\n" Option Program CASE='case $opt in' for arq in *.sh do printf "\t%03d\t%s\n" $i $arq CASE="$CASE "$(printf "%03d)\t %s;;" $i $arq) i=$((i+1)) done CASE="$CASE *) . erro;; esac" read -n3 -p "Inform the desired option: " opt echo eval "$CASE"

It looks complicated because I used much printf formatting for the screen, but it is very simple, we understand it: the first printf was placed to the header and then immediately I started dynamically assemble the variable $CASE, in which the end will be an eval to run the selected program. Note however that within the loop of the for there are two printf: the first is used to format the screen and the second to mount the case (before the read command if you put a row echo "$CASE", you'll see that the case command mounted inside the variable is all indented. Freshness, right? :). In for output, a row was added to the variable $CASE, for the case of making an invalid option, run an external function to give error messages.

Let's run it to see the output generated:

$ domenu.sh Option Program

001 10porpag1.sh 002 10porpag2.sh 003 10porpag3.sh 004 alo1.sh 005 alo2.sh 006 contpal.sh 007 domenu.sh 008 logaute.sh 009 monbg.sh 010 readpipe.sh 011 redirread.sh Inform the desired option:

In this program would be interesting to give an option of termination, and this would require the inclusion of a line after loop of mounting the screen and alter the row in which we make final attribution of the variable value $CASE. Let's see how it would look:

$ cat domenu #!/bin/bash # # List numbering programs with .sh in # current directory and executes the operator chooses # clear; i=1 printf "%11s\t%s\n\n" Option Program CASE='case $opt in' for arq in *.sh do printf "\t%03d\t%s\n" $i $arq CASE="$CASE "$(printf "%03d)\t %s;;" $i $arq) i=$((i+1)) done printf "\t%d\t%s\n\n" 999 "End of program" # Row included CASE="$CASE 999) exit;; # Row changed *) ./error;; esac" read -n3 -p "Inform the desired option: " opt echo eval "$CASE"

Signs Processes

Exists in Linux something called signal (signal). There are many signals that can be sent to (or generated by) running processes. We will from now on take a peek on the signals sent to the process and later we will take a quick glance at the signals generated by processes.

Signs killers

To send a signal to a process, we normally use kill command, whose syntax is:

    kill -sig PID

Where PID is the process identifier (Process IDentification or Process ID). Besides the kill command, some key sequences can also generate sig. The following table shows the most important signs to monitor:

Most Important Signs
 15   SIGTERM  When it receives a kill or kill -TERM 
Sinal  Generated by:
0 EXIT  Normal end of program
1 SIGHUP  When it receives a kill -HUP
2 SIGINT  The keyboard interrupt (<CTRL+C>)
3 SIGQUIT  Keyboard interrupt (<CTRL+\>)

Besides these signs, there is the notorious -9 or SIGKILL that for the process that is receiving, equates to put my finger on the off computer button which would be highly undesirable since many programs require "clear the midfield" at its end. If your ultimate occur as intended, in other words if you have a normal termination, it is very easy to do this cleaning, but if your program has an abrupt end a lot can occur:

  • It is possible that in a certain room of time, your computer is full of useless files work;
  • Your processor may jam the zombies and defuncts processes generated from processes children who lost their parents;
  • You must release opened sockets to not allow the frozen clients;
  • Your database may be corrupted because management systems databases require a time to record your buffers disk (commit).

Anyway, there are a thousand reasons not to use a kill to the sign -9 and monitor abnormal purposes programs.

The trap doesn't hurt

To make the monitoring described above exists trap command whose syntax is:

    trap "cmd1; cmd2; cmdn" S1 S2 ... SN


    trap 'cmd1; cmd2; cmdn' S1 S2 ... SN

Where commands cmd1, cmd2, cmdn will be executed if the program receives the signals S1 S2 ... SN.

The quotation marks (") or apostrophes (') are only needed if trap have more cmd command associated. Each cmd can also be a an internal function, a external or other script.

To understand the use of quotation marks (") and apostrophes (') we will use an example that is a fragment of a script that makes a ftp for a remote machine ($RemoComp), in which the user is $Guy, your password is $Secret and will transmit the file contained in $Arq. Suppose further that these four variables were received in a previous reading routine and that this script is widely used by many people to installation. Consider this bit of code:

ftp -ivn $RemoComp << FimFTP >> /tmp/$$ 2>> /tmp/$$
    user $Guy $Secret
    get $Arq

Note that the outputs of the dialogs ftp, as the errors found are being redirected to /tmp/$$, which is a fairly standard construction for temporary files used in scripts with more than one user, because $$ is the variable that contains the process number (PID), which is unique, and this type of construction is avoided that two or more users compete ownership and rights to the file.

If this ftp is interrupted by a kill or <CTRL+C>, certainly leave debris. This is precisely the way in which more you use the command trap. How is this excerpt from a script, we should, at the very beginning, as one of his first command, do:

    trap "rm -f /tmp/$$ ; exit" 0 1 2 3 15

This way, if there was a sudden interruption (signs 1, 2, 3 or 15) before the program terminate (in exit within the command trap), or a normal end (signal 0), the file /tmp/$$ would be removed.

If the command line trap there was no instruction exit, the end of execution of this line the program flow would return to the point where you were when you received the signal that caused the execution of this trap.

This trap could be subdivided, being as follows:

    trap "rm -f /tmp/$$" 0
    trap "exit" 1 2 3 15

Thus when get one of the signs the program would end, and when finished, would generate a signal 0, which would remove the file. If your end is normal, the signal will also be generated and rm runs.

Note also that the Shell search the command line once while the trap is interpreted (and that is why it is usual to put it at the beginning of the program) and again when one of the signals listed is received. So in the last example, the value of $$ will be replaced at the time the command = trap = was read the first time, since the quotes (") do not protect the dollar sign ($) the interpretation of Shell.

If you wish that replacement was performed only when the received signal, the command should be enclosed in apostrophes ('). Thus, the first interpretation of trap the Shell would not see the dollar sign ($), but the apostrophes (') would be removed and finally the Shell could replace the variable value. In this case, the row would be as follows:

    trap 'rm -f /tmp/$$ ; exit' 0 1 2 3 15

Suppose two cases: you have two scripts that we'll call script1, the first line will be a trap and script2, the latter being placed in the first run, and because they are two processes, will have two distinct PID.

  • 1st case: The ftp It's found in script1
    In this case, the command argument trap should be quoted (") because if it occurred an interruption (<CTRL+C> or <CTRL+\>) in the script2, the line would only be interpreted at this moment and the PID of script2 be different from those found in /tmp/$$ (don't forget that $$ is the variable that contains the PID of the active process);

  • 2st case: The ftp above It's found in script2
    In this case, the command argument trap should be quoted ('), because if the interruption occurred while running script1, the file wouldn't have been created, if it occurred during the execution of script2, the value of $$ would PID of this process, which would coincide with that of /tmp/$$.

The command trap when run without arguments, lists the signals being monitored in the environment, as well as the command line that will be executed when such signals are received.

If the command line trap is null (empty), this means that the specified signal should be ignored when received. For example, the command:

    trap "" 2

Specifies that the interrupt signal (<CTRL+C>) should be ignored. Thus in this case when you don't want your run is stopped. In the last example note that the first argument must be specified for the signal is ignored, and is not equivalent to write the following, whose purpose is to return the sign 2 to its default state (default):

    trap 2

If you ignore a signal, all Subshells will ignore this signal. So if you specify what action should be taken when receiving a signal, then all Subshells will also take action when they receive this signal, that is, the signals are automatically exported. For the signal we have shown (sign 2), this means that the Subshells are closed.

Suppose you run the command:

    trap "" 2

and then execute one Subshell, which will make another run script as a Subshell. If an interrupt signal is generated, this has no effect either on the main Shell nor on the "Subshell" called by him, since they all will ignore the signal.

Another way to restore a signal to its default is doing:

    trap - signal

In korn shell (ksh) there isn't -s option of the command read to read a password. What we usually do is to use the stty command with the echo option that inhibits the writing on the screen until you find one stty echo to restore this writing. So, if we were using the interpreter ksh, the reading the password would have to be made ​​as follows:

    echo -n "Password: "
    stty -echo
    read Password
    stty echo

The problem with this type of construction is that if the operator did not know the password, he probably would make a <CTRL+C> or a <CTRL+\> during the instruction read to discontinue the program, if he acted this way, whatever he wrote, did not appear on your terminal screen. To prevent this from happening, the best thing to do is:

    echo -n "Password: "
    trap "stty echo
          exit" 2 3
    stty -echo
    read Password
    stty echo
    trap 2 3

To end this subject, open a graphical console and write the following at the command prompt:

$ trap "echo Changed the window size" 28

Then take the mouse (arghh!!) And drag it in order to vary the size of the current window. Surprised? Is the Shell event oriented ... smile

More one simple because I couldn't resist. Now type this:

$ trap "echo it was" 17

Then do:

$ sleep 3 &

You just create a subshell which will sleep for three seconds in background. After this time, you will get the message it was, because the 17 is issued each time a subshell ends its execution.

To return these signals to their defaults, do:

$ trap 17 28


$ trap - 17 28

We just saw two signs that are not as important as we saw earlier, but I'll record them in the following table:

Not Very Important Signs
  28     SIGWINCH     Change in the size of the graphics window  
Signal  Generatedr:
  17     SIGCHLD     End of a son process  

Very cool this command, right? If you find some cool use case of signs, please let me know by e-mail because it is very rare literature about the subject.

Getopts command

The getopts command retrieves the options and their arguments from a list of parameters according to POSIX.2 syntax, that is, letters (or numbers) after a minus sign (-) followed or not an argument; in the case of only letters (or numbers) they can be grouped. You must use this command to "to slice" options and arguments passed to your script.


    getopts optionschain name

The optionschain must specify a character string with all the options recognized by script, so if it recognizes the options -a b and -c, optionschain must abc. If you want an option is followed by an argument, put a colon (:) after the letter, as in a:bc. This tells to the getopts the option -a has the form:

    -a argument

Normally one or more white spaces separate the option parameter; however getopts also manipulates parameters that come attached to the option as in:


optionschain can not contain interrogation (?).

The name constant syntax line above, defines a variable each time the command getopts runs, will receive the next option of the positional parameters and place it in the variable name.

getopts puts a question mark (?) in variable defined in name if find an option undefined in optionschain or if didn't find the expected argument for a given option.

As we already know, every last option for a command line has a numeric index, so the first option is contained in $1, the second in $2, and so on. When getopts gets an option, it stores the index of the next parameter to be processed in the variable OPTIND.

When an option has an associated argument (indicated by : in the optionschain) getopts stores the argument in the variable OPTARG. If an option has no argument or the expected argument is not found, the variable OPTARG be "killed" (unset).

The command terminates its execution when:

  • Find a parameter that does not start for minus (-);
  • The special parameter - marks the end of the options;
  • When it finds an error (for example, an unrecognized option).

The example below is merely didactic, serving to show, in a small fragment code full use of the command.

$ cat getoptst.sh #!/bin/sh

# Run so: # # getoptst.sh -h -Pprinter arq1 arq2 # # and note that the information of all options are displayed # # The chain 'P:h' says the option -P is a complex option # and requires an argument, and that h is a simple option that does not require # arguments.

while getopts 'P:h' OPT_LETTER do echo "getopts made the variable OPT_LETTER equal the '$OPT_LETTER'" echo " OPTARG is '$OPTARG'" done used_up=`expr $OPTIND - 1` echo "Dispensing the first \$OPTIND-1 = $used_up arguments" shift $used_up echo "What remained the command line was '$*'"

To understand it better, we'll run it as is suggested in your header:

$ getoptst.sh -h -Pprinter arq1 arq2 getopts made the variable OPT_LETTER equal the 'h' OPTARG is '' getopts made the variable OPT_LETTER equal the 'P' OPTARG is 'printer' Dispensing the first $OPTIND-1 = 2 arguments What remained the command line was 'arq1 arq2'

This way, without too much hassle, I separated all the options with their respective arguments, leaving only the parameters that were passed by the operator for subsequent treatment.

Notice that if we had written the command line with the argument (printer) separated from (-P) option, the result would be exactly the same, except $OPTIND, since in this case it identifies a set of three options/arguments and only the previous two. Check it:

$ getoptst.sh -h -P printer arq1 arq2 getopts made the variable OPT_LETRA equal the 'h' OPTARG is '' getopts made the variable OPT_LETTER equal the 'P' OPTARG is 'printer' Dispensing the first $OPTIND-1 = 3 arguments What remained the command line was 'arq1 arq2'

Notice at the following example, that if we pass an invalid option, the $OPT_LETTER variable will get a question mark (?) and the $OPTARG will be "erased" (unset).

$ getoptst.sh -f -Pprinter arq1 arq2 # The -f option is not valid ./getoptst.sh: illegal option -- f getopts made the variable OPT_LETTER equal the '?' OPTARG is '' getopts made the variable OPT_LETTER equal the 'P' OPTARG is 'printer' Dispensing the first $OPTIND-1 = 2 arguments What remained the command line was 'arq1 arq2'

     - Tell me something: you couldn't have used a case to avoid getopts?

     - Could do, but for what? The commands are there to be used ... The example given was didactic, but imagine a program that accepts many options and their parameters could or could not be attached to options, your options could also be united or not, would be a case infernal and with getopts just follow the steps above

     - It is... Seeing this way I think you're right. It's because I'm getting tired with so much new information in my head. Let's have a nightcap or you still want to explain some peculiarity of the Shell?

     - Neither of them, I have also tired but today I will not have a nightcap because I'm going to teach in UNIRIO, which was the first federal university that is preparing the use of Free Software, your students of the degree course in computer.

But before I'll let you think of a problem for you: when you vary the size of a screen, at its center not dynamically appear in reverse video the amount of rows and columns? So! I want you to play it using the Shell language.

     - Waiter, quickly bring the bill! I'll count to one and if you do not bring I'm leaving!

Any doubt or lack of companionship for a beer or even to speak ill of politicians just send an email to of 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.