CI066 - Oficina de Programação
Notas de Aula # 10

Prof. Armando Luiz N. Delgado

1 Programação Shell - estruturas de controle: condicionais e laços

1.1 Scripts shell e processos

Todo processo em UNIX é iniciado a partir do shell. Da mesma forma acontece com scripts shell.

O mecanismo que é usado por UNIX, denominado fork, está esquematizado na Figura 1:

Figura 1: Scripts shell e processos.
\includegraphics[scale=0.8]{shellfork}

Chamamos de subshell ao processo iniciado como filho de uma sessão shell. Como se vê na figura, todo script shell é executado em um subshell.

A noção de subshell é importante para o tratamento de variáveis de ambiente, como se verá em aulas posteriores.

Todos os processos originados a partir de um processo P qualquer são denominados filhos do processo P. Reversamente, o processo P é chamado de pai dos seus subprocessos. Quando a opção l de ps(1) é usada (sozinha ou em conjunto com outras opções) são apresentados os dois principais identificadores de um processo: o PID (Process ID) e o PPID (Parent Process ID - PID do processo pai).

OBS.: $\textstyle \parbox{10cm}{Quando se mata um processo qualquer,
duas coisas pode...
...rio
deve usar \command{kill} explicitamente para cada processo.
\end{itemize}}$

1.2 Comandos, Pipelines, Listas, Comandos compostos e Substituição de comandos

Um comando simples é uma sequência de palavras separadas por espaços. A primeira palavra especifica o comando ou programa a ser executado e as palavras restantes são passadas ao programa como argumentos:

         ls /usr/bin
         grep "Arnoldo" fones.txt

Um comando tem sempre um valor de retorno denominado estado de saída, exit status ou simplesmente status. Em UNIX, um status 0 (zero) significa sucesso na execução do comando. Um status diferente de 0 (zero) indica a ocorrência de alguma anormalidade ou condição de erro. Estes estados de retorno são usados principalmente em controle de fluxo de programas shell, como veremos adiante.

O parâmetro especial $? expande o valor do status do último comando executado. O parâmetro $! expande para o PID do processo executado em background mais recentemente.

Alguns comandos úteis:

Pipelines são definidos como um ou mais comandos separados por  | :

       [!] comando1 [ | comando2 ...]

A saída padrão de comando1 é conectada à entrada padrão de comando2.

O status da execução de um pipeline é o status do último comando executado. Se o sinal ! é usado, o status do pipeline é a negação lógica do status do último comando executado.

Lista é uma seqüência de um ou mais pipelines separados pelos operadores:

;
(seqüência),
&&
(AND),
 |  | 
(OR),
&
(background),
e opcionalmente terminada por ;, & ou <newline>.

       comando1 && comando2   # comando2 é executado SE, E SOMENTE
                              # SE, comando1 retorna status 0 (zero)

       comando1 || comando2   # comando2 é executado SE, E SOMENTE
                              # SE, comando1 retorna status
                              # DIFERENTE de 0 (zero)

Os operadores && (AND) e  |  |  (OR) têm igual precedência. Por sua vez, eles têm precedência sobre ; e & que têm igual precedência entre si.

O status de listas com ;, && e  |  |  é o status do último comando executado na lista. No caso de listas com &, o status é sempre 0 (zero).

Um Comando composto é definido por uma das seguintes formas:

(lista)
lista é executada em subshell. Definição de variáveis não são efetivas depois que o comando composto termina. O status é o mesmo da lista.

[[ expressão ]]
Retorna um status de 0 ou 1 dependendo da avaliação de expressão, denominada a expressão condicional. Se o valor da expressão é VERDADEIRO, o status é 0 (zero). Caso contrário, o status é 1 (um). Uma expressão condicional é definida por uma série de operadores:
-a arq
verdade se arquivo arq existe;
-d arq
verdade se arquivo arq existe e é um diretório;
-f arq
verdade se arquivo arq existe e é um arquivo regular;
-h arq
verdade se arquivo arq existe e é um link simbólico;
-x arq
verdade se arquivo arq existe e é executável;
-s arq
verdade se arquivo arq existe e possui tamanho maior que 0 (zero);
arq1 -nt arq2
verdade se arquivo arq1 é mais novo que arquivo arq2;
arq1 -ot arq2
verdade se arquivo arq1 é mais velho que arquivo arq2;
-z string
verdade se string possui comprimento 0 (zero);
-n string
verdade se string possui comprimento diferente de 0 (zero);
string1 == string2
verdade se string1 é igual a string2.
string1 != string2
verdade se string1 é diferente de string2;

Outros operadores podem ser vistos em test(1) ou na seção CONDITIONAL EXPRESSIONS em bash(1).

(( expressão ))
Retorna um status de 0 ou 1 dependendo da avaliação de expressão como uma expressão aritmética. Se o valor da expressão é diferente de 0 (zero), o status é 0 (zero). Caso contrário, o status é 1 (um). Uma expressão aritmética é definida por uma série de operadores:
!
negação lógica;
&&
AND lógico;
 |  | 
OR lógico;
$<= \quad >= \quad < \quad >$
comparação aritmética;
==   !=
igualdade e desigualdade;
**
exponenciação;
* / %
multiplicação, divisão e módulo (resto de divisão);
- +
soma e subtração;
$\sim$
negação bit-a-bit;
&
AND bit-a-bit;
^
XOR bit-a-bit;
 | 
OR bit-a-bit;
=     OP=
atribuição, onde OP pode ser qualquer operador aritmético ou bit-a-bit (e.g., $X~+=~2$);

Outros operadores podem ser vistos na seção ARITHMETIC EVALUATION em bash(1).

1.3 Expansão Aritmética

A Expansão Aritmética permite a avaliação de uma expressão aritmética e a substituição do resultado como valor de variável ou dentro de alguma linha de comando. O formato para expansão aritmética é:

               $((expressão))

As expressões abaixo são portanto expressões aritméticas válidas e têm todas o mesmo efeito:

               CONT=$(($CONT + 1))
               CONT=$((CONT + 1))
               ((CONT = CONT + 1))

1.4 Substituição de Comando

A Substituição de Comando permite que a saída padrão de um comando seja colocada (inserida) no ponto em que este comando foi usado. Existem 2 formas:

              `command`     # usando crase
       ou
               $(command)

Quebras de linhas no início ou final da saída do comando são descartadas. Quebras de linhas no meio da saída são substituídas por espaços.

Exemplo:

              wc -l `find  ~ -name '*.c'` # conta as  linhas de todos os
                                          # arquivos  com programas  em
                                          # Ling. C existentes  na área
                                          # do usuário

1.5 Controle de fluxo e iteração

Condicional
 
if lista_1
then
    lista_A
elif lista_2
then 
    lista_B
...
else
    lista_else
fi
Se o status de lista_1 for 0 (zero), executa lista_A. Senão, se o status de lista_2 for 0 (zero), executa lista_B. Se todos os status forem diferentes de 0 (zero), executa lista_else. Os blocos definidos por elif e else são opcionais.

Laço while
 
    while lista_1
    do
        lista
    done
Enquanto o status de lista_1 for 0 (zero), executa lista.

Laço until
 
    until lista_1
    do
        lista
    done
Enquanto o status de lista_1 for DIFERENTE de 0 (zero), executa lista.

Laço for
 
     for var in palavras
     do
        lista
     done

O termo palavras é considerado uma seqüência de itens separados por espaço. Um de cada vez, um item da seqüência é atribuído como valor da variavel var e lista é executada. O laço termina quando não restam mais itens a serem atribuídos à variavel var

Laço for aritmético
 
     for ((inic; condição; reinic ))
     do
        lista
     done

Esta estrutura é análoga à estrutura for da Linguagem C. A expressão aritmética inic é avaliada uma vez. lista é executada repetidas vezes enquanto a expressão aritmética condição for diferente de 0 (zero). Ao cada iteração, a expressão artimética reinic. O laço termina quando a expressão aritmética condição for 0 (zero).

Exemplo 1: Inserir a linha Arnoldo Orlando (011)-234-4567 no arquivo fones.txt somente se não existir nenhuma pessoa de nome Arnoldo. Após a inserção (se houver), deve-se ordenar fones.txt.

     if ! grep --quiet "Arnoldo" fones.txt
     then
        echo "Arnoldo Orlando   (011)-234-4567" >> fones.txt ; sort -o fones.txt fones.txt
        exit
     else
         echo "Já existe um Arnoldo !!!!"
         exit 1
     fi

Outra solução, agora mais compacta:

  ! grep --quiet "Arnoldo" fones.txt && ( echo "Arnoldo Orlando  (011)-234-4567" >> fones.txt ; sort -o fones.txt fones.txt )

Note-se o uso dos parênteses, obrigatórios neste caso para forçar a execução da seqüência de echo(1) e sort(1) somente se grep(1) não encontra Arnoldo.

Exemplo 2: Escreva um script que escreva na tela as frases Bom Dia !!, Boa Tarde !!, Boa Noite !! e Vai dormir, menino !!! de acordo com a hora do dia apresentada por date(1).

      #!/bin/bash

      HORA=`date +"%H"`        # Também poderia ser HOME=$(date +"%H")

      if [[ $HORA -ge 6 && $HORA -lt 12 ]]
      then
           echo "Bom Dia !!!"
      elif  [[ $HORA -ge 12 && $HORA -lt 18 ]]
      then
          echo "Boa Tarde !!!"
      elif  [[ $HORA -ge 18 && $HORA -le 23 ]]; then
          echo "Boa Noite !!!"
      elif  [[ $HORA -ge 0 && $HORA -lt 2 ]]; then
          echo "Vai dormir, menino !!!"
      else
          echo "Por que tu bebe, nojento !!!"
     fi

Exemplo 3: Mudar para um diretório corrente específico, SOMENTE se este diretório não existir. Neste caso, antes de mudar de diretório, ele deve ser criado.

     MeuDIR="${HOME}/teste"
     [[ -d "${MeuDIR}" ]] || (mkdir ${MeuDIR} && cd ${MeuDIR})

Mudar para um diretório corrente específico, de forma que se este diretório não existir, ele deve ser criado antes.

     MeuDIR="${HOME}/teste"
     [[ -d "${MeuDIR}" ]] || mkdir ${MeuDIR} && cd ${MeuDIR}
ou
     ([[ -d "${MeuDIR}" ]] || mkdir ${MeuDIR}) && cd ${MeuDIR}

Exemplo 4:

     #!/bin/sh

     MOZILLA_HOME=/usr/local/netscape
     WWWBROWSER="/usr/local/bin/netscape.bin -install"
     XENVIRONMENT="/usr/lib/X11/app-defaults/Netscape-4.79"

     if [[ `basename $0` = "news" ]]; then
        SERVICE="-news news://${NNTPSERVER}"
     else
        SERVICE=""
     fi

     export XENVIRONMENT WWWBROWSER SERVICE MOZILLA_HOME

     ${WWWBROWSER} $* ${SERVICE} &

Exemplo 5:

        read NOMES

        for i in $NOMES
        do
             echo "$i" >>nomes.txt
        done

Exemplo 6:

        touch nomes.txt

        while [[ $(wc -l nomes.txt | cut -c1-7) -lt 15 ]]; do

           read -p "Indique no maximo 15 nomes: " NOMES

           for i in $NOMES
           do
               echo "$i" >>nomes.txt
           done
        done

Exemplo 7:

    if [[ "${SET_WALLPAPER}" = "no" ]]
    then
        for i in Esetroot xsetroot
        do
          cmd=$(which $i)
          if [[ -x $cmd ]]; then
             XSETROOT="$cmd"
             if [[ "${cmd}" = "Esetroot" ]]; then
                 XSETROOT_OPT="/usr/local/Graphics/Backgrounds/BlueStone.xpm"
             else
                 XSETROOT_OPT="-solid grey"
             fi
             break
          fi
        done
        ${XSETROOT} ${XSETROOT_OPT}
        exit
    fi



Armando Luiz Nicolini Delgado
2008-07-11