Programação Shell Linux

Aqui temos um livro livre e completo sobre Shell

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

Comandos Shell Script

Diagrama Sintático
Júlio Neves
Júlio Neves
Home | Artigos Español EnglishAgradecimentos | Links Amigos

Comprar o livro

Changelogs

  Papo de Botequim - Parte 1  

  Papo de Botequim - Parte 2  

  Papo de Botequim - Parte 3  

  Papo de Botequim - Parte 4  

  Papo de Botequim - Parte 5  

  Papo de Botequim - Parte 6  

  Papo de Botequim - Parte 7  

  Papo de Botequim - Parte 8  

  Papo de Botequim - Parte 9  

  Papo de Botequim - Parte 10  

  Papo de Botequim - Parte 11  

  Tira Gosto  

Papo de Botequim - Parte IV

- E aí cara, tentou fazer o exercício que te pedi para revigorar as idéias?

- Claro, que sim! Em programação, se você não treinar, não aprende. Você me pediu para fazer um scriptizinho para informar se um determinado usuário, que será passado como parâmetro esta logado (arghh!) ou não. Eu fiz o seguinte:

$ cat logado #!/bin/bash # Pesquisa se uma pessoa está logada ou não if who | grep $1 then echo $1 está logado else echo $1 não se encontra no pedaço fi

- Calma rapaz! Já vi que você chegou cheio de tesão, primeiro vamos pedir os nossos chopes de praxe e depois vamos ao Shell. Chico traz dois chopes, um sem colarinho!

- Agora que já molhamos os nossos bicos, vamos dar uma olhadinha na execução do seu bacalho:

$ logado jneves jneves pts/0 Oct 18 12:02 (10.2.4.144) jneves está logado

Realmente funcionou. Passei o meu login como parâmetro e ele disse que eu estava logado, porém ele mandou uma linha que eu não pedi. Esta linha é a saída do comando who, e para evitar que isso aconteça é só mandá-la para o buraco negro que a esta altura você já sabe que é o /dev/null. Vejamos então como ficaria:

$ cat logado #!/bin/bash # Pesquisa se uma pessoa está logada ou não (versão 2) if who | grep $1 > /dev/null then echo $1 está logado else echo $1 não se encontra no pedaço fi

Agora vamos aos testes:

$ logado jneves jneves está logado $ logado chico chico não se encontra no pedaço
Pinguim com placa de atenção Ah, agora sim! Lembre-se desta pegadinha, a maior parte dos comandos tem uma saída padrão e uma saída de erros (o grep é uma das poucos exceções, já que não dá mensagem de erro quando não acha uma cadeia) e é necessário estarmos atentos para redirecioná-las para o buraco negro quando necessário.

Bem, agora vamos mudar de assunto: na última vez que nos encontramos aqui no Botequim, eu estava te mostrando os comandos condicionais e, quando já estávamos de goela seca falando sobre o if, você me perguntou como se testa condições. Vejamos então o

O Comando test

Bem, todos estamos acostumados a usar o if testando condições, e estas são sempre, maior, menor, maior ou igual, menor ou igual, igual e diferente. Bem, em Shell para testar condições, usamos o comando test, só que ele é muito mais poderoso que o que estamos habituados. Primeiramente vou te mostrar as principais opções (existem muitas outras) para testarmos arquivos em disco:

Opções do Comando test para arquivos
  -x arq     arq existe e com direito de execução  
  Opção     Verdadeiro se:  
  -e arq     arq existe  
  -s arq     arq existe e tem tamanho maior que zero  
  -f arq     arq existe e é um arquivo regular  
  -d arq     arq existe e é um diretório;  
  -r arq     arq existe e com direito de leitura  
  -w arq     arq existe e com direito de escrita  

Veja agora as principais opções para teste de cadeias de caracteres:

Opções do comando test para cadeias de caracteres
  c1 = c2    Cadeia c1 e c2 são idênticas 
  Opção     Verdadeiro se:  
  -z cadeia    Tamanho de cadeia é zero 
  -n cadeia    Tamanho de cadeia é maior que zero 
  cadeia    A cadeia cadeia tem tamanho maior que zero 

E pensa que acabou? Engano seu! Agora é que vem o que você está mais acostumado, ou seja as famosas comparações com numéricos. Veja a tabela:

Opções do comando test para números
  n1 -le n2     n1 é menor ou igual a n2     less or equal  
  Opção     Verdadeiro se:     Significado  
  n1 -eq n2     n1 e n2 são iguais     equal  
  n1 -ne n2     n1 e n2 não são iguais     not equal  
  n1 -gt n2     n1 é maior que n2     greater than  
  n1 -ge n2     n1 é maior ou igual a n2     greater or equal  
  n1 -lt n2     n1 é menor que n2     less than  

Além de tudo, some-se a estas opções as seguintes facilidades:

Operadores
  -o     OU lógico  
  Operador     Finalidade  
  Parênteses ( )     Agrupar  
  Exclamação !     Negar  
  -a     E lógico  

Ufa! Como você viu tem coisa prá chuchu, e como eu te disse no início, o nosso if é muito mais poderoso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona, primeiramente testaremos a existência de um diretório:

Exemplos:

    if  test -d lmb
    then
        cd lmb
    else
        mkdir lmb
        cd lmb
    fi

No exemplo, testei se existia um diretório lmb definido, caso negativo (else), ele seria criado. Já sei, você vai criticar a minha lógica dizendo que o script não está otimizado. Eu sei, mas queria que você o entendesse assim, para então poder usar o ponto-de-espantação (!) como um negador do test. Veja só:

    if  test ! -d lmb
    then
        mkdir lmb
    fi
    cd lmb

Desta forma o diretório lmb seria criado somente se ele ainda não existisse, e esta negativa deve-se ao ponto-de-exclamação (!) precedendo a opção -d. Ao fim da execução deste fragmento de script, o programa estaria com certeza dentro do diretório lmb.

Vamos ver dois exemplos para entender a diferença comparação entre números e entre cadeias.

    cad1=1
    cad2=01
    if  test $cad1 = $cad2
    then
        echo As variáveis são iguais.
    else
        echo As variáveis são diferentes.
    fi

Executando o fragmento de programa acima vem:

     As variáveis são diferentes.

Vamos agora alterá-lo um pouco para que a comparação seja numérica:

    cad1=1
    cad2=01
    if  test $cad1 -eq $cad2
    then
        echo As variáveis são iguais.
    else
        echo As variáveis são diferentes.
    fi

E vamos executá-lo novamente:

     As variáveis são iguais.

Como você viu nas duas execuções obtive resultados diferentes porque a cadeia 01 é realmente diferente da cadeia 1, porém, a coisa muda quando as variáveis são testadas numericamente, já que o número 1 é igual ao número 01.

Exemplos:

Para mostrar o uso dos conectores -o (OU) e -a (E), veja um exemplo animal feito direto no prompt (me desculpem os zoólogos, mas eu não entendendo nada de reino, filo, classe, ordem, família, gênero e espécie, desta forma o que estou chamando de família ou de gênero tem grande chance de estar incorreto):

$ Familia=felinae $ Genero=gato $ if test $Familia = canidea -a $Genero = lobo -o $Familia = felina -a $Genero = leão > then > echo Cuidado > else > echo Pode passar a mão > fi Pode passar a mão

Neste exemplo caso o animal fosse da família canídea E (-a) do gênero lobo, OU (-o) da familia felina E (-a) do gênero leão, seria dado um alerta, caso contrário a mensagem seria de incentivo.

Pinguim com placa de dica Os sinais de maior (>) no início das linhas internas ao if são os prompts de continuação (que estão definidos na variável $PS2) e quando o Shell identifica que um comando continuará na linha seguinte, automaticamente ele o coloca até que o comando seja encerrado.

Vamos mudar o exemplo para ver se continua funcionando:

$ Familia=felino $ Genero=gato $ if test $Familia = felino -o $Familia = canideo -a $Genero = onça -o $Genero = lobo > then > echo Cuidado > else > echo Pode passar a mão > fi Cuidado

Obviamente a operação redundou em erro, isto foi porque a opção -a tem precedência sobre a -o, e desta forma o que primeiro foi avaliado foi a expressão:

     $Familia = canideo -a $Genero = onça

Que foi avaliada como falsa, retornando o seguinte:

     $Familia = felino -o FALSO -o $Genero = lobo

Que resolvida vem:

     VERDADEIRO -o FALSO -o FALSO

Como agora todos conectores são -o, e para que uma série de expressões conectadas entre si por diversos OU lógicos seja verdadeira, basta que uma delas seja, a expressão final resultou como VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionar façamos o seguinte:

$ if test \($Familia = felino -o $Familia = canideo\) -a \($Genero = onça -o $Genero = lobo\) > then > echo Cuidado > else > echo Pode passar a mão > fi Pode passar a mão

Desta forma, com o uso dos parênteses agrupamos as expressões com o conector -o, priorizando as suas execuções e resultando:

     VERDADEIRO -a FALSO

Para que seja VERDADEIRO o resultado duas expressões ligadas pelo conector -a é necessário que ambas sejam verdadeiras, o que não é o caso do exemplo acima. Assim o resultado final foi FALSO sendo então o else corretamente executado.

Se quisermos escolher um CD que tenha faixas de 2 artistas diferentes, nos sentimos tentados a usar um if com o conector -a, mas é sempre bom lembrarmos que o bash nos dá muito recursos, e isso poderia ser feito de forma muito mais simples com um único comando grep, da seguinte maneira:

$ grep Artista1 musicas | grep Artista2

Da mesma forma para escolhermos CDs que tenham a participação do Artista1 e do Artista2, não é necessário montarmos um if com o conector -o. O egrep (ou grep -E, sendo este mais aconselhável) também resolve isso para nós. Veja como:

$ egrep (Artista1|Artista2) musicas

Ou (nesse caso específico) o próprio grep puro e simples poderia nos quebrar o galho:

$ grep Artista[12] musicas

No egrep acima, foi usada uma expressão regular, onde a barra vertical (|) trabalha como um OU lógico e os parênteses são usados para limitar a amplitude deste OU. Já no grep da linha seguinte, a palavra Artista deve ser seguida por um dos valores da lista formada pelos colchetes ([ ]), isto é, 1 ou 2.

- Tá legal, eu aceito o argumento, o if do Shell é muito mais poderoso que os outros caretas, mas cá pra nós, essa construção de if test ... é muito esquisita, é pouco legível.

- É você tem razão, eu também não gosto disso e acho que ninguém gosta. Acho que foi por isso, que o Shell incorporou outra sintaxe que substitui o comando test.

Exemplos:

Para isso vamos pegar aquele exemplo para fazer uma troca de diretórios, que era assim:

    if  test ! -d lmb
    then
        mkdir lmb
    fi
    cd lmb

e utilizando a nova sintaxe, vamos fazê-lo assim:

    if  [ ! -d lmb ]
    then
        mkdir lmb
    fi
    cd lmb

Ou seja, o comando test pode ser substituído por um par de colchetes ([ ]), separados por espaços em branco dos argumentos, o que aumentará enormemente a legibilidade, pois o comando if irá ficar com a sintaxe semelhante à das outras linguagens e por isso este será o modo que o comando test será usado daqui para a frente.

Querida, Encolheram o Comando Condicional

Se você pensa que acabou, está muito enganado. Repare a tabela (tabela verdade) a seguir:

Valores Booleanos E OU
 FALSO-FALSO   FALSO   FALSO 
 VERDADEIRO-VERDADEIRO   VERDADEIRO   VERDADEIRO 
 VERDADEIRO-FALSO   FALSO   VERDADEIRO 
 FALSO-VERDADEIRO   FALSO   VERDADEIRO 

Ou seja, quando o conector é E e a primeira condição é verdadeira, o resultado final pode ser VERDADEIRO ou FALSO, dependendo da segunda condição, já no conector OU, caso a primeira condição seja verdadeira, o resultado sempre será VERDADEIRO e se a primeira for falsa, o resultado dependerá da segunda condição.

Ora, os caras que desenvolveram o interpretador não são bobos e estão sempre tentando otimizar ao máximo os algoritmos. Portanto, no caso do conector E, a segunda condição não será avaliada, caso a primeira seja falsa, já que o resultado será sempre FALSO. Já com o OU, a segunda será executada somente caso a primeira seja falsa.

Aproveitando disso, criaram uma forma abreviada de fazer testes. Batizaram o conector E de && e o OU de || e para ver como isso funciona, vamos usá-los como teste no nosso velho exemplo de trocarmos de diretório, que em sua última versão estava assim:

    if  [ ! -d lmb ]
    then
        mkdir lmb
    fi
    cd lmb

Isso também poderia ser escrito da seguinte maneira:

    [ ! -d lmb ] && mkdir lmb
    cd lmb

Ou ainda retirando a negação (!):

    [ -d lmb ] || mkdir lmb
    cd lmb

No primeiro caso, se o primeiro comando (o test que está representado pelos colchetes) for bem sucedido, isto é, não existir o diretório lmb, o mkdir será efetuado porque a primeira condição era verdadeira e o conector era E.

No exemplo seguinte, testamos se o diretório lmb existia (no anterior testamos se ele não existia) e caso isso fosse verdade, o mkdir não seria executado porque o conector era OU. Outra forma:

     cd lmb || mkdir lmb

Neste caso, se o cd fosse mal sucedido, seria criado o diretório lmb mas não seria feito o cd para dentro dele. Para executarmos mais de um comando desta forma, é necessário fazermos um grupamento de comandos, e isso se consegue com o uso de chaves ({ }). Veja como seria o correto:

    cd lmb ||
        {
        mkdir lmb
        cd lmb
        }

Ainda não está legal, porque caso o diretório não exista, o cd dará a mensagem de erro correspondente. Então devemos fazer:

    cd lmb 2> /dev/null ||
        {
        mkdir lmb
        cd lmb
        }

Como você viu o comando if nos permitiu fazer um cd seguro de diversas maneiras. É sempre bom lembrarmos que o seguro a que me referi é no tocante ao fato de que ao final da execução você sempre estará dentro de lmb, desde que você tenha permissão entrar em lmb, permissão para criar um diretório em ../lmb, haja espaço em disco, ...

E tome de test

Ufa! Você pensa que acabou? Ledo engano! Ainda tem uma forma de test a mais. Essa é legal porque ela te permite usar padrões para comparação. Estes padrões atendem às normas de Geração de Nome de Arquivos (File Name Generation, que são ligeiramente parecidas com as Expressões Regulares, mas não podem ser confundidas com estas). A diferença de sintaxe deste para o test que acabamos de ver é que esse trabalha com dois pares de colchete da seguinte forma:

[[ expressão ]]

Onde expressão é uma das que constam na tabela a seguir:

Expressões Condicionais Para Padrões
  expr1 ¦¦ expr2     "OU" lógico, verdadeiro se expr1 ou expr2 for verdadeiro  
  Expressão     Retorna  
  cadeia == padrão
  cadeia1 = padrao  
  Verdadeiro se cadeia1 casa com padrão  
  cadeia1 != padrao     Verdadeiro se cadeia1 não casa com padrao.  
  cadeia1 < cadeia1     Verdadeiro se cadeia1 vem antes de cadeia1 alfabeticamente.  
  cadeia1 > cadeia1     Verdadeiro se cadeia1 vem depois de cadeia1 alfabeticamente  
  expr1 && expr2     "E" lógico, verdadeiro se ambos expr1 e expr2 são verdadeiros  
$ echo $H 13 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida Hora inválida $H=12 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida $

Neste exemplo, testamos se o conteúdo da variável $H estava compreendido entre zero e nove ([0-9]) ou (||) se estava entre dez e doze (1[0-2]), dando uma mensagem de erro caso não fosse.

Exemplos:

Para saber se uma variável tem o tamanho de um e somente um caractere, faça:

$ var=a $ [[ $var == ? ]] && echo var tem um caractere var tem um caractere $ var=aa $ [[ $var == ? ]] && echo var tem um caractere $

Como você pode imaginar, este uso de padrões para comparação, aumenta muito o poderio do comando test. No início deste papo, antes do último chope, afirmamos que o comando if do interpretador Shell é mais poderoso que o seu similar em outras linguagens. Agora que conhecemos todo o seu espectro de funções, diga-me: você concorda ou não com esta assertiva?

Acaso Casa com case

Vejamos um exemplo didático: dependendo do valor da variável $opc o script deverá executar uma uma das opções: inclusão, exclusão, alteração ou fim. Veja como ficaria este fragmento de script:

    if  [ $opc -eq 1 ]
    then
        inclusao
    elif [ $opc -eq 2 ]
    then
        exclusao
    elif [ $opc -eq 3 ]
    then
        alteracao
    elif [ $opc -eq 4 ]
    then
        exit
    else
        echo Digite uma opção entre 1 e 4
    fi

Neste exemplo você viu o uso do elif com um else if, esta á a sintaxe válida e aceita, mas poderíamos fazer melhor, e isto seria com o comando case, que tem a sintaxe a seguir:

    case $var in
        padrao1) cmd1
                 cmd2
                 cmdn ;;
        padrao2) cmd1
                 cmd2
                 cmdn ;;
        padraon) cmd1
                 cmd2
                 cmdn ;;
    esac

Onde a variável $var é comparada aos padrões padrao1, ..., padraon e caso um deles atenda, o bloco de comandos cmd1, ..., cmdn correspondente é executado até encontrar um duplo ponto-e-vírgula (;;), quando o fluxo do programa se desviará para instrução imediatamente após o esac.

Na formação dos padrões, são aceitos os seguintes caracteres:

Caracteres Para Formação de Padrões
¦   OU lógico  
  Caractere     Significado  
*   Qualquer caractere ocorrendo zero ou mais vezes  
?   Qualquer caractere ocorrendo uma vez  
[...]   Lista de caracteres  

Para mostrar como fica melhor, vamos repetir o exemplo anterior, só que desta vez usaremos o case e não o if ... elif ... else ... fi.

    case $opc in
        1) inclusao ;;
        2) exclusao ;;
        3) alteracao ;;
        4) exit ;;
        *) echo Digite uma opção entre 1 e 4
    esac

Como você deve ter percebido, eu usei o asterisco como a última opção, isto é, se o asterisco atende a qualquer coisa, então ele servirá para qualquer coisa que não esteja no intervalo de 1 a 4. Outra coisa a ser notada é que o duplo ponto-e-vírgula não é necessário antes do esac.

Exemplos:

Vamos agora fazer um script mais radical. Ele te dará bom dia, boa tarde ou boa noite dependendo da hora que for executado, mas primeiramente veja estes comandos:

$ date Tue Nov 9 19:37:30 BRST 2004 $ date +%H 19

O comando date informa a data completa do sistema, mas ele tem diversas opções para seu mascaramento. Neste comando, a formatação começa com um sinal de mais (+) e os caracteres de formatação vêm após um sinal de percentagem (%), assim o %H significa a hora do sistema. Dito isso vamos ao exemplo:

$ cat boasvindas.sh #!/bin/bash # Programa bem educado que # dá bom-dia, boa-tarde ou # boa-noite conforme a hora Hora=$(date +%H) case $Hora in 0? | 1[01]) echo Bom Dia ;; 1[2-7] ) echo Boa Tarde ;; * ) echo Boa Noite ;; esac exit

Peguei pesado, né? Que nada vamos esmiuçar a resolução caso-a-caso (ou seria case-a-case? smile )

0? | 1[01] - Significa zero seguido de qualquer coisa (?), ou (|) um seguido de zero ou um ([01]) ou seja, esta linha pegou 01, 02, ... 09, 10 e 11;

1[2-7]     - Significa um seguido da lista de dois a sete, ou seja, esta linha pegou 12, 13, ... 17;

*          - Significa tudo que não casou com nenhum dos padrões anteriores.

- Cara, até agora eu falei muito e bebi pouco. Agora eu vou te passar um exercício para você fazer em casa e me dar a resposta da próxima vez que nos encontrarmos aqui no botequim, tá legal?

- Tá, mas antes informe ao pessoal que está acompanhando este curso conosco como eles podem te encontrar para fazer críticas, contar piada, convidar para o chope, curso ou palestra ou até mesmo para falar mal dos políticos.

- É fácil, meu e-mail é julio.neves@gmail.com, mas pare de me embromar que eu não vou esquecer de te passar o script para fazer. É o seguinte: quero que você faça um programa que receberá como parâmetro o nome de um arquivo e que quando executado salvará este arquivo com o nome original seguido de um til (~) e colocará este arquivo dentro do vi (o melhor editor que se tem notícia) para ser editado. Isso é para ter sempre a última cópia boa deste arquivo caso o cara faça alterações indevidas. Obviamente, você fará as críticas necessárias, como verificar se foi passado um parâmetro, se o arquivo passado existe, ... Enfim, o que te der na telha e você achar que deve constar do script. Deu prá entender?

- Hum, hum...

- Chico! Traz mais um sem colarinho que o cara aqui já está dando para entender! smile

Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell que mande um e-mail para a nossa gerencia de treinamento para informar-se.

Qualquer dúvida ou falta de companhia para um chope ou até para falar mal dos políticos é só mandar um e-mail para mim.

Valeu!

-- EvandroPastor - 19 Sep 2005

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