CI064, 2019-1                               © Roberto André Hexsel, 2014-2019


Bash, ou como computeiros ganham mais tempo

Nas aulas passadas vimos algumas facilidades providas por Bash que nos permitem trabalhar eficientemente. Nesta aula iniciaremos o estudo mais aprofundado da programação em Bash, o que nos propiciará ainda mais contentamento, eficiência e eficácia. imenso
Antes de mais nada, crie um prompt vazio, copiando a linha abaixo no seu terminal alias prompt:="" Três clicks no botão esquerdo para copiar, um click no meio para colar
alias

Apelidos ou aliases

O manual do Bash diz que as regras para expansão de apelidos são confusas e é mais seguro usar funções do que apelidos. Contudo, para uma solução porca e rápida, apelidos podem ser úteis como um "quebra galho", mas não como parte de uma ferramenta "de verdade" --para tanto devem ser usadas funções. O comando alias mostra a lista de apelidos disponíveis. unalias remove um apelido da lista. Por exemplo, prompt: alias ls="ls -F" muda a noção que Bash tem do que seja o programa /bin/ls para seu novo apelido; quando Bash interpretar a linha abaixo prompt: ls /bin # ls agora é um apelido o comando que será executado é a substituição do nome ls pelo seu apelido: prompt: ls -F /bin # "ls -F" é a expansão do apelido "ls" Para remover o apelido: prompt: unalias ls De novo, como um quebra galho, porco e rápido, apelidos podem ser úteis. O comando, por si só, lista todos os apelidos válidos prompt: alias lista de aliases ou nada Vamos ao que realmente interessa, que são as funções, mas antes temos que entender o que são as variáveis, expressões, testes e examinar os comandos if, case, for e while. Levantemos pois a tampa da caixa da Pandora. caixa de Pandora

Variáveis e Ambiente

Bash executa com um "ambiente" (environment) que é um conjunto de pares "nome=valor" com definições de configuração pessoal da shell, com configurações de idioma, com as funções definidas pelo usuário, etc. O comando env mostra o ambiente. Não tente entender toda a copiosa saída, mas dê uma passada de olhos para ver se reconhece algo familiar. prompt: env Algumas das variáveis de ambiente importantes são listadas abaixo. Há variáveis que podem ser definidas pelo usuário, e há variáveis invariáveis que são definidas por Bash. PATH contém a lista de caminhos que devem ser percorridos na busca de programas por executar. Tipicamente PATH contém /bin, /usr/bin... HOME aponta o caminho completo do diretório "home" do usuário. "~" (til) é uma abreviatura para ${HOME}. Definida por Bash. USER nome do usuário no sistema (username). Definida por Bash. EDITOR nome do editor preferido pelo usuário (emacs, no meu caso). Para verificar os valores de suas variáveis diga prompt: echo $PATH $HOME $USER $EDITOR Como se define o valor de uma variável? Simples, basta atribuir-lhe o valor desejado. No exemplo, um conjunto de caminhos é atribuído à variável de ambiente PATH --não efetue esta atribuição porque o conjunto mostrado pode ser muito pequeno. Ao executar um programa, Bash procura o executável em todos os caminhos apontados por $PATH. prompt: PATH=/bin:/usr/bin:/usr/local/bin:. # não faça isso! O conjunto de caminhos é a concatenação, com ':' (dois pontos), de caminhos completos para os diretórios que contêm executáveis. No exemplo acima, são três os diretórios por pesquisar (/bin, /usr/bin e /usr/local/bin) e mais o diretório corrente (.). A variável PATH é definida num arquivo de configuração para o seu sistema local e não deve ser alterada, a menos que você saiba o que está fazendo. Supondo que você necessite aumentar o conjunto de caminhos de busca de executáveis, como se atribui o novo valor à PATH? Note o ':' para concatenar no novo componente do conjunto. prompt: PATH=${PATH}:/novo/caminho/para/executaveis prompt: echo $PATH /bin:/usr/bin:/usr/local/bin:.:/novo/caminho/para/executaveis O comando echo mostra o valor expandido da variável PATH -- note o cifrão. A atribuição é só com o nome (PATH) enquanto que para de-referenciar a variável é necessário efetuar sua expansão, com o prefixo '$' (cifrão ou dólar). Tente os comandos abaixo para ver a diferença. prompt: echo PATH prompt: echo $PATH De volta à atribuição à PATH. O novo valor é a concatenação do valor antigo (${PATH}) com o novo componente (/novo/...). A variável que é de-referenciada está protegida por "{...}" para garantir que Bash não concatene a palavra "PATH" com algum outro caractere que não seja um meta-caractere. Sempre que este risco existir, proteja-se a variável com um par de chaves. No caso do echo isso não é necessário porque $PATH é seu único argumento. Mais concisamente: um nome sem caracteres reservados é uma variável, o mesmo nome prefixado por '$' é o valor, ou conteúdo expandido, da variável. Diga prompt: echo $PATH e verifique se o diretório corrente (.) está nos seus caminhos. Se não está, pode ser uma excelente ideia acrescentá-lo: prompt: PATH=${PATH}:. Isso feito, programas e scripts podem ser executados a partir do diretório corrente sem que seja necessário prefixá-los com "./". Eu prefiro colocar o diretório corrente como sendo o último diretório no conjunto de caminhos porque isso garante que, se acidentalmente eu criar um comando com o mesmo nome de um programa "oficial", o programa oficial será executado e não a versão acidental. Se eu quiser executar a versão local, basta prefixá-la com um "./": prompt: ./meu_programa_de_mesmo_nome_que_um_oficial Veremos mais exemplos do uso de variáveis no que se segue.
testes Alice asked the Cheshire Cat, who was sitting in a tree, “What road do I take?” The cat asked, “Where do you want to go?” “I don’t know,” Alice answered. “Then,” said the cat, “it really doesn’t matter, does it?” ― Lewis Carroll, Alice's Adventures in Wonderland

Testes e Expressões

Agora nos aproximamos de coisas realmente interessantes. O comando test permite testar uma série condições e retorna verdadeiro ou falso, dependendo do resultado da comparação. A lista completa dos testes é exibida com prompt: help test Testemos então. Uma cadeia de caracteres (string) é delimitada por um par de aspas duplas. Retornaremos ao assunto de aspas adiante. Como se faz para descobrir se: 1) /bin/ls é um arquivo regular (não é um link) e tenho permissão para executá-lo? prompt: test -f /bin/ls && test -x /bin/ls && echo "/bin/ls é executável" /bin/ls é executável -f = is a file, -x is eXecutable 2) a cadeia de caracteres é vazia? prompt: test -z "" && echo "string vazia" string vazia -z = string is empty (Zero size) 3) a cadeia de caracteres é, ou não é, vazia? prompt: test -z "xx" && echo "string vazia" || echo "não vazia" não vazia prompt: test -z "" && echo "string vazia" || echo "não vazia" string vazia Bash aceita uma variação para os testes, que é escrever a condição entre um par de colchetes. Ao invés de "test condição" pode-se escrever "[ condição ]". Os espaços que separam os colchetes da condição são obrigatórios. O teste do terceiro exemplo pode ser escrito como prompt: [ -z "xx" ] && echo "string vazia" || echo "não vazia" não vazia Nesta forma, a sintaxe se assemelha com o teste de uma condição na linguagem C. A construção "((expressão))" resulta na avaliação aritmética de "expressão". O status de "((expressão))" é zero (TRUE) se a avaliação resulta num valor diferente de zero, e é diferente de zero (FALSE) caso contrário. Também acho confuso - leia de novo. prompt: (( 5-5 )) && echo "diferente de zero" || echo "igual a zero" igual a zero prompt: (( 5+5 )) && echo "diferente de zero" || echo "igual a zero" diferente de zero Os espaços entre a expressão e "((" ou "))" são opcionais. Note que a lista TESTE && PROG1 || PROG2 equivale a um if TESTE then PROG1 else PROG2 fi Quando se deseja obter o resultado da avaliação da expressão, ao invés da resposta binária TRUE/FALSE, deve-se expandir o valor da expressão, com $(( expressão )). O comando abaixo efetua a soma de dois inteiros e atribui o resultado à variável "v". e então o echo $v exibe o valor da variável. prompt: v=$(( 5+3 )) ; echo $v 8 prompt: v=$(( 5-3 )) ; echo $v 2 A construção "[[ expressão ]]" é uma versão estendida do comando test e oferece um conjunto maior de operadores de comparação. Detalhes no manual e em "help [[ ]]". Em todas as comparações de igualdade ou diferença, com '=' ou '==' ou '!=', o operador de igualdade deve estar separado de seus operandos por espaços: [ x = y ] ou [[ "x" == "y" ]] Digressão. As opções de linha de comando são sempre avaliadas pelos programas antes de qualquer processamento pelo programa. Em especial, as opções são avaliadas antes da leitura da entrada padrão. Isso permite que uma ou mais das opções redirecionem stdin ou stdout. Assim, numa lista de comandos (pipeline ou AND/OR-list), as opções de todos os programas são avaliadas, para só então os dados começarem a fluir. Fim da digressão.

Quoting

"Quoting" é um mecanismo que permite que o significado de variáveis e meta-caracteres seja preservado ou alterado, dependendo do uso que se deseje dar a eles. Já vimos uma forma de "quoting" com o escape para o asterisco (\*). Outros meta-caracteres que devem ser escapados com a contra-barra são '?', '$', '&' e '|'. A contra-barra é escapada com uma contra-barra. Caracteres entre aspas simples mantém seus valores literais, perdendo os ditos caracteres quaisquer superpoderes. Cadeias de caracteres normais (entre aspas simples) não são consideradas variáveis da shell. prompt: echo $PATH /bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:. prompt: echo '$PATH' $PATH prompt: echo '$*?&|' $*?&| Caracteres entre aspas duplas preservam seus valores literais exceto `$´, `'´ (apóstrofo ou abre aspas-reto) e `\´ (contra-barra). Uma palavra precedida por um `$´ é expandida como uma variável da shell --pode ser uma variável sem um valor, vazia, como VVVV, abaixo prompt: echo "o caminho vazio eh $VVVV" o caminho vazio eh Outro escape interessante é o da quebra de linha. Por vezes, para melhorar a legibilidade, é necessário quebrar um comando longo em mais de uma linha; para evitar que Bash interprete as quebras de linha como o fim do comando, a quebra deve ser escapada com uma contra-barra. No exemplo abaixo, o ENTER é invisível e não pode haver nenhum caractere entre a contra-barra o o ENTER. prompt: exemplo de \ENTER um comando \ENTER muito longo
6502 program

Programação

Já vimos que um "pipeline" é uma sequência de comandos separados por '|'. Uma lista é uma sequência de um ou mais pipelines, separados por um dentre ';', '&', '&&' ou '||' e opcionalmente terminados por ';', '&' ou ENTER. Um comando composto pode ser um dentre (lista) -- a lista de comandos é executada numa subshell e quaisquer alterações introduzidas no ambiente durante a execução da subshell são perdidas ao final da sua execução; { lista; } -- este é um comando agrupado e a lista é executada como se fosse um bloco da linguagem C; (( expressão )) e [[ expressão ]] -- como descritos em avaliação de expressões.

Comando if

O comando if é similar ao condicional de C, com duas cláusulas opcionais: if COMANDOS; then COMANDOS; fi Se os comandos da cláusula if retornam zero (TRUE), então executa os comandos da cláusula then. "help if" para a sintaxe completa. Lembre que, para Bash, zero=TRUE. if COMANDOS; then COMANDOS-THEN; else COMANDOS-ELSE ; fi Se os comandos da cláusula if retornam zero, então executa COMANDOS-THEN, senão executa COMANDOS-ELSE. if COMANDOS; then COMANDOS-IF; elif COMANDOS; then COMANDOS-ELIF; fi Se os comandos da cláusula if retornam zero, então executa COMANDOS-IF, senão, executa os comandos da cláusula elif, se estes retornam zero então executa COMANDOS-ELIF. if COMANDOS; then COMANDOS-IF; elif COMANDOS; then COMANDOS-ELIF; else COMANDOS-ELSE ; fi A semântica desta última, prezada aluna, é você quem descreve. Exemplo 1: descubra o mais novo dentre dois arquivos, usando a versão colchetes de test. Estes comandos estão sem o prompt para facilitar a leitura e a cópia. f1=/bin/ls f2=/bin/grep if [ $f1 -nt $f2 ]; then novo=$f1 else novo=$f2 fi echo $novo ls -l $f1 $f2 Exemplo 2: Este é parte de um script que usaremos mais adiante no curso. WORK_PATH=/home/soft/linux/mips/cross/bin HOME_PATH=/opt/cross/bin if [ -x ${HOME_PATH}/mips-gcc ] ; then PATH=${PATH}:$HOME_PATH elif [ -x ${WORK_PATH}/mips-gcc ] ; then PATH=${PATH}:$WORK_PATH else echo -e "\n\n\tPANIC: cross-compiler not installed\n\n" fi echo $PATH O cross-compilador para o MIPS está instalado em diretórios distintos na minha casa e aqui no Departamento. O que este comando faz é descobrir a localização do gcc-cross e então acrescenta ao PATH o caminho para as ferramentas de cross-compilação, ou reclama que há um problema com a instalação das ferramentas. Desafio 2: transforme os comandos dos exemplos acima em listas que combinam ANDs (&&) e ORs (||). Though this be madness yet there is method in it.

Comando for

O comando for é similar ao comando de repetição de C, embora Bash nos ofereça dois sabores distintos, um que é similar ao de C, e outro dissimilar. "help for" para a sintaxe completa. A forma similar àquela de C é for (( expr1 ; expr2 ; expr3 )) ; do LISTA ; done A expressão aritmética expr1 é avaliada; a expressão aritmética expr2 é avaliada repetidamente, até que a avaliação resulta em zero. A cada vez que expr2 produzir resultado diferente de zero, a LISTA é executada e a expressão expr3 é avaliada. Uma expressão omitida avalia sempre como 1. A forma realmente interessante é for NOME in PALAVRA ... ; do LISTA ; done A PALAVRA é avaliada e expandida para uma lista de itens; à variável NOME é atribuída um elemento dentre os itens por vez, e LISTA é executada. Por exemplo, o laço abaixo imprime 5 números, entre 11 e 15: prompt: for N in 1 2 3 4 5 ; do echo $(( $N + 10 )) ; done 11 12 13 14 15 Execute um mesmo comando N vezes, usando seq, que gera uma sequência de números. Esta versão usa substituição de comando $(comando). prompt: N=4 prompt: for i in $(seq 1 $N) ; do echo ${i}_hhh_${i} ; done 1_hhh_1 2_hhh_2 3_hhh_3 4_hhh_4 Praticamente o mesmo comando, fazendo uso de expansão de chaves: prompt: for i in {1..4} ; do echo ${i}_jjj_${i} ; done 1_jjj_1 2_jjj_2 3_jjj_3 4_jjj_4 Estes comandos não são muito divertidos, porque eles têm exatamente o mesmo efeito do que a execução de prompt: seq 1 $N 1 2 3 4 Um exemplo um pouco mais interessante faz uso de avaliação aritmética: prompt: for i in {1..4} ; do echo $(( $i * $i )) ; done 1 4 9 16 As coisas começam a ficar algo mais divertidas quando outros comandos são empregados: criemos 4 diretórios, chamados de dir1, dir2, dir3, dir4: prompt: for i in {1..4} ; do mkdir dir${i} ; done prompt: ls dir1 dir2 dir3 dir4 Ou, se quisermos mover estes diretórios para outro lugar: prompt: mkdir X prompt: for i in {1..4} ; do mv dir${i} X ; done prompt: ls X dir1 dir2 dir3 dir4 prompt: ls X Ou, se quisermos efetuar algum comando em cada arquivo/diretório: o asterisco é expandido para "a lista de todos os arquivos no diretório X" (expansão de caminhos) e os elementos da lista são atribuídos à 'i', um a cada iteração dentre X/dir1, X/dir2, etc. prompt: for i in X/* ; do mv ${i} . ; done prompt: ls X prompt: ls dir1 dir2 dir3 dir4 X Esta pode parecer uma forma assaz contorcionada do comando "mv X/* .". Minha pretensão é apenas demonstrar o que se pode fazer com o comando for. ACHTUNG a lista de NOMEs pode ser gerada automagicamente por Bash; para tanto basta usar qualquer das expansões, ou combinações de expansões, providas por Bash. Por exemplo, for i in * ; do something_with ${i} ; done faz alguma coisa com todos os arquivos no diretório corrente.

Comando while

O comando while avalia uma condição e, se esta for verdadeira, executa os comandos no seu corpo. "help while" para a sintaxe completa. while COMMANDOS; do COMMANDOS-DO; done A lista de comandos da cláusula while é executada e se status da execução da lista for zero (TRUE), então os COMANDOS-DO são executados. Seguem dois exemplos que equivalem ao programa seq. O primeiro usa avaliação aritmética, enquanto que o segundo emprega test. Os dois laços empregam uma variável que deve ser interpretada por Bash como "inteiro" e não como uma cadeia de caracteres; a declaração da variável CNT explicita que seu tipo é um inteiro (-i) -- detalhes com "help declare". Estes comandos estão sem o prompt para facilitar a leitura/cópia. declare -i CNT # CNT deve ser interpretado com inteiro CNT=0 # inicializa o contador while (( $CNT < 10 )) ; do # avaliacao aritmetica CNT=$(( $CNT+1 )) # avaliacao aritmetica com atribuicao echo $CNT done CNT=0 # reinicializa o contador while [ $CNT -lt 10 ] ; do # test CNT=$(( $CNT+1 )) # avaliacao aritmetica com atribuicao echo $CNT done Estes imprimem a sequência de 1 a 10, um valor em cada linha.

Comando case

O comando case executa um COMANDO seletivamente, dependendo do casamento entre a PALAVRA e um PADRÃO. "help case" para a sintaxe completa. case PALAVRA in PADRÃO1 | PADRÃO2) COMANDO ;; esac Este comando é similar ao switch da linguagem C. Dependendo de qual padrão é casado, o comando daquela cláusula é executado. Este é mais um exemplo bobo, mas mostra como se pode testar o valor de uma variável da shell. Estes comandos estão sem o prompt para facilitar a leitura/cópia. A=3 case $A in 5) echo "A igual a cinco" ;; 6|7|8) echo "A igual a seis ou sete ou oito" ;; *) echo "A difere de 5,6,7,8" ;; esac A difere de 5,6,7,8 O padrão '*' casa qualquer padrão não listado anteriormente. Adiante veremos uma aplicação mais interessante de um case.

Da depuração de programas em Bash

Os exemplos acima, com a criação de arquivos e sua movimentação pela árvore de diretórios são destrutivos: uma vez que o script foi executado, suas ações, possivelmente devastadoras, são efetivadas. Dane-se! Antes de executar um comando que possa quebrar algo, caso esteja errado, é uma boa ideia fazer o seu eco. Pois é, echo antes do som. Assaz nerd. Vejamos um comando da classe devastadora: for F in a* c* e* g* ; do rm -f ${F%.o} ${F#test} mais_alguma_coisa_devastadora_com_montes_de_metacaracteres done Executar este comando, sem testar antes, é suicídio, especialmente porque eu errei no '#' do segundo argumento do rm; ao invés de '#' deveria ser '##'. O teste seguro se faz trocando o rm por um echo, para ver como ficará a lista de arquivos, depois da expansão dos metacaracteres. Para fins de teste o laço fica: for F in a* c* e* g* ; do echo "rm -f ${F%.o} ${F#test}" echo "mais_alguma_coisa_devastadora_com_montes_de_metacaracteres" done O echo do rm mostra o texto "rm -f" seguido da lista de arquivos; o segundo echo mostra a expansão de todos os argumentos para o comando devastador. Na hora de executar o laço, remova-se os echos e as aspas duplas adicionais. Lembre que aspas duplas e simples tem significados distintos. Exercício: O que faz o trecho de código abaixo? A=1 B=16 W=WwAn for P in frag jpeg_enc rtr zip_dec zip_enc ; do echo -n -e "$P\n1k\t2k\t4k\t8k\t16k\t32k\t64k\n" for C in 1k 2k 4k 8k 16k 32k 64k; do echo "grep din_dmm ${P}_${C}A${A}B${B}${W} | tr ... | cut ..." done echo done Exercício: O que faz o trecho de código abaixo? B=64 W=WwAn for P in jpeg_enc zip_enc ; do echo -n -e "$P\n1k\t2k\t4k\t8k\n" for A in 1 2 4 ; do for C in 1k 2k 4k 8k ; do echo "grep din_dmm ${P}_${C}A${A}B${B}${W} | tr ... | cut ..." done echo done done
parametros

Parâmetros - como alimentar scripts e funções

Um script é um arquivo texto que contém atribuições à variáveis e comandos que controlam a execução de programas. Posto de outra forma, um script é um arquivo texto com um programa a ser interpretado por Bash. Detalhes na próxima seção. Scripts são artefatos extremamente úteis, principalmente porque é possível passar-lhes parâmetros de uma forma simples e eficiente. Parâmetros posicionais são atribuídos quando o script é invocado, e não podem ser modificados com uma atribuição. Hmm... a menos que se empregue o comando set. Quais são os parâmetros posicionais? 0 é o caminho completo para o script e é o "nome completo do script". Diga "echo $0". O '$' de-referencia o parâmetro '0'. 1 a 9 são os primeiros nove parâmetros, da esquerda para a direita, na ordem em que aparecem na invocação do script. De-referenciados com $1, $2 ... $9. {10} e maiores são os parâmetros além do nono e devem ser nominados com as chaves para que $10 --${1}0, '$1' seguido de '0'-- não seja confundido com ${10}. # é expandido para o número de parâmetros posicionais. De-referencia com $# $ é expandido para o PID da shell. De-referencia com $$ ? é expandido para o status de execução do pipeline executado mais recentemente em foreground. De-referencia com $? * é expandido para os parâmetros posicionais, iniciando de 1. Se a expansão ocorre entre aspas duplas, a expansão é para uma única palavra, com o valor de cada palavra separado por um espaço (normalmente): "$*" equivale a "$1 $2 $3 ..." @ é expandido para os parâmetros posicionais, iniciando de 1. Se a expansão ocorre entre aspas duplas, cada parâmetro é expandido para uma palavra separada: "$@" equivale a "$1" "$2" "$3"... A expansão de parâmetros, vista na aula anterior, também se aplica aos parâmetros posicionais. Finalmente podemos formalizar a noção de uma variável da shell: uma variável é um parâmetro denotado por um nome. Uma variável tem um valor e zero ou mais atributos, que são definidos com o comando declare. Um parâmetro é set se a ele foi atribuído um valor. A string nula ("") é um valor válido, e uma variável pode ser unset somente com o comando unset.
scripts

Scripts e Funções - como nascem, se alimentam e se reproduzem

Comecemos pelo mais simples, que são scripts. Um script nada mais é do que um arquivo texto com comandos por interpretar e executar. Só isso, nada mais. Por convenção, (alguns) arquivos binários são identificados por um "número mágico", enquanto arquivos texto que devem ser interpretados contém, na primeira linha, o caminho completo para o interpretador. "man file" para os detalhes. No caso de scripts para Bash, a primeira linha deve conter #!/bin/bash Parâmetros podem ser passados para o interpretador, junto com o seu caminho completo. O "#!" é conhecido por "hash-bang" numa certa comunidade e os valores dos caracteres '#' e '!' formam o número mágico de scripts. Se o arquivo texto que contém o script for tornado executável (chmod a+x), então o script torna-se um "programa" que pode ser invocado diretamente, enunciando-se seu nome, caso o arquivo esteja num dos caminhos em PATH. Há outras maneiras de invocar um script; voltaremos a elas em breve. Vamos ao nosso primeiro, e naturalmente bobo, exemplo. Os números à esquerda não fazem parte do corpo do script. ----move.sh---- 1 #!/bin/bash 2 3 ### move.sh fonte destino 4 5 ## auxilio na depuracao 6 # set -x ## seja verboso 7 # set -e ## aborte em caso de erro 8 9 fnte=$1 ## primeiro argumento 10 11 dest=$2 ## segundo argumento 12 13 cp -i ${fnte} ${dest} ## nao sobreescreve destino 14 15 rm -i ${fnte} ## nao apaga sem perguntar 16 17 exit 0 ## avisa que tudo terminou bem ----fim do script---- Linhas e seus significados: 1: identifica o interpretador, que é /bin/bash 3: comentário descrevendo a função do script 6: set -x "liga" o modo verboso e todos os comandos são mostrados na tela ao executar -- excelente na depuração de scripts 7: set -e "liga" o modo paranoico, e qualquer erro interrompe a execução do script 9: o primeiro parâmetro do script "$1" é atribuído à variável "fnte" 11: o segundo parâmetro do script "$2" é atribuído à variável "dest" 13: copia da fonte para o destino, em modo cuidadoso (-i) 15: remove o arquivo fonte, em modo cuidadoso (-i) 17: retorna indicando sucesso Copie o texto para um arquivo chamado move.sh e remova os números das linhas empregando cut para removê-las. Transforme move.sh num executável: prompt: chmod u+x move.sh prompt: touch XXXX YYYY prompt: ls prompt: move.sh YYYY XXXX prompt: ls Se Bash reclamar dizendo que não encontra o programa move.sh, você deve acrescentar o diretório corrente à sua variável PATH: prompt: export PATH=${PATH}:. e então tentar novamente os comandos acima. Remova o comentário da linha 6 (ligando o -x) e repita os comandos acima, a partir do touch. Qual é o efeito? Retorne o comentário da linha 6, remova o comentário da linha 7 (ligando o -e), e diga: prompt: move.sh YYYY XXXX O que ocorreu? Qual o problema? Quando este comportamento é desejável? Exercício: altere o script, nas linhas 13 a 17, e empregue uma AND-OR-list para efetuar a movimentação de forma segura e informar ao usuário a razão para a falha, se for o caso. As outras maneiras de executar um script são usando a shell explicitamente: prompt: bash move.sh XXXX YYYY ou através do comando source, ou sua abreviatura "."; note o ponto separado por um espaço: prompt: source move.sh XXXX YYYY prompt: . move.sh XXXX YYYY A primeira versão permite que, enquanto se executa Bash, outra shell (sh, csh, tcsh) possa ser invocada para interpretar um script escrito para aquela shell. Algo como "csh script_para_csh.sh" num terminal que executa Bash. A segunda versão é usada para executar/interpretar um script escrito para a "shell nativa", Bash no nosso caso. A segunda versão também é o modo de enriquecer o ambiente de execução de Bash; se o argumento de source é um arquivo com definições de variáveis e funções, estas são acrescentadas ao ambiente. Se você ainda não o fez, crie um diretório bin em seu HOME e armazene neste diretório todos os scripts que lhe sejam convenientes. Não esqueça de acrescentar $HOME/bin ao seu PATH. Um script pode conter qualquer sequência de comandos Bash e/ou invocação de quaisquer programas. Scripts podem simplificar enormemente tarefas monótonas e repetitivas, além de aliviar nossos preciosos punhos de muita digitação desnecessária. Não é uma boa ideia nomear scripts com nomes de comandos oficiais. Se quiser acrescentar opções aos comandos oficiais, use funções, mas isso nem sempre é útil. Por exemplo, alterei minha definição de ls e frequentemente encontro problemas quando executo scripts que dependem do ls por causa da modificação que introduzi. Seis de um ou meia dúzia do outro. Eis um exemplo algo mais interessante. O script abaixo foi usado como parte de um trabalho de pesquisa com memórias cache para aplicações embarcadas. Uma série grande de simulações era executada com muitos parâmetros e cada simulação produzia um arquivo, cujo nome era composto do nome do programa simulado, dos parâmetros de projeto da cache e sufixo .din, porque o simulador chama-se dinero. O script gera a sequência de parâmetros da cache numa determinada ordem para produzir gráficos com as ordenadas e abcissas apropriadas. O script extract.sh extrai os dados relevantes do resultado da simulação. Seu conteúdo não é importante no momento. Os nomes dos arquivos são distintos daqueles da aula passada. ----cacheSz.sh---- 1 #!/bin/bash 2 3 apl=$1 # primeiro argumento 4 W=WwAn 5 B=16 # block size 6 7 for C in 1 4 16 ; do # capacity [kbytes] 8 for A in 1 2 4 8 ; do # associativity 9 F=${apl}_${C}${A}${B}.din # file name with parameters 10 if [ -n $F ] ; then # -s 11 echo '#' $F 12 echo " " extract.sh $C $A $B $F # extract results 13 fi 14 done 15 echo 16 done ----fim do script---- Linhas e seus significados: 3: atribui o parâmetro $1 à variável apl (aplicativo); 4,5: define uma variável que não varia, neste exemplo; 7: o laço itera sobre a capacidade da cache; 8: o laço itera sobre a associatividade da cache; 9: a atribuição gera o nome do arquivo de dados; 10: o teste, com -n, verifica que o arquivo da simulação existe; 11: o echo emite o comentário com o nome do arquivo simulado; e 12: o comando (sem o echo) extrai os dados do arquivo simulado. O script é invocado com o nome do programa simulado e produz uma tabela com o nome do programa simulado, comentado, seguido dos valores simulados --que não são computados pelo script porque os arquivos de dados e aplicativos não estão disponíveis. A saída é mostrada abaixo. prompt: ./cacheSz.sh prog # prog_1_1_16.din extract.sh 1 1 16 prog_1_1_16.din # prog_1_2_16.din extract.sh 1 2 16 prog_1_2_16.din # prog_1_4_16.din extract.sh 1 4 16 prog_1_4_16.din # prog_1_8_16.din extract.sh 1 8 16 prog_1_8_16.din # prog_4_1_16.din extract.sh 4 1 16 prog_4_1_16.din # prog_4_2_16.din extract.sh 4 2 16 prog_4_2_16.din # prog_4_4_16.din extract.sh 4 4 16 prog_4_4_16.din # prog_4_8_16.din extract.sh 4 8 16 prog_4_8_16.din # prog_16_1_16.din extract.sh 16 1 16 prog_16_1_16.din # prog_16_2_16.din extract.sh 16 2 16 prog_16_2_16.din # prog_16_4_16.din extract.sh 16 4 16 prog_16_4_16.din # prog_16_8_16.din extract.sh 16 8 16 prog_16_8_16.din Os valores extraídos dos arquivos de simulação são impressos na saída padrão de cacheSz.sh. O teste da linha 10 evita que o script falhe caso alguma das simulações esteja faltando. tear

Funções

Finalmente, funções. As funções em Bash são similares às funções de C, exceto que as variáveis do programa, ou do ambiente, são todas visíveis para a função, e vice-versa. Variáveis locais à função devem ser declaradas explicitamente com o comando local. Os argumentos da função são seus parâmetros posicionais e o parâmetro # é ajustado para refletir o número de argumentos. Quando a função termina, os parâmetros posicionais retornam aos seus valores originais no programa que invocou a função. Funções podem ser recursivas e não há limite para o número de chamadas recursivas. Abaixo estão algumas das funções que tenho definidas no meu ~/.bashrc. O arquivo ~/.bashrc é o resource configuration file para Bash e pode conter definições de variáveis e de funções para configurar o ambiente de execução. Este arquivo é um exemplo de um dot-file porque seu nome inicia por um '.' e não é mostrado normalmente pelo programa ls. "ls -a" mostra todos os arquivos, inclusive os dot-files. Descubra o que estas fazem antes de executá-las. # funcoes para listar arquivos e diretorios function ls() { command ls -F "$@" ; } function ll() { command ls -F -l -g "$@" ; } function la() { command ls -F -a "$@" ; } function lr() { command ls -F -R "$@" ; } function lt() { command ls -F -t "$@" ; } function lth() { command ls -F -t "$@" | head -10 ; } function lh() { command ls -F -t "$@" | head -5 ; } A função "ls" pode ser problemática; por que? lth e lh mostram uma lista de arquivos; qual a utilidade delas? # abreviaturas para alterar permissoes de arquivos/diretorios function cr() { chmod a+r "$@" ; } function cw() { chmod u+w "$@" ; } function cx() { chmod a+x "$@" ; } # remove arquivos temporarios do LaTeX function clr() { command rm -f *.{dvi,log,aux,blg,bbl,toc,lof,idx} ; } O exemplo abaixo é mais interessante; a função m descobre se o argumento é um arquivo ou um diretório e o exibe corretamente. Uso: "m A" ou "m .". # `m' looks at parameter/s and does all needed to display its contents. function m() { for file in "$@" ; do if [ ! $# = 1 ] ; # more than one file; add separator then echo ------- $file ------- ; echo fi ; if [ -d $file ] ; # directory then ls -F $file else if [ "${file%.gz}x" = "${file}x" ] then more $file # file not compressed else gzcat $file | more # file compressed fi ; fi ; if [ ! $# = 1 ] ; # more than one file; add blank line then echo fi ; done ; } Outro exemplo: a função usage imprime na tela as opções de uso de um determinado script. Caso alguma das opções de linha de comando seja inválida, a mensagem é exibida na tela, e possivelmente, o programa é terminado. no topo do arquivo com o script usage() { cat << EOF usage: $0 [options] source.c creates {prog,data}.bin to be input by textbench OPTIONS: -h Show this message -O n Optimization level, defaults to n=1 {0,1,2,3,s} -v Verbose, creates memory map: source.map -n when verbose, display register numbers instead of names -W Pass -Wall on to GCC -mif Generate output file ROM.mif for Altera's FPGAs -syn Compile for synthesis, else for simulation -new Automagically update all addresses (packageMemory.vhd changed) EOF no corpo do script if [ $# = 0 ] ; then usage ; exit 1 ; fi # termine se não há argumento(s) ... if [ ${inp}.c != $1 ] ; then # termine se o primeiro argumento não é um .c usage ; echo " invalid option: $1"; exit 1 ; fi Desafio 2: na primeira aula, você foi desafiada a escrever um embaralhador de textos com a finalidade de dificultar a vida dos espiões yankees que trabalham para se apossar do nosso petróleo, bem como de nativos traidores entreguistas como o Senador José Cerra (PSDB-SP), desesperados para entregar nosso petróleo. Escreva um script que tenta decifrar as mensagens que foram cifradas com a solução do primeiro desafio. Use força bruta. Seu script recebe como entrada um arquivo cifrado e produz um arquivo decifrado, ou um arquivo vazio. Use uma função que verifica se uma palavra, potencialmente decifrada, pertence a um conjunto de palavras que necessariamente estariam numa mensagem sobre exploração de petróleo, tais como: campo, libra, lula, óleo, plataforma, petroleiro, profundidade, pressão, pré-sal, produção, refinaria, vazão.
Resumo
  1. Apelidos
  2. Variáveis
  3. Ambiente
  4. test
  5. Quoting
  6. Listas
  7. if
  8. for
  9. while
  10. case
  11. Parâmetros (ou argumentos)
  12. Scripts
  13. Funções
  14. No que funções de Bash diferem de funções em C?

Exercícios 1) Adicione ao script m() um teste para mostrar arquivos comprimidos com bzip2. 2) O que faz o script abaixo? ----script---- #!/bin/bash mv ${1} ${1}-iso iconv --from-code=ISO-8859-1 --to-code=UTF-8 ${1}-iso > ${1} ----fim do script---- 3) O que fazem as funções abaixo? function sfx() { date "+%y%b%d%H%M%S" |tr A-Z a-z ; } function mdt() { date "+%y%b%d" |tr A-Z a-z ; } 4) O que fazem os trechos de código abaixo? if [ -a ARQ ] ; then if [ -w ARQ ] ; then rm ARQ ; fi fi for N in * ; do echo $N ; done for N in * ; do if [ -x $N ] ; then echo $N ; fi done for N in 1 2 3 4 5 6 7 8 9 10 ; do if [ $(( $N % 2 )) -eq 0 ] ; then echo "$N par" ; else echo "$N impar" ; fi done 4) Escreva dois laços que imprimem uma matriz de 5x5; 5) Escreva dois laços que imprimem uma matriz de 5x5, cujos elementos são todos zero; 6) Escreva dois laços que imprimem uma matriz de 5x5, cujos elementos são todos zero, exceto os da diagonal, cujos valores são a soma dos números de linha e da coluna. 7) Modifique o script move.sh para que receba um arquivo como argumento e o copie para o diretório /tmp. 8) O script abaixo mostra a diferença entre "$*" e "$@". Verifique-o, considerando as opções de um programa/script: -a -b e -a 12 -b [obrigado Bruno Ribas!] ----teste.sh---- #!/bin/bash echo "Usando \"\$*\"" for i in "$*" ; do echo "==>$i" done echo "Usando \"\$@\"" for i in "$@" ; do echo "==>$i" done ----fim do script---- 9) Você mantém uma cópia de segurança do seu diretório de trabalho no subdiretório chamado zOLD. Acidentalmente, alguns arquivos com sufixo ".tex" foram copiados de outro lugar para o diretório de trabalho e você não sabe quais são os arquivos válidos (mais recentes e/ou maiores). Em desespero você tenta verificar cada arquivo contra sua cópia de segurança, conferindo as datas de criação e os tamanhos. Veja se o comando abaixo resolve seu problema. Se não, conserte-o. cd zOLD for F in *.tex ; do echo -e "\n\n\n$F\n" ## para que serve este? if [ ./$F -nt ../$F ] ; then /bin/ls -lf $F ../$F read Y ## para que serve este? fi done Avançado: como alternativa ao 'if' pode-se usar "diff -a -bE -q ./$F ../$F". Esta é uma boa alternativa ao teste com o 'ls'? Justifique. 10) Melhore o comando acima para que ele avise se algum dos arquivos existe num diretório e não no outro. 11) O professor pede ao administrador de sistema que crie um diretório para cada aluno de ci064, no diretório em que residem as páginas web. As restrições que o administrador deve obedecer são: (i) não criar diretórios que já existem; (ii) não deixar nenhum aluno de fora; (iii) os diretórios devem pertencer ao usuário; e (iv) diretórios devem estar com as permissões corretas. Escreva um programa que cria os diretórios conforme especificado, completando o esqueleto abaixo. Você não tem autoridade para criar diretórios em /home e nem mudar as permissões de diretórios de outros usuários. Ao invés de executar os comandos, mostre o echo dos comandos: echo "mkdir $D". Troque xxx, yyy, zzz por valores adequados. for A in "todos em /home/bcc/" ; do D=/home/html/inf/$A if "$D nao existe" ; then echo "mkdir $D" echo "chown xxx:yyy $D" echo "chmod zzz $D" fi done 12) Difícil e desafiador. Escreva uma série de comandos para verificar a honestidade da afirmação abaixo: Um "pipeline" é uma sequência de comandos separados por '|'. Uma lista é uma sequência de um ou mais pipelines, separados por um dentre ';', '&', '&&' ou '||' e opcionalmente terminados por ';', '&' ou ENTER. Sua tarefa é escrever uma série de listas para verificar se a afirmação é implementada corretamente. Não ignore nenhuma combinação de operadores. --fim da aula--