LINUX USER
Papo de Botequim
Curso de Shell Script
Papo de Botequim Você não agüenta mais aquele seu
porque, em inglês, Shell significa concha, carapaça, isto é, fica entre o usuário e o sistema operacional, de forma que tudo que interage com o sistema operacional, tem que passar pelo seu crivo.
amigo usuário de Linux enchendo o seu saco com aquela história de que o sistema é fantástico e o Shell é uma
O ambiente Shell
ferramenta maravilhosa? A partir desta edição vai ficar mais fácil entender o porquê deste entusiasmo... POR JULIO CEZAR NEVES
D
iálogo entreouvido em uma mesa de um botequim, entre um usuário de Linux e um empurrador de mouse: • Quem é o Bash? • É o filho caçula da família Shell. • Pô cara! Estás a fim de me deixar maluco? Eu tinha uma dúvida e você me deixa com duas! • Não, maluco você já é há muito tempo: desde que decidiu usar aquele sistema operacional que você precisa reiniciar dez vezes por dia e ainda por cima não tem domínio nenhum sobre o que esta acontecendo no seu computador. Mas deixa isso prá lá, pois vou te explicar o que é Shell e os componentes de sua família e ao final da nossa conversa você dirá: “Meu Deus do Shell! Porque eu não optei pelo Linux antes?”.
O ambiente Linux Para você entender o que é e como funciona o Shell, primeiro vou te mostrar como funciona o ambiente em camadas do Linux. Dê uma olhada no gráfico mostrado na Figura 1. Neste gráfico podemos ver que a camada de hardware é a mais profunda e é formada pelos componentes físicos do seu computador. Em torno dela, vem a camada do kernel que é o cerne do Linux, seu núcleo, e é quem põe o hardware para funcionar, fazendo seu gerenciamento e controle. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar as tarefas para que foram desenvolvidos. Fechando tudo isso vem o Shell, que leva este nome
Bom já que para chegar ao núcleo do Linux, no seu kernel que é o que interessa a todo aplicativo, é necessária a filtragem do Shell, vamos entender como ele funciona de forma a tirar o máximo proveito das inúmeras facilidades que ele nos oferece. O Linux, por definição, é um sistema multiusuário – não podemos nunca nos esquecer disto – e para permitir o acesso de determinados usuários e barrar a entrada de outros, existe um arquivo chamado /etc/passwd, que além de fornecer dados para esta função de “leão-de-chácara” do Linux, também provê informações para o início de uma sessão (ou “login”, para os íntimos) daqueles que passaram por esta primeira barreira. O último campo de seus registros informa ao sistema qual é o Shell que a pessoa vai receber ao iniciar sua sessão. Lembra que eu te falei de Shell, família, irmão? Pois é, vamos começar a entender isto: o Shell é a conceituação de concha envolvendo o sistema operacional propriamente dito, é o nome genérico para tratar os filhos desta idéia que, ao longo dos muitos anos de exis-
Quadro 1: Uma rapidinha nos principais sabores de Shell Bourne Shell (sh): Desenvolvido por Stephen Bourne do Bell Labs (da AT&T, onde também foi desenvolvido o Unix), foi durante muitos anos o Shell padrão do sistema operacional Unix. É também chamado de Standard Shell por ter sido durante vários anos o único, e é até hoje o mais utilizado. Foi portado para praticamente todos os ambientes Unix e distribuições Linux. Korn Shell (ksh): Desenvolvido por David Korn, também do Bell Labs, é um superconjunto do sh, isto é, possui todas as facilidades
82
Agosto 2004
do sh e a elas agregou muitas outras. A compatibilidade total com o sh vem trazendo muitos usuários e programadores de Shell para este ambiente. Boune Again Shell (bash): Desenvolvido inicialmente por Brian Fox e Chet Ramey, este é o Shell do projeto GNU. O número de seus adeptos é o que mais cresce em todo o mundo, seja por que ele é o Shell padrão do Linux, seja por sua grande diversidade de comandos, que incorpora inclusive diversos comandos característicos do C Shell.
www.linuxmagazine.com.br
C Shell (csh): Desenvolvido por Bill Joy, da Universidade de Berkley, é o Shell mais utilizado em ambientes BSD. Foi ele quem introduziu o histórico de comandos. A estruturação de seus comandos é bem similar à da linguagem C. Seu grande pecado foi ignorar a compatibilidade com o sh, partindo por um caminho próprio. Além destes Shells existem outros, mas irei falar somente sobre os três primeiros, tratando-os genericamente por Shell e assinalando as especificidades de cada um.
Papo de Botequim
tência do sistema operacional Unix, foram aparecendo. Atualmente existem diversos sabores de Shell (veja Quadro 1 na página anterior).
Como funciona o Shell O Shell é o primeiro programa que você ganha ao iniciar sua sessão (se quisermos assassinar a língua portuguesa podemos também dizer “ao se logar”) no Linux. É ele quem vai resolver um monte de coisas de forma a não onerar o kernel com tarefas repetitivas, poupando-o para tratar assuntos mais nobres. Como cada usuário possui o seu próprio Shell interpondo-se entre ele e o Linux, é o Shell quem interpreta os comandos digitados e examina as suas sintaxes, passando-os esmiuçados para execução. • Êpa! Esse negócio de interpretar comando não tem nada a ver com interpretador não, né? • Tem sim: na verdade o Shell é um interpretador que traz consigo uma poderosa linguagem com comandos de alto nível, que permite construção de loops, de tomadas de decisão e de armazenamento de valores em variáveis, como vou te mostrar. • Vou explicar as principais tarefas que o Shell cumpre, na sua ordem de execução. Preste atenção, porque esta ordem é fundamental para o entendimento do resto do nosso bate papo.
Análise da linha de comando Neste exame o Shell identifica os caracteres especiais (reservados) que têm significado para a interpretação da linha e logo em seguida verifica se a linha passada é um comando ou uma atribuição de valores, que são os ítens que vou descrever a seguir.
Comando Quando um comando é digitado no “prompt” (ou linha de comando) do Linux, ele é dividido em partes, separadas por espaços em branco: a primeira parte é o nome do programa, cuja existência será verificada; em seguida, nesta ordem, vêm as opções/parâmetros, redirecionamentos e variáveis. Quando o programa identificado existe, o Shell verifica as permissões dos arquivos en-
Com que Shell eu vou? Quando digo que o último campo do arquivo /etc/passwd informa ao sistema qual é o Shell que o usuário vai usar ao se “logar”, isto deve ser interpretado ao pé-da-letra. Se este campo do seu registro contém o termo prog, ao acessar o sistema o usuário executará o programa prog. Ao término da execução, a sessão do usuário se encerra automaticamente. Imagine quanto se pode incrementar a segurança com este simples artifício.
volvidos (inclusive o próprio programa), e retorna um erro caso o usuário que chamou o programa não esteja autorizado a executar esta tarefa. $ ls linux linux
Neste exemplo o Shell identificou o ls como um programa e o linux como um parâmetro passado para o programa ls.
Se o Shell encontra dois campos separados por um sinal de igual (=) sem espaços em branco entre eles, ele identifica esta seqüência como uma atribuição. $ valor=1000
Neste caso, por não haver espaços em branco (que é um dos caracteres reservados), o Shell identificou uma atribuição e colocou 1000 na variável valor.
Resolução de Redirecionamentos Após identificar os componentes da linha que você digitou, o Shell parte para a resolução de redirecionamentos. O Shell tem incorporado ao seu elenco de habilidades o que chamamos de
Figura 1: Ambiente em camadas de um sistema Linux
redirecionamento, que pode ser de entrada (stdin), de saída (stdout) ou dos erros (stderr), conforme vou explicar a seguir. Mas antes precisamos falar de...
Substituição de Variáveis Neste ponto, o Shell verifica se as eventuais variáveis (parâmetros começados por $), encontradas no escopo do comando, estão definidas e as substitui por seus valores atuais.
Substituição de MetaCaracteres Se algum meta-caracter (ou “coringa”, como *, ? ou []) for encontrado na linha de comando, ele será substituído por seus possíveis valores. Supondo que o único item no seu diretório corrente cujo nome começa com a letra n seja um diretório chamado nomegrandeprachuchu, se você fizer: $ cd n*
Atribuição
Shell Programas e Comandos Núcleo ou Kernel Hardware
LINUX USER
como até aqui quem está manipulando a linha de comando ainda é o Shell e o programa cd ainda não foi executado, o Shell expande o n* para nomegrandeprachuchu (a única possibilidade válida) e executa o comando cd com sucesso.
Entrega da linha de comando para o kernel Completadas todas as tarefas anteriores, o Shell monta a linha de comando, já com todas as substituições feitas e chama o kernel para executá-la em um novo Shell (Shell filho), que ganha um número de processo (PID ou Process IDentification) e fica inativo, tirando uma soneca durante a execução do programa. Uma vez encerrado este processo (e o Shell filho), o “Shell pai” recebe novamente o controle e exibe um “prompt”, mostrando que está pronto para executar outros comandos.
Cuidado na Atribuição Jamais faça:
$ valor = 1000 bash: valor: not found Neste caso, o Bash achou a palavra valor isolada por espaços e julgou que você estivesse mandando executar um programa chamado valor, para o qual estaria passando dois parâmetros: = e 1000.
www.linuxmagazine.com.br
Agosto 2004
83
LINUX USER
Papo de Botequim
Decifrando a Pedra de Roseta Para tirar aquela sensação que você tem quando vê um script Shell, que mais parece uma sopa de letrinhas ou um conjunto de hieróglifos, vou lhe mostrar os principais caracteres especiais para que você saia por aí como Champollion decifrando a Pedra de Roseta.
Caracteres para remoção do significado. É isso mesmo, quando não desejamos que o Shell interprete um caractere específico, devemos “escondê-lo” dele. Isso pode ser feito de três maneiras diferentes, cada uma com sua peculiaridade: • Apóstrofo (´): quando o Shell vê uma cadeia de caracteres entre apóstrofos, ele retira os apóstrofos da cadeia e não interpreta seu conteúdo. $ ls linuxm* linuxmagazine $ ls 'linuxm*' bash: linuxm* no such file U or directory
No primeiro caso o Shell “expandiu” o asterisco e descobriu o arquivo linuxmagazine para listar. No segundo, os apóstrofos inibiram a interpretação do Shell e veio a resposta que não existe o arquivo linuxm*. • Contrabarra ou Barra Invertida (\): idêntico aos apóstrofos exceto que a barra invertida inibe a interpretação somente do caractere que a segue. Suponha que você, acidentalmente, tenha criado um arquivo chamado * (asterisco) – o que alguns sabores de Unix permitem – e deseja removê-lo. Se você fizesse: $ rm *
Você estaria na maior encrenca, pois o rm removeria todos os arquivos do diretório corrente. A melhor forma de fazer o serviço é: $ rm \*
Desta forma, o Shell não interpreta o asterisco, evitando a sua expansão. Faça a seguinte experiência científica: $ cd /etc $ echo '*'
84
Agosto 2004
$ echo \* $ echo *
Viu a diferença? • Aspas (“): exatamente iguais ao apóstrofo, exceto que, se a cadeia entre aspas contiver um cifrão ($), uma crase (`), ou uma barra invertida (\), estes caracteres serão interpretados pelo Shell. Não precisa se estressar, eu não te dei exemplos do uso das aspas por que você ainda não conhece o cifrão ($) nem a crase (`). Daqui para frente veremos com muita constância o uso destes caracteres especiais; o mais importante é entender seu significado.
Caracteres de redirecionamento A maioria dos comandos tem uma entrada, uma saída e pode gerar erros. Esta entrada é chamada Entrada Padrão ou stdin e seu dispositivo padrão é o teclado do terminal. Analogamente, a saída do comando é chamada Saída Padrão ou stdout e seu dispositivo padrão é a tela do terminal. Para a tela também são enviadas normalmente as mensagens de erro oriundas dos comandos, chamada neste caso de Saída de Erro Padrão ou stderr. Veremos agora como alterar este estado de coisas. Vamos fazer um programa gago. Para isto digite (tecle “Enter” ao final de cada linha – comandos do usuário são ilustrados em negrito): $ cat E-e-eu sou gago. Vai encarar? E-e-eu sou gago. Vai encarar?
O cat é um comando que lista o conteúdo do arquivo especificado para a Saída Padrão (stdout). Caso a entrada não seja definida, ele espera os dados da stdin (a entrada padrão). Ora como eu não especifiquei a entrada, ele a está
Redirecionamento Perigoso Como já havia dito, o Shell resolve a linha e depois manda o comando para a execução. Assim, se você redirecionar a saída de um arquivo para ele próprio, primeiramente o Shell “esvazia”este arquivo e depois manda o comando para execução! Desta forma, para sua alegria, você acabou de perder o conteúdo de seu querido arquivo.
www.linuxmagazine.com.br
esperando pelo teclado (Entrada Padrão) e como também não citei a saída, o que eu teclar irá para a tela (Saída Padrão), criando desta forma – como eu havia proposto – um programa gago. Experimente!
Redirecionamentop da Saída Padrão Para especificarmos a saída de um programa usamos o símbolo “>” ou o “>>”, seguido do nome do arquivo para o qual se deseja mandar a saída. Vamos transformar o programa anterior em um “editor de textos”: $ cat > Arq
O cat continua sem ter a entrada especificada, portanto está aguardando que os dados sejam teclados, porém a sua saída está sendo desviada para o arquivo Arq. Assim sendo, tudo que esta sendo teclado esta indo para dentro de Arq, de forma que fizemos o editor de textos mais curto e ruim do planeta. Se eu fizer novamente: $ cat > Arq
Os dados contidos em Arq serão perdidos, já que antes do redirecionamento o Shell criará um Arq vazio. Para colocar mais informações no final do arquivo eu deveria ter feito: $ cat >> Arq
Redirecionamento da Saída de Erro Padrão Assim como por padrão o Shell recebe os dados do teclado e envia a saída para a tela, os erros também vão para a tela se você não especificar para onde eles devem ser enviados. Para redirecionar os erros, use 2> SaidaDeErro. Note que entre o número 2 e o sinal de maior (>) não existe espaço em branco. Vamos supor que durante a execução de um script você pode, ou não (dependendo do rumo tomado pela execução do programa), ter criado um arquivo chamado /tmp/seraqueexiste$$. Como não quer ficar com sujeira no disco rígido, ao final do script você coloca a linha a seguir: rm /tmp/seraqueexiste$$
Papo de Botequim
Dados ou Erros? Preste atenção! Não confunda >> com 2>. O primeiro anexa dados ao final de um arquivo, e o segundo redireciona a Saída de Erro Padrão (stderr) para um arquivo que está sendo designado. Isto é importante!
Caso o arquivo não existisse seria enviado para a tela uma mensagem de erro. Para que isso não aconteça faça: rm /tmp/seraqueexiste$$ 2> U /dev/null
Para que você teste a Saída de Erro Padrão direto no prompt do seu Shell, vou dar mais um exemplo. Faça: $ ls naoexiste bash: naoexiste no such file U or directory $ ls naoexiste 2> arquivodeerros $ $ cat arquivodeerros bash: naoexiste no such file U or directory
Neste exemplo, vimos que quando fizemos um ls em naoexiste, ganhamos uma mensagem de erro. Após redirecionar a Saída de Erro Padrão para arquivodeerros e executar o mesmo comando, recebemos somente o “prompt” na tela. Quando listamos o conteúdo do arquivo para o qual foi redirecionada a Saída de Erro Padrão, vimos que a mensagem de erro tinha sido armazenada nele. É interessante notar que estes caracteres de redirecionamento são cumulativos, isto é, se no exemplo anterior fizéssemos o seguinte: $ ls naoexiste 2>> U arquivodeerros
a mensagem de erro oriunda do ls seria anexada ao final de arquivodeerros.
Redirecionamento da Entrada Padrão Para fazermos o redirecionamento da Entrada Padrão usamos o < (menor que). “E pra que serve isso?”, você vai me perguntar. Deixa eu dar um exemplo, que você vai entender rapidinho. Suponha que você queira mandar um mail para o seu chefe. Para o chefe nós
caprichamos, né? Então ao invés de sair redigindo o mail direto no “prompt”, de forma a tornar impossível a correção de uma frase anterior onde, sem querer, você escreveu um “nós vai”, você edita um arquivo com o conteúdo da mensagem e após umas quinze verificações sem constatar nenhum erro, decide enviá-lo e para tal faz: $ mail chefe@chefia.com.br < U arquivocommailparaochefe
e o chefe receberá uma mensagem com o conteúdo do arquivocommailparaochefe. Outro tipo de redirecionamento “muito louco” que o Shell permite é o chamado “here document”. Ele é representado por << e serve para indicar ao Shell que o escopo de um comando começa na linha seguinte e termina quando encontra uma linha cujo conteúdo seja unicamente o “label” que segue o sinal <<. Veja o fragmento de script a seguir, com uma rotina de ftp: ftp -ivn hostremoto << fimftp user $Usuario $Senha binary get arquivoremoto fimftp
neste pedacinho de programa temos um monte de detalhes interessantes: • As opções usadas para o ftp (-ivn) servem para ele listar tudo que está acontecendo (opção -v de “verbose”), para não ficar perguntando se você tem certeza que deseja transmitir cada arquivo (opção -i de “interactive”) e finalmente a opção -n serve para dizer ao ftp para ele não solicitar o usuário e sua senha, pois estes serão informados pela instrução específica (user); • Quando eu usei o << fimftp, estava dizendo o seguinte para o interpretador: “Olha aqui Shell, não se meta em
Direito de Posse O $$ contém o PID,isto é,o número do seu processo. Como o Linux é multiusuário,é bom anexar sempre o $$ ao nome dos seus arquivos para não haver problema de propriedade,isto é,caso você batizasse o seu arquivo simplesmente como seraqueexiste,a primeira pessoa que o usasse (criando-o então) seria o seu dono e a segunda ganharia um erro quando tentasse gravar algo nele.
LINUX USER
Etiquetas Erradas Um erro comum no uso de labels (como o fimftp do exemplo anterior) é causado pela presença de espaços em branco antes ou após o mesmo. Fique muito atento quanto a isso, por que este tipo de erro costuma dar uma boa surra no programador, até que seja detectado. Lembre-se: um label que se preze tem que ter uma linha inteira só para ele.
nada a partir deste ponto até encontrar o ‘label’ fimftp. Você não entenderia droga nenhuma, já que são instruções específicas do ftp”. Se fosse só isso seria simples, mas pelo próprio exemplo dá para ver que existem duas variáveis ($Usuario e $Senha), que o Shell vai resolver antes do redirecionamento. Mas a grande vantagem deste tipo de construção é que ela permite que comandos também sejam interpretados dentro do escopo do “here document”, o que, aliás, contraria o que acabei de dizer. Logo a seguir te explico como esse negócio funciona. Agora ainda não dá, estão faltando ferramentas. • O comando user é do repertório de instruções do ftp e serve para passar o usuário e a senha que haviam sido lidos em uma rotina anterior a este fragmento de código e colocados respectivamente nas duas variáveis: $Usuario e $Senha. • O binary é outra instrução do ftp, que serve para indicar que a transferência de arquivoremoto será feita em modo binário, isto é o conteúdo do arquivo não será inteerpretado para saber se está em ASCII, EBCDIC, … • O comando get arquivoremoto diz ao cliente ftp para pegar este arquivo no servidor hostremoto e trazê-lo para a nossa máquina local. Se quiséssemos enviar um arquivo, bastaria usar, por exemplo, o comando put arquivolocal.
Redirecionamento de comandos Os redirecionamentos de que falamos até agora sempre se referiam a arquivos, isto é, mandavam para arquivo, recebiam de arquivo, simulavam arquivo local, … O que veremos a partir de agora, redireciona a saída de um comando para a entrada de outro. É utilíssimo e, apesar de não ser macaco gordo, sempre quebra os
www.linuxmagazine.com.br
Agosto 2004
85
Papo de Botequim
maiores galhos. Seu nome é “pipe” (que em inglês significa tubo, já que ele canaliza a saída de um comando para a entrada de outro) e sua representação é a | (barra vertical). $ ls | wc -l 21
O comando ls passou a lista de arquivos para o comando wc, que quando está com a opção -l conta a quantidade de linhas que recebeu. Desta forma, podemos afirmar categoricamente que no meu diretório existiam 21 arquivos. $ cat /etc/passwd | sort | lp
A linha de comandos acima manda a listagem do arquivo /etc/passwd para a entrada do comando sort. Este a classifica e envia para o lp que é o gerenciador da fila de impressão.
Caracteres de ambiente Quando queremos priorizar uma expressão, nós a colocamos entre parênteses, não é? Pois é, por causa da aritmética é normal pensarmos deste jeito. Mas em Shell o que prioriza mesmo são as crases (`) e não os parênteses. Vou dar exemplos para você entender melhor. Eu quero saber quantos usuários estão “logados” no computador que eu administro. Eu posso fazer: $ who | wc -l 8
O comando who passa a lista de usuários conectados ao sistema para o comando wc -l, que conta quantas linhas recebeu e mostra a resposta na tela. Muito bem, mas ao invés de ter um número oito solto na tela, o que eu quero mesmo é que ele esteja no meio de uma frase. Ora, para mandar frases para a tela eu só preciso usar o comando echo; então vamos ver como é que fica:
Buraco Negro Em Unix existe um arquivo fantasma. Chama-se /dev/null.Tudo que é enviado para este arquivo some. Assemelha-se a um Buraco Negro. No caso do exemplo, como não me interessava guardar a possível mensagem de erro oriunda do comando rm, redirecionei-a para este arquivo.
86
Agosto 2004
$ echo "Existem who | wc -l U usuarios conectados" Existem who | wc -l usuarios U conectados
Hi! Olha só, não funcionou! É mesmo, não funcionou e não foi por causa das aspas que eu coloquei, mas sim por que eu teria que ter executado o who | wc -l antes do echo. Para resolver este problema, tenho que priorizar a segunda parte do comando com o uso de crases: $ echo "Existem `who | wc -l` U usuarios conectados" Existem 8 usuarios U conectados
Para eliminar esse monte de brancos antes do 8 que o wc -l produziu, basta retirar as aspas. Assim: $ echo Existem `who | wc -l` U usuarios conectados Existem 8 usuarios conectados
As aspas protegem da interpretação do Shell tudo que está dentro dos seus limites. Como para o Shell basta um espaço em branco como separador, o monte de espaços será trocado por um único após a retirada das aspas. Outra coisa interessante é o uso do ponto-e-vírgula. Quando estiver no Shell, você deve sempre dar um comando em cada linha. Para agrupar comandos em uma mesma linha, temos que separá-los por ponto-e-vírgula. Então: $ pwd ; cd /etc; pwd ;cd -;pwd /home/meudir /etc /home/meudir
Neste exemplo, listei o nome do diretório corrente com o comando pwd, mudei para o diretório /etc, novamente listei o nome do diretório e finalmente voltei para o diretório onde estava anteriormente (cd -), listando seu nome. Repare que coloquei o ponto-e-vírgula de todas as formas possíveis, para mostrar que não importa se existem espaços em branco antes ou após este caracter. Finalmente, vamos ver o caso dos parênteses. No exemplo a seguir, colocamos diversos comandos separados por ponto-e-vírgula entre parênteses:
www.linuxmagazine.com.br
$ (pwd ; cd /etc ; pwd) /home/meudir /etc $ pwd /home/meudir
“Quequeiiisso” minha gente? Eu estava no /home/meudir, mudei para o /etc, constatei que estava neste diretório com o pwd seguinte e quando o agrupamento de comandos terminou, eu vi que continuava no /etc/meudir! Hi! Será que tem coisa do mágico Mandrake por aí? Nada disso. O interessante do uso de parênteses é que eles invocam um novo Shell para executar os comandos que estão em seu interior. Desta forma, fomos realmente para o diretório /etc, porém após a execução de todos os comandos, o novo Shell que estava no diretório /etc morreu e retornamos ao Shell anterior que estava em /home/meudir. Que tal usar nossos novos conceitos? $ mail suporte@linux.br << FIM Ola suporte, hoje as `date U “+%hh:mm”` ocorreu novamente U aquele problema que eu havia U reportado por telefone. De U acordo com seu pedido segue a U listagem do diretorio: `ls -l` Abracos a todos. FIM
Finalmente agora podemos demonstrar o que conversamos anteriormente sobre “here document”. Os comandos entre crases tem prioridade, portanto o Shell os executará antes do redirecionamento do “here document”. Quando o suporte receber a mensagem, verá que os comandos date e ls foram executados antes do comando mail, recebendo então um instantâneo do ambiente no momento de envio do email. - Garçom, passa a régua! ■
SOBRE O AUTOR
LINUX USER
Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando fez parte da equipe que desenvolveu o SOX, sistema operacional, similar ao Unix, da Cobra Computadores. É professor do curso de Mestrado em Software Livre das Faculdades Estácio de Sá, no Rio de Janeiro.
Papo de Botequim
LINUX USER
Curso de Shell Script
Papo de Botequim - Parte II Nossos personagens voltam à mesa do bar para discutir expressões regulares e colocar a “mão na massa” pela primeira vez, construindo um aplicativo simples para catalogar uma coleção de CDs. POR JÚLIO CÉSAR NEVES
arçom! Traz um “chops” e dois “pastel”. O meu amigo hoje não vai beber porque está finalmente sendo apresentado a um verdadeiro sistema operacional, e ainda tem muita coisa a aprender! – E então, amigo, tá entendendo tudo que te expliquei até agora? – Entendendo eu tô, mas não vi nada prático nisso… – Calma rapaz, o que te falei até agora serve como base ao que há de vir daqui pra frente. Vamos usar essas ferramentas que vimos para montar programas estruturados. Você verá porque até na TV já teve programa chamado “O Shell é o Limite”. Para começar vamos falar dos comandos da família grep – Grep? Não conheço nenhum termo em inglês com este nome… – É claro, grep é um acrônimo (sigla) para Global Regular Expression Print, que usa expressões regulares para pesquisar a ocorrência de cadeias de caracteres na entrada definida. Por falar em expressões regulares (ou regexp), o Aurélio Marinho Jargas escreveu dois artigos [1 e 2] imperdíveis para a Revista do Linux sobre esse assunto e também publicou um livro [3] pela Editora Novatec. Acho bom você ler esses artigos, eles vão te ajudar no que está para vir.
G
Eu fico com grep,você com gripe Esse negócio de gripe é brincadeira, só um pretexto para pedir umas caipirinhas. Eu te falei que o grep procura cadeias de caracteres dentro de uma entrada definida, mas o que vem a ser uma “entrada definida”? Bem, existem várias formas de definir a entrada do comando grep. Veja só. Para pesquisar em um arquivo:
$ grep franklin /etc/passwd
Pesquisando em vários arquivos: $ grep grep *.sh
Pesquisando na saída de um comando: $ who | grep carvalho
No 1º exemplo, procurei a palavra franklin em qualquer lugar do arquivo /etc/passwd. Se quisesse procurar um nome de usuário, isto é, somente no início dos registros desse arquivo, poderia digitar $ grep ‘^franklin’ /etc/passwd. “E para que servem o circunflexo e os apóstrofos?”, você vai me perguntar. Se tivesse lido os artigos que mencionei, saberia que o circunflexo serve para limitar a pesquisa ao início de cada linha e os apóstrofos servem para o Shell não interpretar esse circunflexo, deixando-o passar incólume para o comando grep. No 2º exemplo mandei listar todas as
linhas que usavam a palavra grep, em todos os arquivos terminados em .sh. Como uso essa extensão para definir meus arquivos com programas em Shell, malandramente, o que fiz foi listar as linhas dos programas que poderia usar como exemplo do comando grep. Olha que legal! O grep aceita como entrada a saída de outro comando, redirecionado por um pipe (isso é muito comum em Shell e é um tremendo acelerador da execução de comandos). Dessa forma, no 3° exemplo, o comando who listou as pessoas “logadas” na mesma máquina que você (não se esqueça jamais: o Linux é multiusuário) e o grep foi usado para verificar se o Carvalho estava trabalhando ou “coçando”. O grep é um comando muito conhecido, pois é usado com muita freqüência. O que muitas pessoas não sabem é que existem três comandos na família grep: grep, egrep e fgrep. A principais diferenças entre os 3 são: • grep - Pode ou não usar expressões regulares simples, porém no caso de não usá-las, o fgrep é melhor, por ser mais rápido. • egrep (“e” de extended, estendido) - É muito poderoso no uso de expressões regulares. Por ser o mais poderoso dos três, só deve ser usado quando for necessária a elaboração de uma expressão regular não aceita pelo grep. • fgrep (“f” de fast, rápido) - Como o nome diz, é o ligeirinho da família, executando o serviço de forma muito veloz (por vezes é cerca de 30% mais rápido que o grep e 50% mais que o egrep), porém não permite o uso de expressões regulares na pesquisa. –Agora que você já conhece as diferenças entre os membros da família, me diga: o que você acha dos três exemplos que eu dei antes das explicações? – Achei que o fgrep resolveria o teu problema mais rapidamente que o grep. – Perfeito! Tô vendo que você está atento, entendendo tudo que estou te explicando! Vamos ver mais exemplos
www.linuxmagazine.com.br
Setembro 2004
87
Papo de Botequim
LINUX USER
Quadro 1 - Listando subdiretórios $ ls -l | grep drwxr-xr-x drwxr-xr-x drwxr-xr-x drwxr-xr-x drwxr-xr-x drwxrwxr-x drwxrwxr-x drwxrwxr-x drwxr-xr-x drwxrwxr-x drwxr-xr-x drwxr-xr-x
‘^d’ 3 11 3 3 2 14 12 3 3 3 3 3
root root root root root root root root root root root root
root root root root root root root root root root root root
para clarear de vez as diferenças de uso entre os membros da família. Eu sei que em um arquivo qualquer existe um texto falando sobre Linux, só não tenho certeza se está escrito com L maiúsculo ou minúsculo. Posso fazer uma busca de duas formas: egrep (Linux | linux) arquivo.txt
ou então:
Dec Jul Oct Aug Aug Jul Jan Jan Jul Jan Dec Jun
18 2000 doc 13 18:58 freeciv 17 2000 gimp 8 2000 gnome 8 2000 idl 13 18:58 locale 14 2000 lyx 17 2000 pixmaps 2 20:30 scribus 17 2000 sounds 18 2000 xine 19 2000 xplns
-l). Os apóstrofos foram usados para o Shell não “ver” o circunflexo. Vamos ver mais um. Veja na Tabela 1 as quatro primeiras posições possíveis da saída de um ls -l em um arquivo comum (não é diretório, nem link, nem …). Para descobrir todos os arquivos executáveis em um determinado diretório eu poderia fazer: $ ls -la | egrep ‘^-..(x|s)’
grep [Ll]inux arquivo.txt
No primeiro caso, a expressão regular complexa (Linux | linux) usa os parênteses para agrupar as opções e a barra vertical (|) é usada como um “ou” (or, em inglês) lógico, isto é, estou procurando Linux ou linux. No segundo, a expressão regular [Ll]inux significa: começado por L ou l seguido de inux. Como esta é uma expressão simples, o grep consegue resolvê-la, por isso é melhor usar a segunda forma, já que o egrep tornaria a pesquisa mais lenta. Outro exemplo. Para listar todos os subdiretórios do diretório corrente, basta usar o comando $ ls -l | grep ‘^d’. Veja o resultado no Quadro 1. No exemplo, o circunflexo (^) serviu para limitar a pesquisa à primeira posição da saída do ls longo (parâmetro
Tabela 1 Posição
Valores possíveis
1ª
-
2ª
r ou -
3ª
w ou -
4ª
x,s(suid) ou -
88
4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096
Setembro 2004
novamente usamos o circunflexo para limitar a pesquisa ao início de cada linha, ou seja, listamos as linhas que começam por um traço (-), seguido de qualquer coisa (o ponto), novamente seguido de qualquer coisa, e por fim um x ou um s. Obteríamos o mesmo resultado se usássemos o comando: $ ls -la | grep ‘^-..[xs]’
e além disso, agilizaríamos a pesquisa.
A “CDteca” Vamos começar a desenvolver programas! Creio que a montagem de um banco de dados de músicas é bacana para efeito didático (e útil nestes tempos de downloads de arquivos MP3 e queimadores de CDs). Não se esqueça que, da mesma forma que vamos desenvolver um monte de programas para organizar os seus CDs de música, com pequenas adaptações você pode fazer o mesmo para organizar os CDs de software que vêm com a Linux Magazine e outros que você compra ou queima, e disponibilizar esse banco de software para todos os que trabalham com você (o Linux é multiusuário, e como tal deve
www.linuxmagazine.com.br
ser explorado). – Péra aí! De onde eu vou receber os dados dos CDs? – Vou mostrar como o programa pode receber parâmetros de quem o estiver executando e, em breve, ensinarei a ler os dados da tela ou de um arquivo.
Passando parâmetros Veja abaixo a estrutura do arquivo contendo a lista das músicas: nomedoálbum^intérprete1~nomeU damúsica1:...:intérpreten~nomeU damúsican
Isto é, o nome do álbum será separado por um circunflexo do resto do registro, formado por diversos grupos compostos pelo intérprete de cada música do CD e a música interpretada. Estes grupos são separados entre si por dois pontos (:) e, internamente, o intérprete será separado por um til (~) do nome da música. Quero escrever um programa chamado musinc, que incluirá registros no meu arquivo músicas. Passarei cada álbum como parâmetro para o programa: $ musinc “álbum^interprete~U musica:interprete~musica:...”
Desta forma, musinc estará recebendo os dados de cada álbum como se fosse uma variável. A única diferença entre um parâmetro recebido e uma variável é que os primeiros recebem nomes numéricos (o que quis dizer é que seus nomes são formados somente por um algarismo, isto é, $1, $2, $3, …, $9). Vamos, fazer mais alguns testes: $ cat teste #!/bin/bash #Teste de passagem echo “1o. parm -> echo “2o. parm -> echo “3o. parm ->
de parametros $1” $2” $3”
Agora vamos rodar esse programinha: $ teste passando parametros para U testar bash: teste: cannot execute
Ops! Esqueci-me de tornar o script executável. Vou fazer isso e testar novamente o programa:
Papo de Botequim
$ chmod 755 teste $ teste passando parametros para U testar 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para
Repare que a palavra testar, que seria o quarto parâmetro, não foi listada. Isso ocorreu porque o programa teste só lista os três primeiros parâmetros recebidos. Vamos executá-lo de outra forma: $ teste “passando parametros” U para testar 1o. parm -> passando parametros 2o. parm -> para 3o. parm -> testar
As aspas não deixaram o Shell ver o espaço em branco entre as duas primeiras palavras, e elas foram consideradas como um único parâmetro. E falando em passagem de parâmetros, uma dica: veja na Tabela 2 algumas variáveis especiais. Vamos alterar o programa teste para usar as novas variáveis: $ cat teste #!/bin/bash # Programa para testar passagem U de parametros (2a. Versao) echo O programa $0 recebeu $# U parametros echo “1o. parm -> $1” echo “2o. parm -> $2” echo “3o. parm -> $3” echo Para listar todos de uma U \”tacada\” eu faco $*
Listagem 1: Incluindo CDs na “CDTeca” $ cat musinc #!/bin/bash # Cadastra CDs (versao 1) # echo $1 >> musicas
Listagem 2 $ cat musinc #!/bin/bash # Cadastra CDs (versao 2) # echo $1 >> musicas sort -o musicas musicas
Execute o programa: $ teste passando parametros para testar O programa teste recebeu 4 U parametros 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para Para listar todos de uma U “tacada” eu faco passando U parametros para testar
Repare que antes das aspas usei uma barra invertida, para escondê-las da interpretação do Shell (se não usasse as contrabarras as aspas não apareceriam). Como disse, os parâmetros recebem números de 1 a 9, mas isso não significa que não posso usar mais de nove parâmetros. Significa que só posso endereçar nove. Vamos testar isso: $ cat teste #!/bin/bash # Programa para testar passagem U de parametros (3a. Versao) echo O programa $0 recebeu $# U parametros echo “11o. parm -> $11” shift echo “2o. parm -> $1” shift 2 echo “4o. parm -> $1”
Execute o programa:
LINUX USER
inclusão de CDs no meu banco chamado musicas. O programa é muito simples (como tudo em Shell). Veja a Listagem 1. O script é simples e funcional; limitome a anexar ao fim do arquivo musicas o parâmetro recebido. Vamos cadastrar 3 álbuns para ver se funciona (para não ficar “enchendo lingüiça,” suponho que em cada CD só existem duas músicas): $ musinc “album3^Artista5U ~Musica5:Artista6~Musica5” $ musinc “album1^Artista1U ~Musica1:Artista2~Musica2” $ musinc “album 2^Artista3U ~Musica3:Artista4~Musica4”
Listando o conteúdo do arquivo musicas: $ cat musicas album3^Artista5~Musica5:Artista6U ~Musica6 album1^Artista1~Musica1:Artista2U ~Musica2 album2^Artista3~Musica3:Artista4U ~Musica4
Podia ter ficado melhor. Os álbuns estão fora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois testálo novamente. Veja a listagem 2. Simplesmente inseri uma linha que classifica o arquivo musicas, redirecionando a saída para ele mesmo (para isso serve a opção -o), após cada álbum ser anexado.
$ teste passando parametros para U testar O programa teste recebeu 4 U parametros que são: 11o. parm -> passando1 2o. parm -> parametros 4o. parm -> testar
$ cat musicas album1^Artista1~Musica1:Artista2U ~Musica2 albu2^Artista3~Musica3:Artista4U ~Musica4 album3^Artista5~Musica5:Artista6U ~Musica6
Duas coisas muito interessantes aconteceram neste script. Para mostrar que os nomes dos parâmetros variam de $1 a $9 digitei echo $11 e o que aconteceu? O Shell interpretou como sendo $1 seguido do algarismo 1 e listou passando1; O comando shift, cuja sintaxe é shift n, podendo o n assumir qualquer valor numérico, despreza os n primeiros parâmetros, tornando o parâmetro de ordem n+1. Bem, agora que você já sabe sobre passagem de parâmetros, vamos voltar à nossa “cdteca” para fazer o script de
Oba! Agora o programa está legal e quase funcional. Ficará muito melhor em uma nova versão, que desenvolveremos após aprender a adquirir os dados da tela e formatar a entrada.
Tabela 2: Variáveis especiais Variável
Significado
$0
Contém o nome do programa
$#
Contém a quantidade de parâmetros passados
$*
Contém o conjunto de todos os parâmetros (muito parecido com $@)
www.linuxmagazine.com.br
Setembro 2004
89
Papo de Botequim
Ficar listando arquivos com o comando cat não está com nada, vamos fazer um programa chamado muslist para listar um álbum, cujo nome será passado como parâmetro. Veja o código na Listagem 3: Vamos executá-lo, procurando pelo album 2. Como já vimos antes, para passar a string album 2 é necessário protegê-la da interpretação do Shell, para que ele não a interprete como dois parâmetros. Exemplo: $ muslist “album 2” grep: can’t open 2 musicas: album1^Artista1~Musica1U :Artista2~Musica2 musicas: album2^Artista3~Musica3U :Artista4~Musica4 musicas:album3^Artista5~Musica5U :Artista6~Musica6
Que lambança! Onde está o erro? Eu tive o cuidado de colocar o parâmetro passado entre aspas para o Shell não o dividir em dois! É, mas repare como o grep está sendo executado: grep $1 musicas
Mesmo colocando álbum 2 entre aspas, para que fosse encarado como um único parâmetro, quando o $1 foi passado pelo Shell para o comando grep, transformouse em dois argumentos. Dessa forma, o conteúdo da linha que o grep executou foi o seguinte: grep album 2 musicas
Como a sintaxe do grep é:
Listagem 3 - muslist $ cat muslist #!/bin/bash # Consulta CDs (versao 1) # grep $1 musicas
Listagem 4 muslist melhorado $ cat muslist #!/bin/bash # Consulta CDs (versao 2) # grep -i “$1” musicas
90
Setembro 2004
Listagem 5 - musexc
grep <cadeia de caracteres> U [arq1, arq2, ..., arqn]
O grep entendeu que deveria procurar a cadeia de caracteres album nos arquivos 2 e musicas. Como o arquivo 2 não existe, grep gerou o erro e, por encontrar a palavra album em todos os registros de musicas, listou a todos. É melhor ignorarmos maiúsculas e minúsculas na pesquisa. Resolveremos os dois problemas com a Listagem 4. Nesse caso, usamos a opção -i do grep que, como já vimos, serve para ignorar maiúsculas e minúsculas, e colocamos o $1 entre aspas, para que o grep continuasse a ver a cadeia de caracteres resultante da expansão da linha pelo Shell como um único argumento de pesquisa. $ muslist “album 2” album2^Artista3~Musica3:Artista4U ~Musica4
Agora repare que o grep localiza a cadeia pesquisada em qualquer lugar do registro; então, da forma que estamos fazendo, podemos pesquisar por álbum, por música, por intérprete e mais. Quando conhecermos os comandos condicionais, montaremos uma nova versão de muslist que permitirá especificar por qual campo pesquisar. Ah! Em um dia de verão você foi à praia, esqueceu os CDs no carro, aquele “solzinho” de 40 graus empenou seu disco favorito e agora você precisa de uma ferramenta para removê-lo do banco de dados? Não tem problema, vamos desenvolver um script chamado musexc, para excluir estes CDs. Antes de desenvolver o “bacalho”, quero te apresentar a uma opção bastante útil da família de comandos grep. É a opção -v, que quando usada lista todos os registros da entrada, exceto o(s) localizado(s) pelo comando. Exemplos: $ grep -v “album 2” musicas album1^Artista1~Musica1:Artista2U ~Musica2 album3^Artista5~Musica5:Artista6U ~Musica6
Conforme expliquei antes, o grep do exemplo listou todos os registros de musicas exceto o referente a album 2, porque atendia ao argumento do co-
www.linuxmagazine.com.br
$ cat musexc #!/bin/bash # Exclui CDs (versao 1) # grep -v “$1” musicas > /tmp/mus$$ mv -f /tmp/mus$$ musicas
mando. Estamos então prontos para desenvolver o script para remover CDs empenados da sua “CDteca”. Veja o código da Listagem 5. Na primeira linha mandei para /tmp/mus$$ o arquivo musicas, sem os registros que atendessem a consulta feita pelo comando grep. Em seguida, movi /tmp/mus$$ por cima do antigo musicas. Usei o arquivo /tmp/mus$$ como arquivo de trabalho porque, como já havia citado no artigo anterior, o $$ contém o PID (identificação do processo) e, dessa forma, cada um que editar o arquivo musicas o fará em um arquivo de trabalho diferente, evitando colisões. Os programas que fizemos até aqui ainda são muito simples, devido à falta de ferramentas que ainda temos. Mas é bom praticar os exemplos dados porque, eu prometo, chegaremos a desenvolver um sistema bacana para controle dos seus CDs. Na próxima vez que nos encontrarmos, vou te ensinar como funcionam os comandos condicionais e aprimoraremos mais um pouco esses scripts. Por hoje chega! Já falei demais e estou de goela seca! Garçom! Mais um sem colarinho! ■
INFORMAÇÕES [1] http://www.revistadolinux.com.br/ed/003/ ferramentas.php3 [2] http://www.revistadolinux.com.br/ed/007/ ereg.php3 [3] http://www.aurelio.net/er/livro/
SOBRE O AUTOR
LINUX USER
Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando fez parte da equipe que desenvolveu o SOX, um sistema operacional similar ao Unix, produzido pela Cobra Computadores.
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
LINUXUSER
CursodeShellScript
Papode botequimIII Umchopinho,umaperitivoeopapocontinua.Destavezvamosaprender algunscomandosdemanipulaçãodecadeiasdecaracteres,queserãomuito úteisnahoradeincrementarnossa“CDteca”.PORJULIOCEZARNEVES
G
arçon! traga dois chopes por favor que hoje eu vou ter que falar muito. Primeiro quero mostrar uns programinhas simples deusaremuitoúteis,comoocut,que é usado para cortar um determinado pedaço de um arquivo. A sintaxe e algunsexemplosdeusopodemservistosnoQuadro1: Como dá para ver, existem quatro sintaxesdistintas:naprimeira(-c1-5) especifiquei uma faixa, na segunda (-c -6) especifiquei todo o texto até umaposição,naterceira(-c4-)tudode umadeterminadaposiçãoemdiantee naquarta(-c1,3,5,7,9),sóasposições determinadas. A última possibilidade (-c-3,5,8-)foisóparamostrarquepodemosmisturartudo. Mas não pense que acabou por aí! Como você deve ter percebido, esta formadecutémuitoútilparalidarcom arquivoscomcamposdetamanhofixo, masatualmenteoquemaisexistesão arquivoscomcamposdetamanhovariável,ondecadacampoterminacomum delimitador.Vamosdarumaolhadano arquivomusicasquecomeçamosaprepararnaúltimavezqueviemosaquino botequim.VejaoQuadro2.
Então, recapitulando, o layout do arquivo é o seguinte: nome do álbum^intérprete1~nomedamúsica1:...: intérpreten~nomedamúsican,istoé,o nomedoálbumseráseparadoporum circunflexo(^)dorestodoregistro,que éformadopordiversosgruposcompostos pelo intérprete de cada música do CDearespectivamúsicainterpretada. Estesgrupossãoseparadosentresipor dois-pontos(:)eointérpreteseráseparadodonomedamúsicaporumtil(~). Então,parapegarosdadosreferentes atodasassegundasmúsicasdoarquivo musicas,devemosdigitar: $ cut -f2 -d: musicas Artista2~(usica2 Artista4~(usica4 Artista6~(usica5 Artista8~(usica8@10_L:
Ou seja, cortamos o segundo campo z(-fdefield,campoeminglês)delimitado (-d) por dois-pontos (:). Mas, se quisermossomenteosintérpretes,devemosdigitar: $ cut -f2 -d: musicas | cut U -f1 -d~
Artista2 Artista4 Artista6 Artista8
Paraentendermelhorisso,vamosanalisaraprimeiralinhademusicas: $ head -1 musicas album 1^Artista1~(usica1: U Artista2~(usica2
Entãoobserveoquefoifeito: album 1^Artista1~(usica1: U Artista2~(usica2
Destaforma,noprimeirocutoprimeiro campo do delimitador (-d) dois-pontos(:)éalbum1^Artista1~Musica1eo segundo, que é o que nos interessa, é Artista2~Musica2. Vamos então ver o queaconteceunosegundocut: Artista2~(usica2
Agora,primeirocampododelimitador (-d)til(~),queéoquenosinteressa, é Artista2 e o segundo é Musica2. Se o raciocínio que fizemos para a pri-
����������������������������� www.linuxmagazine.com.br
Outubro2004
85
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
Quadro1–Ocomandocut Asintaxedocuté:cut-cPosIni-PosFim [arquivo],ondePosIniéaposiçãoinicial,e PosFimaposiçãofinal.Vejaosexemplos: $ cat numeros 1234567890 0987654321 1234554321 9876556789 $ cut -c1-5 numeros 12345 09876 12345 98765 $ cut -c-6 numeros 123456 098765 123455 987655 $ cut -c4- numeros 4567890 7654321 4554321 6556789 $ cut -c1,3,5,7,9 numeros 13579 08642 13542 97568 $ cut -c -3,5,8- numeros 1235890 0986321 1235321 9875789
Quadro2–Oarquivo musicas $ cat musicas album 1^Artista1~(usica1:U Artista2~(usica2 album 2^Artista3~(usica3:U Artista4~(usica4 album 3^Artista5~(usica5:U Artista6~(usica5 album 4^Artista7~(usica7:U Artista8~(usica8
meiralinhaforaplicadoaorestantedo arquivo, chegaremos à resposta anteriormentedada.Outrocomandomuito interessanteéotrqueserveparasubstituir,comprimirouremovercaracteres. Suasintaxesegueoseguintepadrão: tr [opções] cadeia1 [cadeia2]
O comando copia o texto da entrada padrão(stdin),trocaasocorrênciados caracteres de cadeia1 pelo seu correspondentenacadeia2outrocamúltiplas ocorrências dos caracteres de cadeia1 por somente um caracter, ou ainda caracteres da cadeia1. As principais opções do comando são mostradas na Tabela1. Primeirovejaumexemplobembobo: $ echo bobo | tr o a baba
Istoé,troqueitodasasocorrênciasda letra o pela letra a. Suponha que em determinado ponto do meu script eu peça ao operador para digitar s ou n (sim ou não), e guardo sua resposta na variável $Resp. Ora, o conteúdo de $Resp pode conter letras maiúsculas ouminúsculas,edestaformaeuteria quefazerdiversostestesparasaberse arespostadadafoiS,s,Noun.Entãoo melhoréfazer: $ Resp=$ echo $Resp | tr S) sn
e após este comando eu teria certeza de que o conteúdo de $Resp seria um s ou um n. Se o meu arquivo ArqEnt estátodoemletrasmaiúsculasedesejo passá-lasparaminúsculaseufaço: $ tr A-Z a-z < ArqEnt > / tmp/$$ $ m -f /tmp/$$ ArqEnt
Note que neste caso usei a notação AZparanãoescreverABCD…YZ.Outro tipodenotaçãoquepodeserusadasão as escape sequences (como eu traduziria? Seqüências de escape? Meio sem sentido,né?Masválá…)quetambém sãoreconhecidasporoutroscomandos etambémnalinguagemC,ecujosignificadovocêveránaTabela2: Deixa eu te contar um “causo”: um alunoqueestavadanadocomigoresolveucomplicarminhavidaecomores-
����������������������������� 86
Outubro2004
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
posta a um exercício prático, valendo nota, que passei ele me entregou um script com todos os comandos separados por ponto-e-vírgula (lembre-se queoponto-e-vírgulaserveparaseparardiversoscomandosemumamesma linha). Vou dar um exemplo simplificado,eidiota,deumscriptassim: $ cat confuso echo leia Programação Shell U Linu do Julio Cezar )e es U > li ro;cat li ro;p d;ls;rm U -f li ro2>/de /null;cd ~
Euexecuteioprogramaeelefuncionou: $ confuso leia Programação Shell Linu U do Julio Cezar )e es /home/jne es/L( confuso li ro muse c musicas musinc muslist numeros
Masnotadeprovaécoisaséria(enota de dólar é mais ainda) então, para entender o que o aluno havia feito, o chameieemsuafrentedigitei: $ tr ”;” ”\n” < confuso echo leia Programação Shell U Linu do Julio Cezar )e es p d cd ~ ls -l rm -f li o 2>/de /null
Ocaraficoumuitodesapontado,porque em dois ou três segundos eu desfiz a gozaçãoqueeleperdeuhorasparafazer. Maspresteatenção!Seeuestivesseem umamáquinaUnix,euteriadigitado: $ tr ”;” ”\012” < confuso
Agoravejaadiferençaentreoresultado deumcomandodateexecutadohojee outroexecutadoháduassemanas: Sun Sep 19 14:59:54 Sun Sep 5 10:12:33
2004 2004
Notouoespaçoextraapóso“Sep”na segunda linha? Para pegar a hora eu deveriadigitar: $ date | cut -f 4 -d ’ ’ 14:59:54
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
Mas há duas semanas ocorreria o seguinte: $ date | cut -f 4 -d ’ ’ 5
Isto porque existem 2 caracteres em brancoantesdo5(dia).Entãooideal seriatransformarosespaçosembranco consecutivos em somente um espaço parapodertratarosdoisresultadosdo comandodatedamesmaforma,eisso sefazassim: $ date | tr -s ” ” Sun Sep 5 10:12:33 2004
Eagoraeupossocortar: $ date | tr -s ” ” | cut -f 4 U -d ” ” 10:12:33
OlhasócomooShellestáquebrandoo galho.Vejaoconteúdodeumarquivo baixadodeumamáquinaWindows: $ cat - e ArqDoDOS.t t Este arqui o^($ foi gerado pelo^($ DOS/Win e foi^($ bai ado por um^($ ftp mal feito.^($
Dica:aopção-vdocatmostraoscaracteresdecontroleinvisíveis,comanotação ^L, onde ^ é a tecla Control e L é arespectivaletra.Aopção-emostrao finaldalinhacomoumcifrão($). IstoocorreporquenoDOSofimdos registros é indicado por um Carriage Return (\r – Retorno de Carro, CR) e umLineFeed(\f–AvançodeLinha,ou LF). No Linux porém o final do registroéindicadosomentepeloLineFeed. Vamoslimparestearquivo: $ tr -d ’\r’ < ArqDoDOS.t t > /tmp/$$ $ m -f /tmp/$$ ArqDoDOS.t t
Agoravamosveroqueaconteceu: $ cat - e ArqDoDOS.t t Este arqui o$ foi gerado pelo$ DOS/R in e foi$ bai ado por um$ ftp mal feito.$
Bemaopção-ddotrremovedoarquivo todasasocorrênciasdocaractereespecificado. Desta forma eu removi os caracteres indesejados, salvei o texto emumarquivotemporárioeposteriormenterenomeei-oparaonomeoriginal. Umaobservação:emumsistemaUnix eudeveriadigitar:
LINUXUSER
Evejatambémodate: $ date (on Sep 20 10:47:19 BRT 2004
Repare que o mês e o dia estão no mesmo formato em ambos os comandos.Ora,seemalgumregistrodowho eunãoencontraradatadehoje,ésinal $ tr -d ’\015’ < ArqDoDOS.U que o usuário está “logado” há mais t t > /tmp/$$ de um dia, já que ele não pode ter se “logado” amanhã… Então vamos guarUmadica:oproblemacomostermina- dar o pedaço que importa da data de dores de linha (CR/LF) só aconteceu hojeparadepoisprocurá-lanasaídado porque a transferência do arquivo foi comandowho: feita no modo binário (ou image), Se antesdatransmissãodoarquivotivesse $ Data=$ date | cut -f 2-3 U sidoestipuladaaopçãoasciidoftp,isto -d’ ’ nãoteriaocorrido. –Olha,depoisdestadicatôcomeçandoa Eu usei a construção $(...), para priogostardestetaldeshell,masaindatem rizar a execução dos comandos antes muitacoisaquenãoconsigofazer. deatribuirasuasaídaàvariávelData. –Poisé,aindanãotefaleiquasenada Vamosversefuncionou: sobre programação em shell, ainda $ echo $Data tem muita coisa para aprender, mas Sep 20 comoqueaprendeu,jádápararesolvermuitosproblemas,desdequevocê adquirao“modoshelldepensar”.Você Beleza!Agora,oquetemosquefazeré seriacapazdefazerumscriptquediga procurarnocomandowhoosregistros quaispessoasestão“logadas”hámais quenãopossuemestadata. deumdianoseuservidor? –Claroquenão!Paraissoserianecessá- –A h! Eu acho que estou entendendo! Vocêfalouemprocuraremeocorreuo rioeuconheceroscomandoscondiciocomandogrep,estoucerto? naisquevocêaindanãomeexplicou como funcionam. - Deixa eu tentar –Certíssimo!Sóqueeutenhoqueusaro grepcomaquelaopçãoqueelesólista mudarumpoucoasualógicaetrazêlaparao“modoshelldepensar”,mas os registros nos quais ele não enconantesémelhortomarmosumchope. trou a cadeia. Você se lembra que Agoraquejámolheiapalavra,vamos opçãoéessa? resolveroproblemaquetepropus.Veja –Claro,a-v… comofuncionaocomandowho: –Isso!Táficandobom!Vamosver: $ ho jne es 1 rtorres 0 rlegaria 1 lcarlos 3
pts/ U Sep 18 pts/ U Sep 20 pts/ U Sep 20 pts/ U Sep 20
$ ho | grep - ”$Data” jne es pts/ U 1 Sep 18 13:40
13:40 07:01
Seeuquisesseumpoucomaisdeperfumariaeufariaassim:
08:19 $
ho | grep cut -f1 -d ’ ’ jne es
10:01
”$Data” |U
Tabela1–Ocomandotr Opção
Significado
-s
Comprimenocorrênciasde cadeia1emapenasuma
-d
Removeoscaracteresdecadeia1
Viu?Nãofoinecessáriousarcomando condicional, até porque o nosso comandocondicional,ofamosoif,não testa condição, mas sim instruções. Masantesvejaisso:
����������������������������� www.linuxmagazine.com.br
Outubro2004
87
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
Tabela2
$ ls musicas musicas $ echo $? 0 $ ls ArqIne istente ls: ArqIne istente: )o suchU file or director $ echo $? 1 $ ho | grep jne es jne es pts/1 Sep 18 U 13:40 10.2.4.144 $ echo $? 0 $ ho | grep juliana $ echo $? 1
Seqüência \t \n \v \f \r \\
Oqueéqueesse$?fazaí?Algocomeçadoporcifrão($)pareceserumavariável, certo? Sim é uma variável que contémocódigoderetornodaúltima instrução executada. Posso te garantirqueseestainstruçãofoibemsucedida,$?teráovalorzero,casocontrário seuvalorserádiferentedezero.Oque nosso comando condicional (if) faz é testarestavariável.Entãovamosvera suasintaxe: if cmd then cmd1 cmd2 cmdn else cmd3 cmd4 cmdm fi
Ouseja,casocomandocmdtenhasido executado com sucesso, os comandos doblocodothen(cmd1,cmd2ecmdn) serão executados, caso contrário, os comandos do bloco opcional do else (cmd3,cmd4ecmdm)serãoexecutados. Oblocodoiféterminandocomumfi. Vamosvernapráticacomoissofunciona,usandoumscriptqueincluiusuáriosnoarquivo/etc/passwd: $ cat incusu #!/bin/bash # Versão 1 if grep ^$1 /etc/pass d then echo Usuario \’$1\’U
Significado Tabulação Novalinha <ENTER> Tabulação Vertical Nova Página Inícioda linha<^M> Umabarra invertida
Outubro2004
\013 \014 \015 \0134
já e iste else if useradd $1 then echo Usuário \’$1\’ U incluído em /etc/pass d else echo ”Problemas no U cadastramento. Você é root?” fi fi
Repare que o if está testando direto o comandogrepeestaéasuafinalidade. Casooifsejabemsucedido,ouseja,o usuário (cujo nome está em $1) foi encontradoem/etc/passwd,oscomandosdoblocodothenserãoexecutados (neste exemplo, apenas o echo). Caso contrário,asinstruçõesdoblocodoelse serão executadas, quando um novo if testa se o comando useradd foi executadoacontento,criandooregistrodo usuário em /etc/passwd, ou exibindo umamensagemdeerro,casocontrário. Executar o programa e passe como parâmetroumusuáriojácadastrado: $ incusu jne es jne es: :54002:1001:Julio )e es: U /home/jne es:/bin/ U bash Usuario ’jne es’ ja e iste
No exemplo dado, surgiu uma linha indesejada, ela é a saída do comando grep. Para evitar que isso aconteça, devemosdesviarasaídapara/dev/null. Oprogramaficaassim: $ cat incusu #!/bin/bash # Versão 2 if grep ^$1 /etc/pass d > U
����������������������������� 88
Octal \011 \012
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
/de /null then echo Usuario \’$1\’ já U e iste else if useradd $1 then echo Usuário \’$1\’ U incluído em /etc/pass d else echo ”Problemas no U cadastramento. Você é root?” fi fi
Vamostestá-locomoumusuárionormal: $ incusu Ze)inguem ./incusu[6]: useradd: not found Problemas no cadastramento. U Você é root?
Aquelamensagemdeerronãodeveria aparecer! Para evitar isso, devemos redirecionarasaídadeerro(stderr)do comandouseraddpara/dev/null.Aversãofinalficaassim: $ cat incusu #!/bin/bash # Versão 3 if grep ^$1 /etc/pass d > U /de /null then echo Usuario \’$1\’ já U e iste else if useradd $1 2> /de /null then echo Usuário \’$1\’ U incluído em /etc/pass d else echo ”Problemas no U cadastramento. Você é root?” fi fi
Depoisdisso,vejamosocomportamento doprograma,seexecutadopeloroot: $ incusu botelho Usuário ’botelho’ incluido em U /etc/pass d
Enovamente: $ incusu botelho Usuário ’botelho’ já e iste
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam se aprimorando? Então vejamosagoracomopodemosmelhorar onossoprogramaparaincluirmúsicas na"CDTeca": $ cat musinc #!/bin/bash # Cadastra CDs ersao 3 # if grep ”^$1$” musicas > U /de /null then echo Este álbum já está U cadastrado else echo $1 >> musicas sort musicas -o musicas fi
Comovocêviu,éumapequenaevoluçãoemrelaçãoàversãoanterior.Antes de incluir um registro (que na versão anterior poderia ser duplicado), testamosseoregistrocomeça(^)etermina ($) de forma idêntica ao parâmetro álbum passado ($1). O circunflexo (^)
LINUXUSER
noiníciodacadeiaecifrão($)nofim, Como você viu, o programa melhoservem para testar se o parâmetro (o rouumpouquinho,masaindanãoestá álbumeseusdados)éexatamenteigual pronto. À medida que eu te ensinar a a algum registro já existente. Vamos programaremshell,nossaCDtecavai executar nosso programa novamente, ficarcadavezmelhor. masdestavezpassamoscomoparâme- –E ntendi tudo que você me explicou, troumálbumjácadastrado,pravero mas ainda não sei como fazer um if queacontece: para testar condições, ou seja o uso normaldocomando. $ musinc ”album 4^Artista7~ U –Para isso existe o comando test, que (usica7:Artista8~(usica8” testa condições. O comando if testa Este álbum já está cadastrado o comando test. Como já falei muito, precisodeunschopesparamolhara Eagoraumnãocadastrado: palavra. Vamos parar por aqui e na próxima vez te explico direitinho o $ musinc ”album 5^Artista9~ U usodotestedediversasoutrassinta(usica9:Artista10~(usica10” xesdoif. $ cat musicas –Falou! Acho bom mesmo porque eu album 1^Artista1~(usica1: U também já tô ficando zonzo e assim Artista2~(usica2 tenhotempoparapraticaressemonte album 2^Artista3~(usica3: U decoisasquevocêmefalouhoje. Artista4~(usica4 Parafixaroquevocêaprendeu,tente album 3^Artista5~(usica5: U fazerumscriptizinhoparainformarse Artista6~(usica5 um determinado usuário, cujo nome album 4^Artista7~(usica7: U será passado como parâmetro, está Artista8~(usica8 “logado”nosistemaounão. album 5^Artista9~(usica9: U –Aê Chico! traz mais dois chopes pra Artista10~(usica10 ■ mimporfavor…
����������������������������� www.linuxmagazine.com.br
Outubro2004
89
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
CursodeShellScript
Papode botequimIV DaveHamilton-www.sxc.hu
E
aícara,tentoufazeroexercício que te pedi em nosso último encontro? – Claro que sim! Em programação, se você não treinar não aprende. Você mepediuumscriptparainformarse umdeterminadousuário,cujonome serápassadocomoparâmetroparao script,está“logado”(arghh!)ounão. Fizoseguinte: $ cat logado #!/bin/bash # Pesquisa se um usuário está # logado ou não if ho | grep $1 then echo $1 está logado else echo $1 não está no pedaço fi
– Calma rapaz! Já vi que você chegou cheiodetesão.Primeirovamospedir os nossos chopes de praxe e depois vamosaoShell.Chico,trazdoischopes,umsemcolarinho! – Aaah!Agoraquejámolhamososnossosbicos,vamosdarumaolhadanos resultadosdoseuprograma:
Ogarçonjáperdeuacontadascervejas,eoassuntonãoacaba.Desta vezvamosaprenderatestarosmaisvariadostiposdecondições, parapodermoscontrolaraexecuçãodenossoprogramadeacordo comaentradafornecidapelousuário.PORJULIOCEZARNEVES
$ logado jne es jne es pts/0 12:02 10.2.4.144 jne es está logado
Oct 18 U
Realmente funcionou. Passei meu nome de usuário como parâmetro e eledissequeeuestavalogado,porém ele imprimiu uma linha extra, que eu não pedi, que é a saída do comando who. Para evitar que isso aconteça, é só mandá-la para o buraco negro do mundo UNIX, o /dev/null. Vejamos entãocomoficaria: $ cat logado #!/bin/bash # Pesquisa se uma pessoa está # logada ou não ersão 2 if ho | grep $1 > /de /null then echo $1 está logado else echo $1 não está no pedaço fi
edição04
Testes Todos estamos acostumados a usar o if para testar condições, e estas são sempre maior que, menor que, maior ouiguala,menorouiguala,igualae
Tabela1–Opçõesdo testparaarquivos Opção
Verdadeirose
-earq
arqexiste
-sarq
arqexisteetemtamanhomaiorquezero
-farq
arqexisteeéumarquivoregular
-darq
arqexisteeéumdiretório
-rarq
arqexisteecomdireitodeleitura
Agoravamosaostestes: $ logado jne es jne es está logado $ logado chico chico não está no pedaço
����������������������������� 84
Ah,agorasim!Lembre-sedessapegadinha:amaiorpartedoscomandostem umasaídapadrãoeumasaídadeerros (o grep é uma das poucas exceções: ele não exibe uma mensagem de erro quandonãoachaumacadeiadecaracteres)edevemosredirecioná-lasparao buraconegroquandonecessário. Bem,agoravamosmudardeassunto: na última vez que nos encontramos aquinobotequim,quandojáestávamos degoelaseca,vocêmeperguntoucomo setestamcondições.Paraisso,usamos ocomandotest
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
-warq
arqexisteecomdireitodeescrita
-xarq
arqexisteecomdireitodeexecução
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
Tabela2–Opçõesdotest paracadeiasdecaracteres Opção
Verdadeirose:
-zcadeia
Tamanhodecadeiaézero
-ncadeia
Tamanhodecadeiaémaiorquezero
cadeia
Acadeiacadeiatemtamanhomaiorquezero
c1=c2
Cadeiac1ec2sãoidênticas
diferente de. Para testar condições em ShellScriptusamosocomandotest,só queeleémuitomaispoderosodoque aquilo com que estamos acostumados. Primeiramente, veja na Tabela 1 as principais opções (existem muitas outras) para testar arquivos em disco enaTabela2asprincipaisopçõespara testedecadeiasdecaracteres.
Tabela3–Opçõesdo testparanúmeros Opção
Verdadeirose
Significado
n1-eqn2
n1en2sãoiguais
equal
n1-nen2
n1en2nãosãoiguais
notequal
n1-gtn2
n1émaiorquen2
greaterthan
n1-gen2
n1émaiorouigualan2
greaterorequal
n1-ltn2
n1émenorquen2
lessthan
n1-len2
n1émenorouigualan2
lessorequal
Pensaqueacabou?Enganoseu!Agora éhoradealgomaisfamiliar,asfamosas comparações com valores numéricos. Veja a Tabela 3, e some às opções já apresentadasosoperadoresdaTabela4. Ufa! Como você viu, tem coisa pra chuchu,eonossoifémuitomaispoderoso que o dos outros. Vamos ver em unsexemploscomoissotudofunciona. Testamosaexistênciadeumdiretório:
No exemplo, testei a existência do diretóriolmb.Senãoexistisse(else),ele seriacriado.Jásei,vocêvaicriticara minhalógicadizendoqueoscriptnão estáotimizado.Eusei,masqueriaque você o entendesse assim, para então poderusaro“ponto-de-espantação”(!) comoumnegadordotest.Vejasó: 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,eestanegativadeve-seaoponto deexclamação(!)precedendoaopção -d.Aofimdaexecuçãodessefragmento descript,comcertezaoprogramaestariadentrododiretóriolmb.Vamosver doisexemplosparaentenderadiferença nacomparaçãoentrenúmeroseentre cadeiasdecaracteres. cad1=1 cad2=01 if test $cad1 = $cad2 then echo As ariá eis são iguais. else echo As ariá eis são diferentes. fi
Executandoofragmentodeprograma acima,teremoscomoresultado: As
ariá eis são diferentes.
Vamosagoraalterá-loumpoucopara queacomparaçãosejanumérica:
if test -d lmb then cd lmb else mkdir lmb cd lmb fi
Tabela4 Operador
Finalidade
Parênteses()
0
Exclamação!
0
-a
0
-o
0
cadeia de caracteres “01” é realmente diferente de “1”. Porém, a coisa muda defiguraquandoasvariáveissãotestadasnumericamente,jáqueonúmero1 éigualaonúmero01. Paramostrarousodosconectores-o (ou)e-a(e),vejaumexemplo“animal”, programadodiretonopromptdoBash. Me desculpem os zoólogos, mas eu nãoentendonadadereino,filo,classe, ordem,família,gênero,espécieeoutras coisasdotipo,destaformaoqueestou chamandodefamíliaoudegênerotem grandechancedeestartotalecompletamenteincorreto: $ Familia=felinae $ Genero=gato $ if test $Familia = canidea U -a $Genero = lobo -o $Familia = U 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 dafamíliacanídeae(-a)dogênerolobo, ou(-o)dafamiliafelinae(-a)dogênero leão,seriadadoumalerta,casocontrárioamensagemseriadeincentivo. Atenção: Os sinais de maior (>) no início das linhas internas ao if são os prompts de continuação (que estão definidosnavariável$PS2).Quandoo shell identifica que um comando continuará na linha seguinte, automaticamenteelecolocaestecaractere,atéque ocomandosejaencerrado. Vamosmudaroexemploparaverseo programacontinuafuncionando:
cad1=1 cad2=01 if test $cad1 -eq $cad2 then echo As ariá eis são iguais. else echo As ariá eis são diferentes. fi
$ Familia=felino $ Genero=gato $ if test $Familia = felino -o U $Familia = canideo -a $Genero = U onça -o $Genero = lobo > then > echo Cuidado > else > echo Pode passar a mão > fi Cuidado
Evamosexecutá-lonovamente: As
ariá eis são iguais.
Comovocêviu,nasduasexecuções obtive resultados diferentes, porque a
LINUXUSER
Obviamente a operação resultou em erro,porqueaopção-atemprecedência
����������������������������� www.linuxmagazine.com.br
edição04
85
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
sobrea-oe,dessa,formaoquefoiavaliadoprimeirofoiaexpressão: $Familia = canideo -a $Genero = U onça
Que foi avaliada como falsa, retornandooseguinte:
Da mesma forma, para escolhermos CDs que tenham a participação do Artista1 e do Artista2, não é necessáriomontarumifcomoconector-o.O egrep também resolve isso para nós. Vejacomo: $ egrep musicas
Artista1|Artista2
U
mentealegibilidade,poisocomandoif iráficarcomasintaxesemelhanteàdas outraslinguagens;porisso,esseseráo modocomoocomandotestseráusado daquiparaafrente. Sevocêpensaqueacabou,estámuito enganado.Presteatençãoà“TabelaVerdade”naTabela5.
Tabela5-TabelaVerdade
$Familia = felino -o FALSO -o U $Genero = lobo
Ou(nessecasoespecífico)opróprio greppoderianosquebrarogalho:
Queresolvidaresultaem: $grep Artista[12] musicas VERDADEIRO -o FALSO -o FALSO
Comoagoratodososconectoressão -o,eparaqueumasériedeexpressões conectadas entre si por diversos “ou” lógicossejaverdadeira,bastaqueuma delasoseja.Aexpressãofinalresultou como VERDADEIRO e o then foi executadodeformaerrada.Paraqueisso volteafuncionarfaçamososeguinte:
No egrep acima, foi usada uma expressãoregular,naqualabarravertical(|)trabalhacomoum“oulógico”e os parênteses são usados para limitar a amplitude deste “ou”. Já no grep da linha seguinte, a palavra Artista deve serseguidaporumdosvaloresdalista formadapeloscolchetes([]),istoé,1ou2.
Combinação
E
OU
VERDADEIRO-VERDADEIRO
TRUE
TRUE
VERDADEIRO-FALSO
FALSE
TRUE
FALSO-VERDADEIRO
FALSE
TRUE
FALSO-FALSO
FALSE
FALSE
Ou seja, quando o conector é e e a primeiracondiçãoéverdadeira,oresultadofinalpodeserverdadeirooufalso, dependendodasegundacondição;jáno conectorou,casoaprimeiracondição sejaverdadeira,oresultadosempreserá verdadeiro.Seaprimeiraforfalsa,oresul– Tá legal, eu aceito o argumento, o if tadodependerádasegundacondição. $ if test \ $Familia = felino U do shell é muito mais poderoso que Ora, os caras que desenvolveram o -o $Familia = canideo\ -a U osoutroscaretas-mas,cáentrenós, interpretador não são bobos e estão \ $Genero = onça -o $Genero = U essaconstruçãodeiftest...émuito sempre tentando otimizar ao máximo lobo\ esquisita,époucolegível. os algoritmos. Portanto, no caso do > then – É, você tem razão, eu também não conectore,asegundacondiçãonãoserá > echo Cuidado gostodissoeachoqueninguémgosta. avaliada,casoaprimeirasejafalsa,já > else Acho que foi por isso que o shell que o resultado será sempre falso. Já > echo Pode passar a mão incorporououtrasintaxe,quesubsti- com o ou, a segunda será executada > fi tuiocomandotest. somentecasoaprimeirasejafalsa. Pode passar a mão Aproveitando-se disso, uma forma Para isso vamos pegar aquele exem- abreviadadefazertestesfoicriada.O Desta forma, com o uso dos parên- ploparafazerumatrocadediretórios, conectorefoibatizadode&&eooude teses agrupamos as expressões com o queeraassim: ||.Paravercomoissofunciona,vamos conector -o, priorizando a execução e usá-los como teste no nosso velho resultandoemVERDADEIRO-aFALSO. if test ! -d lmb exemplodetrocadediretório,queem Para que seja VERDADEIRO o resulthen suaúltimaversãoestavaassim: tado de duas expressões ligadas pelo mkdir lmb conector -a, é necessário que ambas fi if [ ! -d lmb ] sejamverdadeiras,oquenãoéocaso cd lmb then doexemploacima.Assim,oresultado mkdir lmb finalfoiFALSO,sendoentãooelsecor- eutilizandoanovasintaxe,vamosfazêfi retamenteexecutado. loassim: cd lmb Se quisermos escolher um CD que tenhafaixasde2artistasdiferentes,nos if [ ! -d lmb ] Ocódigoacimatambémpoderiaser sentimostentadosausarumifcomo then escritodemaneiraabreviada: conector-a,masésemprebomlembrar mkdir lmb queobashnosoferecemuitosrecursos fi [ ! -d lmb ] && mkdir lmb eissopoderiaserfeitodeformamuito cd lmb cd dir maissimplescomumúnicocomando grep,daseguinteforma: Ou seja, o comando test pode ser Tambémpodemosretiraranegação(!): substituídoporumpardecolchetes([]), $ grep Artista1 musicas | grep U [ -d lmb ] || mkdir lmb separados por espaços em branco dos Artista2 cd dir argumentos,oqueaumentaráenorme-
����������������������������� 86
edição04
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
Tabela6 Caractere
Significado
*
Qualquercaractereocorrendozeroou maisvezes
?
Qualquercaractereocorrendoumavez
[...]
Listadecaracteres
|
“ou”lógico
No primeiro caso, se o primeiro comando (o test, que está representadopeloscolchetes)forbemsucedido, istoé,seodiretóriolmbnãoexistir,o comandomkdirseráexecutadoporque aprimeiracondiçãoeraverdadeiraeo conectorerae. Noexemploseguinte,testamosseo diretóriolmbexistia(noanteriortestamosseelenãoexistia)e,casoissofosse verdade, o mkdir não seria executado porqueoconectoreraou.Outraforma deescreveroprograma:
diretórioparadentrodele.Paraexecutarmaisdeumcomandodessaforma, é necessário fazer um grupamento de comandos, o que se consegue com o uso de chaves ({}). Veja como seria o modocorreto: cd lmb || { mkdir lmb cd lmb }
LINUXUSER
sempreestarádentrodelmb,desdeque tenhapermissãoparaentrarnestediretório,permissãoparacriarumsubdiretóriodentrode../lmb,quehajaespaço emdiscosuficiente... Vejamosumexemplodidático:dependendodovalordavariável$opcoscript deverá executar uma das opções a seguir:inclusão,exclusão,alteraçãoou encerrarsuaexecução.Vejacomoficariaocódigo:
Ainda não está legal porque, caso o diretórionãoexista,ocdexibiráuma mensagemdeerro.Vejaomodocerto: cd lmb 2> /de /null || { mkdir lmb cd lmb }
Comovocêviu,ocomandoifnospermitiu fazer um cd seguro de diversas Nesse caso, se o comando cd fosse maneiras.Ésemprebomlembrarqueo mal sucedido, o diretório lmb seria “seguro”aquemerefirodizrespeitoao criadomasnãoseriafeitaamudançade fatodequeaofinaldaexecuçãovocê cd lmb || mkdir lmb
if [ $opc -eq 1 ] then inclusao elif [ $opc -eq 2 ] then e clusao elif [ $opc -eq 3 ] then alteracao elif [ $opc -eq 4 ] then e it else echo Digite uma opção entre U 1 e 4 fi
����������������������������� www.linuxmagazine.com.br
edição04
87
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� PapodeBotequim
Quadro1-Script bem-educado
case $opc in 1 inclusao ;; 2 e clusao ;; 3 alteracao ;; 4 e it ;; * echo Digite uma opção U entre 1 e 4 esac
#!/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 )oite ;; esac e it
Neste exemplo você viu o uso do comando elif como um substituto ou formamaiscurtadeelseif.Essaéuma sintaxeválidaeaceita,maspoderíamos fazeraindamelhor.Paraissousamoso comandocase,cujasintaxemostramos aseguir:
Como você deve ter percebido, eu useioasteriscocomoúltimaopção,isto é,seoasteriscoatendeaqualquercoisa, então servirá para qualquer coisa que 0?|1 [01]– Zeroseguidodequalquer nãoestejanointervalode1a4.Outra coisa (?), ou (|) um seguido de zero coisaasernotadaéqueoduploponto-e- ouum([01]),ouseja,estalinha"casa" vírgulanãoénecessárioantesdoesac. com01,02,...09,10e11; Vamos agora fazer um script mais 1 [2-7]– Significaumseguidodalista radical.Eletedarábomdia,boatarde decaracteresentredoisesete,ouseja, ou boa noite dependendo da hora em estalinhapega12,13,...17; queforexecutado,masprimeiramente *– Significatudooquenãocasoucom vejaestescomandos: nenhumdospadrõesanteriores. $ date Tue )o 9 19:37:30 BRST 2004 $ date +%H 19
case $ ar in padrao1 cmd1 cmd2 cmdn ;; padrao2 cmd1 cmd2 cmdn ;; padraon cmd1 cmd2 cmdn ;; esac
Ondeavariável$varécomparadaaos padrõespadrao1,...,padraon.Casoum dospadrõescorrespondaàvariável,o blocodecomandoscmd1,...,cmdncorrespondenteéexecutadoatéencontrar umduploponto-e-vírgula(;;),quando ofluxodoprogramaseráinterrompido e desviado para instrução imediatamenteapósocomandoesac(que,caso nãotenhamnotado,écaseaocontrário. Eleindicaofimdoblocodecódigo,da mesmaformaqueotcomandofiindica ofimdeumif). Naformaçãodospadrões,sãoaceitos oscaracteresmostradosnaTabela6. Para mostrar como o código fica melhor,vamosrepetiroexemploanterior,sóquedestavezusaremosocase emvezdotradicionalblocodecódigo comif...elif...else...fi.
����������������������������� 88
edição04
Ocomandodateinformaadatacompletadosistemaetemdiversasopções de mascaramento do resultado. Neste comando, a formatação começa com umsinaldemais(+)eoscaracteresde formataçãovêmapósumsinaldepercentagem(%),assimo%Hsignificaa horadosistema.Ditoisso,vejaoexemplonoQuadro1. Pegueipesado,né?Quenada,vamos esmiuçararesolução:
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
– Cara, até agora eu falei muito e bebi pouco. Agora eu vou te passar um exercício para você fazer em casa e medararespostadapróximavezem que nos encontrarmos aqui no botequim,tálegal? – Beleza! – É o seguinte: faça um programa que receba como parâmetro o nome de umarquivoequequandoexecutado salveessearquivocomonomeoriginalseguidodeumtil(~)eabraesse arquivodentrodoviparasereditado. Isso é para ter sempre uma cópia de backup do arquivo caso alguém faça alterações indevidas. Obviamente, você fará as críticas necessárias,comoverificarsefoipassado umparâmetro,seoarquivoindicado existe...Enfim,oquetedernatelhae vocêacharquedevaconstardoscript. Deupraentender? – Hum,hum... – Chico,trazmaisum,semcolarinho!■
SOBREOAUTOR
LINUXUSER
JulioCezarNevesé AnalistadeSuporte deSistemasdesde 1969etrabalhacom Unixdesde1980, quandoparticipou dodesenvolvimento doSOX,umsistema operacionalsimilaraoUnixproduzidopelaCobracomputadores. Podesercontatadonoe-mailjulio. neves@gmail.com
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
LINUXUSER
CursodeShellScript
Papode BotequimV Blocosdecódigoelaços(ouloops,comopreferemalguns) DaveHamilton-www.sxc.hu
F
–
– – –
–
–
ala cara! E as idéias estão em ordem?Jáfundiuacucaouvocê aindaagüentamaisShell? Güento! Tô gostando muito! Gostei tanto que até caprichei no exercício que você passou. Lembra que você me pediu para fazer um programa querecebecomoparâmetroonome de um arquivo e que quando executadosalvaessearquivocomonome original seguido de um til (~) e o abredentrodovi? Claroquelembro,memostreeexpliquecomovocêfez. Beleza,dáumaolhadanoquadro1 É,beleza!Masmedizumacoisa:por que você terminou o programa com umexit0? Eu descobri que o número após o exit indica o código de retorno do programa (o $?, lembra?) e assim, como a execução foi bem sucedida, ele encerra com o $?=0. Porém, se você observar, verá que caso o programanãotenharecebidoonomedo arquivooucasoooperadornãotenha permissãodegravaçãonessearquivo, o código de retorno ($?) seria diferentedozero. Grandegaroto,aprendeulegal,masé bomdeixarclaroqueexit0,simplesmenteexitounãocolocarexitproduzemigualmenteumcódigoderetorno ($?)igualazero.Agoravamosfalar sobre as instruções de loop ou laço, mas antes vou passar o conceito de blocodecódigo.
sãootemadomêsemmaisumaliçãodenossocursodeShell Script.Garçom,saltaumaboaredondinha,quetôafimde refrescaropensamento!PORJULIOCEZARNEVES
Atéagorajávimosalgunsblocosde código, como quando te mostrei um exemploparafazerumcdparadentro deumdiretório: cd lmb 2> /de /null || { mkdir lmb cd lmb }
O fragmento contido entre as duas chaves({})formaumblocodecódigo. Tambémnesseexercícioqueacabamos dever,emquesalvamosoarquivoantes de editá-lo, existem vários blocos de códigocompreendidosentreoscomandosthenefidoif.Umblocodecódigo tambémpodeestardentrodeumcase ouentreumdoeumdone. – Peraí,Julio,quedoedonesãoesses? Não me lembro de você ter falado nisso, e olha que estou prestando muitaatenção... – Poisé,aindanãotinhafaladoporque nãohaviachegadoahoracerta. Todas as instruções de loop ou laço executam os comandos do bloco compreendidosentreumdoeumdone.As instruçõesdeloopoulaçosãofor,while e until , que serão explicadas uma a umaapartirdehoje.
OcomandoFor Se você está habituado a programar, certamente já conhece o comando for, mas o que você não sabe é que o for,
Quadro1:vira.sh $ cat ira.sh #!/bin/bash # # ira - i resguardando # arqui o anterior # Verifica se algum parâmetro foi # passado if [ $# -ne 1 ] then echo Erro -> Uso: $0 U <arqui o> e it 1 fi Arq=$1 # Caso o arqui o não e ista, não # há cópia a ser sal a if [ ! -f $Arq ] then i $Arq e it 0 fi # Se eu não puder alterar o #arqui o, ou usar o i para que? if [ ! $Arq ] then echo Você não tem permissão U de escrita em $Arq e it 2 fi # Já que está tudo OK, ou # sal ar a cópia e chamar o i cp -f $Arq $Arq~ i $Arq e it 0
����������������������������� www.linuxmagazine.com.br
edição05
89
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
queéumainstruçãointrínsecadoShell (isso significa que o código fonte do comandofazpartedocódigofontedo Shell,ouseja,embomprogramêséum built-in),émuitomaispoderosoqueos seuscorrelatosdasoutraslinguagens. Vamos entender a sua sintaxe, primeiro em português e, depois, como funcionapravaler.Olhesó: para ar em faça cmd1 cmd2 cmdn feito
al1
al2 ...
aln
Ondeavariávelvarassumecadaum dosvaloresdalistaval1val2...valne, paracadaumdessesvalores,executao blocodecomandosformadoporcmd1, cmd2 e cmdn. Agora que já vimos o significadodainstruçãoemportuguês, vejamosasintaxecorreta: for do
ar in
al1
al2 ...
aln
cmd1 cmd2 cmdn done
Vamos aos exemplos, para entender direitoofuncionamentodestecomando. Vamos escrever um script para listar todososarquivosdodiretório,separadospordois-pontos,masantesvejaisso: $ echo * ArqDoDOS.t t1 confuso incusu logado muse c musicas musinc muslist
Isto é, o Shell viu o asterisco (*), expandiu-o com o nome de todos os arquivosdodiretórioeocomandoecho jogou-osparaatelaseparadosporespaçosembranco.Vistoisso,vamosresolveroproblemaaquenospropusemos: $ cat testefor1 #!/bin/bash # 1o. Programa didático para # entender o for for Arq in * do echo -n $Arq: done
Entãovamosexecutá-lo: $ testefor1 ArqDoDOS.t t1:confuso:incusu: logado:muse c:musicas:musinc: muslist:$
edição05
Isto é, mandei a variável (protegida da interpretação do Shell pelas aspas) paraumdumphexadecimal(od-h).O resultadopodeserinterpretadocoma tabelaabaixo:
Comovocêviu,oShelltransformou o asterisco (que odeia ser chamado de asterístico) em uma lista de arquivos separados por espaços em branco. Tabela1:Resultadodood-h Quando o for viu aquela lista, disse: ValorHexadecimal Significado “Opa, listas separadas por espaços é 09 <TAB> comigomesmo!” 20 <ESPAÇO> Oblocodecomandosaserexecutado erasomenteoecho,quecomaopção-n 0a <ENTER> listouavariável$Arqseguidadedoispontos(:),semsaltaralinha.Ocifrão ($) do final da linha da execução é o O último 0a foi proveniente do prompt, que permaneceu na mesma <ENTER> dado ao final do comando. linhatambémemfunçãodaopção-n. Paramelhoraraexplicação,vamosver Outroexemplosimples(porenquanto): issodeoutraforma: $ cat testefor2 #!/bin/bash # 2o. Programa didático para # entender o for for Pala ra in Linu (agazine U do Brasil do echo $Pala ra done
Eexecutandotemos: $ testefor2 Linu (agazine do Brasil
Como você viu, esse exemplo é tão bobo e simples como o anterior, mas serve para mostrar o comportamentobásicodofor.Vejasóaforçado comando: ainda estamos na primeira possibilidadedesintaxeejáestoumostrandonovasformasdeusá-lo.Láatrás eu havia falado que o for usava listas separadasporespaçosembranco,mas issoéumameia-verdade,sóparafacilitaracompreensão.Naverdade,aslistas nãosãoobrigatoriamenteseparadaspor espaços.Masantesdeprosseguir,precisotemostrarcomosecomportauma variáveldosistemachamadadeIFS,ou InterFieldSeparatorVejanoexemploa seguirseuconteúdo:
����������������������������� 90
$ echo $IFS | od -h 0000000 0920 0a0a 0000004
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
$ echo : ^I$ :$
:$IFS:
| cat - et
No comando cat, a opção -e representao<ENTER>comoumcifrão($) eaopção-trepresentao<TAB>como um^I.Useiosdois-pontos(:)paramostrar o início e o fim do echo. E dessa forma,pudemosnotarqueostrêscaracteresestãopresentesnaquelavariável. Agoravejavocê:traduzindo,IFSsignificaseparadorentrecampos.Umavez entendidoisso,eupossoafirmarqueo comandofornãousaapenaslistasseparadasporespaçosembranco,massim pelo conteúdo da variável $IFS, cujo valorpadrãosãooscaracteresqueacabamosdever.Paracomprovarmosisso, vamos continuar mexendo em nossa CDTeca, escrevendo um script que recebeonomedoartistacomoparâmetroelistaasmúsicasqueeletoca.Mas primeiramentevamosvercomoestáo nossoarquivomusicas: $ cat musicas album 1^Artista1~(usica1: U Artista2~(usica2 album 2^Artista3~(usica3: U Artista4~(usica4 album 3^Artista5~(usica5: U Artista6~(usica6 album 4^Artista7~(usica7: U Artista1~(usica3
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
album 5^Artista9~(usica9: U Artista10~(usica10
Emcimadesse“leiaute”desenvolvemososcriptaseguir: $ cat listartista #!/bin/bash # Dado um artista, mostra as # suas músicas if [ $# -ne 1 ] then echo Você de eria ter U passado um parâmetro e it 1 fi IFS= : for Art(us in $ cut -f2 -d^ U musicas do echo $Art(us | grep $1 && U echo $Art(us | cut -f2 -d~ done
O script, como sempre, começa testandoseosparâmetrosforampassados corretamente,emseguidaoIFSfoiconfiguradopara<ENTER>edois-pontos(:) (comodemonstramasaspasemlinhas diferentes),porqueéelequemseparaos blocosArtistan~Musicam.Destaforma, avariável$ArtMusirárecebercadaum dessesblocosdoarquivo(reparequeo forjárecebeosregistrossemoálbum emvirtudedocutnasualinha).Caso encontreoparâmetro($1)nobloco,o segundocutlistarásomenteonomeda música.Vamosexecutaroprograma: $ listartista Artista1 Artista1~(usica1 (usica1 Artista1~(usica3 (usica3 Artista10~(usica10 (usica10
Êpa! Aconteceram duas coisas indesejáveis:osblocostambémforamlistados,eaMusica10idem.Alémdomais, onossoarquivodemúsicasestámuito simples: na vida real, tanto a música quantooartistatêmmaisdeumnome. Suponhaqueoartistafosseumadupla sertanejachamadaPerereca&Peteleca (nãogostonemdedaraidéiacomreceio queissosetornerealidade).Nessecaso,
o$1seriaPererecaeorestodesselindo nomeseriaignoradonapesquisa. Paraqueissonãoocorra,eudeveria passaronomedoartistaentreaspas (”)outrocar$1por$*(querepresenta todososparâmetrospassados),queé amelhorsolução,masnessecasoeu teriaquemodificaracríticadosparâmetros e o grep. A nova versão não seriaseeupasseiumparâmetro,mas sim se passei pelo menos um parâmetro.Quantoaogrep,vejasóoque aconteceria após a substituição do $* pelosparâmetros: echo $Art(us & peteleca
| grep perereca U
Issogeraumerro.Ocorretoé: echo $Art(us | grep -i U perereca & peteleca
LINUXUSER
$ listartista Artista1 (usica1 (usica3
Vejaumasegundasintaxeparaofor: for do
ar cmd1 cmd2 cmdn
done
Ué,semoin,comoelevaisaberque valorassumir?Poisé,né?Estaconstrução,àprimeiravista,pareceesquisita, masébastantesimples.Nestecaso,var assumiráumaumcadaparâmetropassadoparaoprograma.Comoexemplo paraentendermelhor,vamosfazerum scriptquerecebacomoparâmetroum montedemúsicaselisteseusautores:
Aqui adicionamos a opção -i para queapesquisaignorassemaiúsculase minúsculas. As aspas foram inseridas paraqueonomedoartistafossevisto comoumasócadeiadecaracteres. Faltaconsertaroerrodeleterlistadoo Artista10.Omelhorédizeraogrepquea cadeiadecaracteresestánoinício(^)de $ArtMusequelogoapósvemumtil(~).É precisoredirecionarasaídadogreppara/ dev/nullparaqueosblocosnãosejamlistados.Vejaanovacaradoprograma: $ cat listartista #!/bin/bash # Dado um artista, mostra as # suas musicas # Versao 2 if [ $# -eq 0 ] then echo Voce de eria ter U passado pelo menos um parametro e it 1 fi IFS= : for Art(us in $ cut -f2 -d^ U musicas do echo $Art(us | grep -i U ^$*~ > /de /null && echo U $Art(us | cut -f2 -d~ done
Oresultadoé:
$ cat listamusica #!/bin/bash # ”ecebe parte dos nomes de # músicas como parâmetro e # lista os intérpretes. Se o # nome for composto, de e # ser passado entre aspas. # e . Eu não sou cachorro não # Churrasquinho de (ãe # if [ $# -eq 0 ] then echo Uso: $0 musica1 U [musica2] ... [musican] e it 1 fi IFS= : for (usica do echo $(usica Str=$ grep -i $(usica U musicas || { echo )ão U encontrada continue } for Art(us in $ echo $Str U | cut -f2 -d^ do echo $Art(us | U grep -i $(usica | cut -f1 -d~ done done
����������������������������� www.linuxmagazine.com.br
edição05
91
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� PapodeBotequim
Damesmaformaqueosoutros,começamosoexercíciocomumacríticasobre os parâmetros recebidos, em seguida fizemos um for em que a variável $Musicareceberácadaumdosparâmetrospassados,colocandoem$Strtodos osálbunsquecontêmasmúsicasdesejadas.Emseguida,ooutroforpegacada blocoArtista~Musicanosregistrosque estão em $Str e lista cada artista que tocaaquelamúsica.Vamosexecutaro programaparaversefuncionamesmo:
for i in $ seq 0 3 9 do echo -n $i done 0 3 6 9
A outra forma de fazer isso é com umasintaxemuitosemelhanteaoforda linguagemC,comovemosaseguir: for do
$ listamusica musica3 (usica4 U Egüinha Pocotó musica3 Artista3 Artista1 (usica4 Artista4 Egüinha Pocotó )ão encontrada
Avariáveliassumiuosvaloresinteiros entre 1 a 9 gerados pelo comando seqeaopção-ndoechofoiusadapara nãosaltarumalinhaacadanúmerolistado.Aindausandooforcomseq:
Onde var=ini significa que a variável var começará de um valor inicial ini;condsignificaqueoloopoulaçofor seráexecutadoenquantovarnãoatingir acondiçãocondeincrsignificaoincrementoqueavariávelvarsofreráacada passadadoloop.Vamosaosexemplos: for do
i=1; i<=9; i++
echo -n $i done 1 2 3 4 5 6 7 8 9
Ouseja,avariável$jsequerexistiae noprimeiroletassumiuovalor0(zero) para,apósoincremento,terovalor1. Vejasócomoascoisasficamsimples: for arq in * do let i++ echo $i -> $Arq done 1 -> ArqDoDOS.t t1 2 -> confuso 3 -> incusu 4 -> listamusica 5 -> listartista 6 -> logado 7 -> muse c 8 -> musicas 9 -> musinc 10 -> muslist 11 -> testefor1 12 -> testefor2
Avariávelipartiudovalorinicial1,o blocodecódigo(aquisomenteoecho) seráexecutadoenquantoiformenorou igual(<=)a9eoincrementodeiserá – Poiséamigo,tenhocertezaquevocê de1acadapassadadoloop. já tomou um xarope do comando Reparequenoforpropriamentedito for. Por hoje chega, na próxima vez (enãonoblocodecódigo)nãocoloquei emquenosencontrarmosfalaremos um cifrão ($) antes do i e a notação sobreoutrasinstruçõesdeloop,mas paraincrementar(i++)édiferentedo eugostariaqueatélávocêfizesseum quevimosatéagora.Ousodeparêntepequenoscriptparacontaraquantisesduplos(assimcomoocomandolet) dadedepalavrasdeumarquivotexto, chama o interpretador aritmético do cujonomeseriarecebidocomoparâShell,queémaistolerante. metro. Essa contagem tem que ser Sóparamostrarcomooletfunciona feitacomocomandofor,parasehabieaversatilidadedofor,vamosfazera tuaraoseuuso.Nãovaleusarowc-w. mesma coisa, mas omitindo a última AêChico!Trazasaideira! ■ partedoescopodofor,passando-apara JulioCezarNeveséAnalistade oblocodecódigo: for do
; i<=9; let i++ echo -n
$i done 1 2 3 4 5 6 7 8 9
����������������������������� edição05
$ echo $j $ let j++ $ echo $j 1
done
for i in $ seq 9 do echo -n $i done 1 2 3 4 5 6 7 8 9
92
Repare que o incremento saiu do corpodoforepassouparaoblocode código; repare também que, quando usei o let, não foi necessário inicializaravariável$i.Vejasóoscomandosa seguir,digitadosdiretamentenoprompt, parademonstraroqueacabodefalar:
ar=ini; cond; incr cmd1 cmd2 cmdn
Alistagemficoufeinhaporqueainda não sabemos formatar a saída; mas qualquerdiadesses,quandovocêsouberposicionarocursor,trabalharcom coresetc.,faremosesseprogramanovamenteusandotodasessasperfumarias. A esta altura dos acontecimentos, você deve estar se perguntando: “E aquelefortradicionaldasoutraslinguagensemqueelesaicontandoapartir de um número, com um determinado incremento, até alcançar uma condição?”. E é aí que eu te respondo: “Eu nãotedissequeonossoforémaisporretaqueodosoutros?”Parafazerisso, existem duas formas. Com a primeira sintaxequevimos,comonoexemplo:
for i in $ seq 4 9 do echo -n $i done 4 5 6 7 8 9
Ounaformamaiscompletadoseq:
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
SOBREOAUTOR
LINUXUSER
SuportedeSistemasdesde1969etrabalhacomUnixdesde1980,quando participoudodesenvolvimentodo SOX,umsistemaoperacionalsimilar aoUnixproduzidopelaCobraComputadores.Podesercontatadono e-mailjulio.neves@gmail.com
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
Curs��eShellS�ript
Papode BotequimVI Blocosdecódigoelaços(ouloops,comopreferemalguns) DaveHamilt��-www.sx�.hu
F
ala,cara!Eaí,játásabendotudo docomandofor?Eutedeixeium exercícioparatreinar,senãome enganoeraparacontaraquantidadede palavrasdeumarquivo...Vocêfez? – Claro! Tô empolgadão com essa linguagem! Eu fiz da forma que você pediu,olhasó... – Êpa! Peraí que eu tô sequinho pra tomarumchope.AêChico,trazdois porfavor.Umsemcolarinho! – Comoeuiadizendo,olhacomoeufiz. Émuitofácil... $ cat contpal.sh #!/bin/bash # Script meramente pedagógico # cuja função é contar a # quantidade de pala ras de # um arqui o. Supõe-se que as # pala ras estão separadas # entre si por espaços, <TAB> # ou <E)TER>. if [ $# -ne 1 ] then echo uso: $0 /caminho/do/ U arqui o e it 2 fi Cont=0 for Pala ra in $ cat $1 do Cont=$ Cont+1 done echo O arqui o $1 tem $Cont U pala ras.
sãootemadomêsemmaisumaliçãodenossocursodeShell Script.Garçom,saltaumaboaredondinha,quetôafimde refrescaropensamento!PORJ�LIOCEZARNEVES
Ou seja, o programa começa, como sempre, verificando se a passagem de parâmetros foi correta; em seguida o comandoforseincumbedepegarcada umadaspalavras(lembre-sequeo$IFS padrãoébranco,TABeENTER,queé exatamenteoquedesejamosparasepararaspalavras),incrementandoavariável$Cont.Vamosrelembrarcomoéo arquivoArqDoDOS.txt. $ cat ArqDoDOS.t t Este arqui o foi gerado pelo DOS/R in e foi bai ado por um ftp mal feito.
O��ma���while
Agora vamos testar o programa passandoessearquivocomoparâmetro: $ contpal.sh ArqDoDOS.t t O arqui o ArqDoDOS.t t tem 14 pala ras.
Funcionou legal! Se você se lembra, em nossa última aula mostramos o loopforaseguir: for do
; i<=9; let i++ echo -n "$i "
done
edição06
www.linuxmagazine.com.br
Todososprogramadoresconhecemeste comando,porqueécomumatodasas linguagens.Nelas,oquenormalmente ocorreéqueumblocodecomandosé executado, enquanto (enquanto, em inglês, é “while”) uma determinada condiçãoforverdadeira.
Tabela1:Expressões��Shell Expressã�
Resulta��
id++id--
pós-incrementoepós-decrementodevariáveis
++id--id
pré-incrementoepré-decrementodevariáveis
**
exponenciação
*/%
multiplicação,divisão,restodadivisão(módulo)
+-
adição,subtração
<=>=<>
comparação
==!=
igualdade,desigualdade
&&
Elógico
||
OUlógico
����������������������������� 86
Umavezquechegamosnesteponto, creioserinteressantecitarqueoShell trabalhacomoconceitode“Expansão Aritmética” (Arithmetic Expansion), queéacionadaporumaconstruçãoda forma$((expressão))ouletexpressão. No último loop for usei a expansão aritmética das duas formas, mas não podemosseguiradiantesemsaberque aexpressãopodeserumadaslistadas natabela1. Masvocêpensaqueopapodeloop (ou laço) se encerra no comando for? Ledoengano,amigo,vamosapartirde agoravermaisdoiscomandos.
��������������������������������������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
Poisbem,issoéoqueacontecenas linguagens caretas! Em programação Shell,oblocodecomandoséexecutado enquanto um comando for verdadeiro. E é claro, se quiser testar uma condição,useocomandowhilejuntocomo comando test, exatamente como você aprendeuafazernoif,lembra?Entãoa sintaxedocomandoficaassim: hile comando do cmd1 cmd2 ... cmdn done
e dessa forma, o bloco formado pelas instruçõescmd1,cmd2,...ecmdnéexecutadoenquantoaexecuçãodainstruçãocomandoforbemsucedida. Suponha a seguinte cena: tinha uma tremenda gata me esperando e euestavapresonotrabalhosempoder sairporqueomeuchefe,queéumpé nosaco(aliáschefe-chatoéumaredundância,né?),aindaestavanasaladele, queficabemnaminhapassagemparaa rua.Elecomeçouaficarcabreirodepois daquintavezquepasseipelasuaporta eolheiparaversejáhaviaidoembora. Entãovolteiparaaminhamesaefiz,no servidor,umscriptassim: $ cat logaute.sh #!/bin/bash # Espero que a Xu a não tenha # cop right de efe e ato : hile ho | grep efe do sleep 30 done echo O ato se mandou, não U hesite, dê e it e á à luta
Neste scriptzinho, o comando while testaopipelinecompostopeloscomandos who e grep, que será verdadeiro enquanto o grep localizar a palavra xefe na saída do comando who. Desta forma,oscriptdormirápor30segundos enquantoochefeestiverlogado(Argh!). Assimqueelesedesconectardoservidor,ofluxodoscriptsairádoloopete mostrará a tão ansiada mensagem de liberdade.Masquandoexecuteioscript, adivinhaoqueaconteceu?
$ logaute.sh efe pts/0 10.2.4.144 efe pts/0 10.2.4.144 ... efe pts/0 10.2.4.144
Jan
4 08:46 U
Jan
4 08:46 U
Jan
4 08:46 U
Istoé,acada30segundosasaídado comandogrepseriaenviadaparaatela, oquenãoélegal,jáquepoluiriaatela domeumicroeamensagemtãoesperadapoderiapassardespercebida.Para evitarisso,jásabemosqueasaídado pipelinetemqueserredirecionadapara odispositivo/dev/null. $ cat logaute.sh #!/bin/bash # Espero que a Xu a não tenha # cop right de efe e ato : hile ho | grep efe > /de /null do sleep 30 done echo O ato se mandou, não U hesite, dê e it e á a luta
Agora quero montar um script que receba o nome (e eventuais parâmetros)deumprogramaqueseráexecutadoembackgroundequemeinforme doseutérmino.Mas,paravocêentender este exemplo, primeiro tenho de mostrarumanovavariáveldosistema. Vejaestescomandosexecutadosdiretamentenoprompt: $ sleep 10& [1] 16317 $ echo $! 16317 [1]+ Done $ echo $! 16317
LINUXUSER
$cat monbg.sh #!/bin/bash # E ecuta e monitora um # processo em background $1 & # Coloca em backgroud hile ps | grep -q $! do sleep 5 done echo Fim do Processo $1
Essescriptébastantesimilaraoanterior,mastemunsmacetesamais,veja só:eletemqueserexecutadoembackgroundparanãoprenderopromptmas o$!seráodoprogramapassadocomo parâmetro, já que ele foi colocado em background após o monbg.sh propriamentedito.Reparetambémnaopção-q (quiet)dogrep,queserveparafazê-lo “trabalharemsilêncio”.Omesmoresultado poderia ser obtido com a linha: whileps|grep$!>/dev/null,comonos exemplosquevimosatéagora. Vamos melhorar o nosso velho musinc,nossoprogramaparaincluir registros no arquivo musicas, mas antes preciso te ensinar a pegar umdadodatela,ejávouavisando: só vou dar uma pequena dica do comando read (que é quem pega o dado da tela), que seja o suficiente para resolver este nosso problema. Emumaoutrarodadadechopevou te ensinar tudo sobre o assunto, inclusive como formatar tela, mas hojeestamosfalandosobreloops.A sintaxe do comando read que nos interessaporhojeéaseguinte: $ read -p "prompt de leitura"
sleep 10
Isto é, criei um processo em background que dorme por 10 segundos, somenteparamostrarqueavariável$! guardaoPID(ProcessID)doúltimoprocesso em background. Mas observe a listagemerepare,apósalinhadoDone, que a variável reteve o valor mesmo apósotérminodesseprocesso. Bem,sabendoisso,jáficamaisfácil monitorar qualquer processo em background.Vejasócomo:
ar
Onde “prompt de leitura” é o texto que você quer que apareça escrito na tela.Quandoooperadorteclartaldado, ele será armazenado na variável var. Porexemplo: $ read -p "Título do Álbum: " Tit
Bem,umavezentendidoisso,vamos à especificação do nosso problema: faremosumprogramaqueinicialmente leráonomedoálbumeemseguidafará umloopdeleitura,pegandoonomeda música e o artista. Esse loop termina quandoforinformadaumamúsicacom nomevazio,istoé,quandoooperador
����������������������������� www.linuxmagazine.com.br
edição06
87
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
Di�a
until comando do cmd1 cmd2 ... cmdn done
Leituradearquivosignificalerumaum todososregistros,oqueésempreuma operaçãolenta.Fiqueatentoparanãousar owhilequandofordesnecessário.OShell temferramentascomoosedeafamília grep,quevasculhamarquivosdeforma otimizadasemquesejanecessárioousodo whileparafazê-loregistroaregistro.
derumsimples<ENTER>.Parafacilitaravidadooperador,vamosoferecer comodefaultomesmonomedoartista damúsicaanterior(jáqueénormalque oálbumsejatododomesmoartista)até queeledesejealterá-lo.Vejanalistagem 1comoficouoprograma. Nosso exemplo começa com a leituradotítulodoálbum.Casoelenão seja informado, terminamos a execuçãodoprograma.Emseguidaumgrep procura,noinício(^)decadaregistro demúsicas,otítuloinformadoseguido doseparador(^)(queestáprecedidode umacontrabarra[\]paraprotegê-loda interpretaçãodoShell). Para ler os nomes dos artistas e as músicasdoálbum,foimontadoumloop whilesimples,cujoúnicodestaqueéo fatodeelearmazenaronomedointérprete da música anterior na variável $oArt,quesóteráoseuconteúdoalteradoquandoalgumdadoforinformado paraavariável$Art,istoé,quandonão for teclado um simples ENTER para manteroartistaanterior. Oquefoivistoatéagorasobreowhile foimuitopouco.Essecomandoémuito utilizado, principalmente para leitura de arquivos, porém ainda nos falta bagagem para prosseguir. Depois que aprendermosmaissobreisso,veremos essainstruçãomaisafundo.
O��ma���until Este comando funciona de forma idêntica ao while, porém ao contrário. Disse tudo mas não disse nada, né? É o seguinte: ambos testam comandos; ambos possuem a mesma sintaxe e ambosatuamemloop;porém,owhile executaoblocodeinstruçõesdoloop enquanto um comando for bem sucedido;jáountilexecutaoblocodoloop atéqueocomandosejabemsucedido. Parecepoucacoisa,masadiferençaé fundamental.Asintaxedocomandoé praticamenteamesmadowhile.Veja:
edessaformaoblocodecomandosformadopelasinstruçõescmd1,cmd2,...e cmdnéexecutadoatéqueaexecuçãoda instruçãocomandosejabemsucedida. Comoeutedisse,whileeuntilfuncionamdeformaantagônica,eissoémuito fácil de demonstrar: em uma guerra, sempre que se inventa uma arma, o inimigo busca uma solução para neutralizá-la.Foibaseadonesseprincipio belicosoquemeuchefedesenvolveu,no mesmoservidoremqueeuexecutavao logaute.sh, um script para controlar o meuhoráriodechegada. Umdiativemosumproblemanarede. Elemepediuparadarumaolhadano microdeleemedeixousozinhonasala. Resolvibisbilhotarosarquivos–guerra éguerra–evejasóoquedescobri:
edição06
Olhaquesafado!Ocaraestavamontandoumlogcomosmeushoráriosde chegada, e ainda por cima chamou o arquivoderelapso.log!Oqueseráque elequisdizercomisso? Nesse script, o pipeline who | grep julio, será bem sucedido somente quando julio for encontrado na saída do comando who, isto é, quando eu me “logar” no servidor. Até que isso aconteça,ocomandosleep,queforma oblocodeinstruçõesdountil,colocará o programa em espera por 30 segundos.Quandoesseloopencerrar-se,será enviadaumamensagemparaoarquivo relapso.log. Supondo que no dia 20/01 eume“loguei”às11:23horas,amensagemseriaaseguinte:
Listagem1 $ cat musinc.sh #!/bin/bash # Cadastra CDs ersao 4 # clear read -p "Título do Álbum: " Tit [ "$Tit" ] || e it 1 # Fim da e ecução se título azio if grep "^$Tit\^" musicas > /de /null then echo "Este álbum já está cadastrado" e it 1 fi Reg="$Tit^" Cont=1 oArt= hile true do echo "Dados da trilha $Cont:" read -p "(úsica: " (us [ "$(us" ] || break # Sai se azio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Se azio Art anterior Reg="$Reg$oArt~$(us:" # (ontando registro Cont=$ Cont + 1 # A linha anterior tb poderia ser Cont++ done echo "$Reg" >> musicas sort musicas -o musicas
����������������������������� 88
$cat chegada.sh #!/bin/bash until ho | grep julio do sleep 30 done echo $ date "+ Em %d/%m às U %H:%(h" > relapso.log
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
���������������������������������������������������� PapodeBotequim
Atalh�s�� l��p
Em 20/01 às 11:23h
LINUXUSER
��
��
Voltando à nossa CDteca, quando vamos cadastrar músicas seria ideal que pudéssemos cadastrar diversos CDs de uma vez só. Na última versão doprogramaissonãoocorre:acadaCD cadastradooprogramatermina.Vejana listagem2comomelhorá-lo. Nestaversão,umloopmaiorfoiadicionadoantesdaleituradotítulo,que só terminará quando a variável $Para deixar de ser vazia. Caso o título do álbum não seja informado, a variável$Parareceberáumvalor(coloquei 1, mas poderia ter colocado qualquer coisa)parasairdesseloop,terminando oprograma.Noresto,oscriptéidêntico àversãoanterior.
Nem sempre um �������� �������� ciclo de programa, c o m p r e e n d i d o ����� �������� ����� ����� entre um do e um done,saipelaporta da frente. Em algu�������� �������� masoportunidades, temos que colocar ���� ���� um comando que aborte de forma �s�����ma����������� c ont rolada esse �s�����ma�������� loop. De maneira Figura1:Aestrutura��s��ma���sbreakecontinue,usa��spara���tr�inversa, algumas lar�flux��eexe�uçã�eml��ps. vezes deseja mos pectivamenteoscomandosbreak(que que o f lu xo de execução do programa volte antes de jávimosrapidamentenosexemplosdo chegar ao done. Para isso, temos res- comandowhile)econtinue,quefuncionamdaformamostradanafigura1. O que eu não havia dito anteriorListagem2 menteéquenassuassintaxesgenéricas elesaparecemdaseguinteforma:
$ cat musinc.sh #!/bin/bash # Cadastra CDs ersao 5 # Para= until [ "$Para" ] do clear read -p "Título do Álbum: " Tit if [ ! "$Tit" ] # Se titulo azio... then Para=1 # Liguei flag de saída else if grep "^$Tit\^" musicas > /de /null then echo "Este álbum já está cadastrado" e it 1 fi Reg="$Tit^" Cont=1 oArt= hile [ "$Tit" ] do echo Dados da trilha $Cont: read -p "(úsica: " (us [ "$(us" ] || break # Sai se azio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Se azio Art anterior Reg="$Reg$oArt~$(us:" # (ontando registro Cont=$ Cont + 1 # A linha anterior tb poderia ser Cont++ done echo "$Reg" >> musicas sort musicas -o musicas fi done
break [qtd loop]
etambém: continue [qtd loop]
Onde qtd loop representa a quantidadedosloopsmaisinternossobreos quaisoscomandosirãoatuar.Seuvalor pordefaulté1. Duvido que você nunca tenha apagado um arquivo e logo após deu um tabefe na testa se xingando porque não devia tê-lo removido. Pois é, na décimavezquefizestabesteira,criei um script para simular uma lixeira, istoé,quandomandoremoverum(ou vários) arquivo(s), o programa “finge” quedeletou,masnodurooqueelefez foimandá-lo(s)paraodiretório/tmp/ LoginName_do_usuario. Chamei esse programadeerreemeenoarquivo/etc/ profile coloquei a seguinte linha, que criaum“apelido”paraele: alias rm=erreeme
Vejaoprogramanalistagem3.Como vocêpodever,amaiorpartedoscript é formada por pequenas críticas aos parâmetros informados, mas como o scriptpodeterrecebidodiversosarquivosaremover,acadaarquivoquenão se encaixa dentro do especificado há
����������������������������� www.linuxmagazine.com.br
edição06
89
��������������������������������������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
��������������������������������������������������� LINUXUSER
PapodeBotequim
Listagem3:erreeme.sh $ cat erreeme.sh #!/bin/bash # # Sal ando cópia de um arqui o antes de remo ê-lo
then echo "$Arq nao e iste." Erro=3 continue # Volta para o comando for fi
# Tem de ter um ou mais arqui os a remo er if [ $# -eq 0 ] then echo "Erro -> Uso: erreeme arq [arq] ... [arq]" echo "O uso de metacaracteres e’ permitido. E .U erreeme arq*" e it 1 fi # Variá el do sistema que contém o nome do usuário. (euDir="/tmp/$LOG)A(E" # Se não e istir o meu diretório sob o /tmp... if [ ! -d $(euDir ] then mkdir $(euDir # Vou criá-lo fi # Se não posso gra ar no diretório... if [ ! - $(euDir ] then echo "Impossi el sal ar arqui os em $(euDir. U (ude as permissões..." e it 2 fi # Variá el que indica o cod. de retorno do programa Erro=0 # Um for sem o in recebe os parametros passados for Arq do # Se este arqui o não e istir... if [ ! -f $Arq ]
– –
– –
# Se estou "es aziando a li eira"... if [ "$DirOrig" = "$(euDir" ] then echo "$Arq ficara sem copia de seguranca" rm -i $Arq # Pergunta antes de remo er # Será que o usuário remo eu? [ -f $Arq ] || echo "$Arqui o remo ido" continue fi # Guardo no fim do arqui o o seu diretório originalU para usá-lo em um script de undelete cd $DirOrig p d >> $Arq m $Arq $(euDir # Sal o e remo o echo "$Arq remo ido" done # Passo e entual número do erro para o código # de retorno e it $Erro
faça-o em casa e me traga para disum email para julio.neves@gmail. cutirmosnonossopróximoencontro com.Agorachegadepapoqueeujá aquinoboteco. estoudegoelasecadetantofalar.Me Poxa,masnesseeuachoquevoudanacompanhanopróximochopeoujá çar,poisnãoseinemcomocomeçar... vaisaircorrendoparafazeroscript C ara, este programa é como tudo quepassei? o que se faz em Shell: extrema- – Deixaeupensarumpouco... mente fácil. É para ser feito em, no – Chico,trazmaisumchopeenquanto máximo, 10 linhas. Não se esqueça elepensa! dequeoarquivoestásalvoem/tmp/ JulioCezarNeveséAnalistade $LOGNAMEequesuaúltimalinhaé SuportedeSistemasdesde1969etrao diretório em que ele residia antes balhacomUnixdesde1980,quando de ser “removido”. Também não se participoudodesenvolvimentodo esqueça de criticar se foi passado o SOX,umsistemaoperacionalsimilar nomedoarquivoaserremovido. aoUnixproduzidopelaCobraComÉeuvoutentar,masseinão... putadores.Podesercontatadono Tenhafé,irmão,eutôtefalandoque e-mailjulio.neves@gmail.com émole!Qualquerdúvidaésópassar
����������������������������� 90
edição06
www.linuxmagazine.com.br
��������������������������������������������������������������������������������
SOBREOAUTOR
umcontinue,paraqueaseqüênciavolte para o loop do for de forma a receber outrosarquivos. QuandovocêestánoWindows(com perdãodamápalavra)etentaremover aquelemontedelixocomnomesesquisitos como HD04TG.TMP, se der erro emumdosarquivososoutrosnãosão removidos,nãoé?Então,ocontinuefoi usado para evitar que uma impropriedadedessasocorra,istoé,mesmoque dê erro na remoção de um arquivo, o programa continuará removendo os outrosqueforampassados. – Eu acho que a esta altura você deve estar curioso para ver o programa querestauraoarquivoremovido,não é? Pois então aí vai vai um desafio:
# Cmd. dirname informa nome do dir de $Arq DirOrig=`dirname $Arq` # Verifica permissão de gra acao no diretório if [ ! - $DirOrig ] then echo "Sem permissão no diretorio de $Arq" Erro=4 continue # Volta para o comando for fi
LinuxUser
Papodebotequim
Curso de Shell Script
PapodeBotequim
ParteVII
Dave Hamilton - www.sxc.hu
De pouco adianta ter acesso à informação se ela não puder ser apresentada de forma atraente e que facilite a compreensão. O comando tput pode ser usado por shell scripts para posicionar caracteres e criar todos os tipos de efeito com o texto mostrado na tela. Garçom, solta uma geladinha! porJulioCezarNeves
C
umequié,rapaz!Derreteuospen- –Peraí,deixaeuverseentendioquevocê –É,tôvendoqueobichinhodoshellte samentosparafazeroscriptzinho fez:vocêcolocanavariávelDiraúltima pegou.Vamosvercomolerdados,mas linhadoarquivoaserrestaurado,em antesvoutemostrarumcomandoque queeutepedi? nossocaso /tmp/$LOG)A(E/$1(onde tedátodasasferramentasparaformatar –É ,eurealmentetivedecolocarmuita $LOG)A(Eéonomedousuáriologado, umateladeentradadedados. pensaçãonatelapreta,masachoque finalmenteconsegui!Bem,pelomenos e $1éoprimeiroparâmetroquevocê nostestesquefizacoisafuncionou, passouaoscript),jáquefoiláquearmamasvocêtemsemprequebotarchifres zenamosonomeecaminhooriginaisdo Oprincipalusodessecomandoéoposiemcabeçadecachorro! arquivoantesdemovê-loparaodiretório cionamentodocursornatela.Alguns –Nãoébemassim.Équeprogramarem (definidonavariávelDir).Ocomando parâmetrospodemnãofuncionarseo ShellScriptémuitofácil,masoqueé grep - apagaessalinha,restaurando modelodeterminaldefinidopelavarirealmenteimportantesãoasdicase oarquivoaoestadooriginal,eomanda áveldeambiente$TER(nãosuportá-los. macetesquenãosãotriviais.Ascordevoltapraondeeleveio.Aúltimalinha Atabela 1apresentaapenasosprincipais reçõesquefaçosãojustamentepara oapagada“lixeira”.Sensacional!Impe- parâmetroseosefeitosresultantes,mas mostrá-los.Masvamospedirdoischocável!Nenhumerro!Viu?Vocêjáestá existemmuitomaisdeles.Parasabertudo pesenquantodouumaolhadelanoteu sobreotput,vejaareferência[1]. pegandoasmanhasdoshell! scriptlánalistagem 1.AêChico,traz –E ntãovamoslá,chegadelesco-lesco Vamosfazerumprogramabembesta doischopes!Enãoseesqueçaqueum eblá-blá-blá,sobreoquênósvamos efácilparailustrarmelhorousodesse falarhoje? comando.Éumaversãodofamigerado delesésemcolarinho! “AlôMundo”,sóquedessavezafrase seráescritanocentrodatelaeemvídeo Listagem 1 – restaura.sh reverso.Depoisdisso,ocursorvoltarápara 01 #!/bin/bash aposiçãooriginal.Vejaalistagem 2. 02 # Comooprogramajáestátodocomen03 # Restaura arqui os deletados ia erreeme tado,achoqueaúnicalinhaqueprecisade 04 # explicaçãoéa8,ondecriamosavariável 05 Coluna.Oestranhoaliéaquelenúmero 06 if [ $# -eq 0 ] 9,quenaverdadeindicaotamanhoda 07 then cadeiadecaracteresquevouescreverna 08 echo "Uso: $0 <)ome do arqui o a ser restaurado>" tela.Dessaforma,esteprogramasomente 09 e it 1 conseguiriacentralizarcadeiasde9carac10 fi teres,masvejaisto:
O comando tput
11 12 13 14 15 16 17
86
# Pega nome do arqui o/diret rio original na última linha Dir='tail -1 /tmp/$LOG)A(E/$1' # O grep - e clui a última linha e recria o arqui o com o diret rio # e nome originalmente usados grep - $Dir /tmp/$LOG)A(E/$1 > $Dir/$1 # Remo e o arqui o que já esta a moribundo rm /tmp/$LOG)A(E/$1
abril2005
edição07 www.linuxmagazine.com.br
$ ar=Papo $ echo ${# ar} 4 $ ar="Papo de Botequim" $ echo ${# ar} 16
LinuxUser
Papodebotequim
Tabela 1: Parâmetros do tput
ealinha12(echo $1)passariaaser:
Parâmetro
Efeito
cup lin col
CUrsor Position – Posiciona o cursor na linha lin e coluna col. A origem (0,0) fica no canto superior esquerdo da tela.
bold
Coloca a tela em modo negrito
echo $*
re
Coloca a tela em modo de vídeo reverso
smso
Idêntico ao anterior
smul
Sublinha os caracteres
Lendo dados da tela Bem,apartirdeagoravamosaprender tudosobreleitura.Sónãopossoensinar alercartasebúziosporquesesoubesse estariarico,numpubLondrinotomando umscotchenãoemumbotecotomando chope.Masvamosemfrente. Daúltimavezemquenosencontramos eudeiumapalhinhasobreocomando read.Antesdeentrarmosemdetalhes, vejasóisso:
blink
Deixa os caracteres piscando
sgr0
Restaura a tela a seu modo normal
reset
Limpa o terminal e restaura suas definições de acordo com terminfo, ou seja, o terminal volta ao comportamento padrão definido pela variável de ambiente $TER(
lines
Informa a quantidade de linhas que compõem a tela
cols
Informa a quantidade de colunas que compõem a tela
el
Erase Line – Apaga a linha a partir da posição do cursor
Papo de Botequim
ed
Erase Display – Apaga a tela a partir da posição do cursor
$ echo $ ar1
il n
Insert Lines – Insere n linhas a partir da posição do cursor
dl n
Delete Lines – Remove n linhas a partir da posição do cursor
dch n
Delete CHaracters – Apaga n caracteres a partir da posição do cursor
sc
Save Cursor position – Salva a posição do cursor
Botequim
Restore Cursor position – Coloca o cursor na posição marcada pelo último sc
$ read ar1 ar2
rc
$ read ar1 ar2 ar3
Papo $ echo $ ar2 de $ echo $ ar3
Papo de Botequim $ echo $ ar1
Ahhh,melhorou!Entãoagorasabemos queaconstrução${# aria el}devolve aquantidadedecaracteresdavariável. Assimsendo,vamosotimizaronosso programaparaqueeleescrevaemvídeo reverso,nocentrodatela(eindependentedonúmerodecaracteres)acadeia decaracterespassadacomoparâmetroe depoisretorneocursoràposiçãoemque estavaantesdaexecuçãodoscript.Veja oresultadonalistagem 3. Estescriptéigualaoanterior,sóque trocamosovalorfixonavariávelColuna (9)por${#1},ondeesse1é$1,ouseja,
essaconstruçãodevolveonúmerode caracteresdoprimeiroparâmetropassadoparaoprograma.Seoparâmetro tivesseespaçosembranco,seriapreciso colocá-loentreaspas,senãoo $1levariaemcontasomenteopedaçoantes doprimeiroespaço.Paraevitareste aborrecimento,ésósubstituiro$1por $*,quecomosabemoséoconjuntode todososparâmetros.Entãoalinha8 ficariaassim: # Centralizando a mensagem na tela Coluna=`$
Colunas - ${#*} / 2 `
Listagem 2: alo.sh 01 02 03 04 05 06 07 08 09 10 11 12 13 14
88
abril2005
# # # / # # #
de Botequim
Comovocêviu,oreadrecebeuma listadeparâmetrosseparadaporespaçosembrancoecolocacadaitemdessa listaemumavariável.Seaquantidade devariáveisformenorqueaquantidade deitens,aúltimavariávelrecebeorestantedeles.Eudisselistaseparadapor espaçosembranco,masagoraquevocê jáconhecetudosobreo$IFS(InterField Separator–Separadorentrecampos),que
Listagem 3: alo.sh melhorado
#!/bin/bash # Script bobo para testar # o comando tput ersao 1 Colunas=`tput cols` Linhas=`tput lines` Linha=$ Linhas / 2 Coluna=$ Colunas - 9 tput sc tput cup $Linha $Coluna tput re echo Al (undo tput sgr0 tput rc
Papo $ echo $ ar2
Sal a a quantidade de colunas na tela Sal a a quantidade linhas na tela Qual é a linha central da tela? 2 # Centraliza a mensagem na tela Sal a a posição do cursor Posiciona o cursor antes de escre er Vídeo re erso
# Restaura o ídeo ao normal # Restaura o cursor à posição original
edição07 www.linuxmagazine.com.br
01 02 03 04 05 06 07 08 09 10 11 12 13 14
#!/bin/bash # Script bobo para testar # o comando tput ersão 2.0 Colunas=`tput cols` # Linhas=`tput lines` # Linha=$ Linhas / 2 # Coluna=$ Colunas - ${#1} tput sc # tput cup $Linha $Coluna # tput re # echo $1 tput sgr0 # tput rc #
Sal a a quantidade de colunas na tela Sal a a quantidade de linhas na tela Qual é a linha central da tela? / 2 # Centraliza a mensagem na tela Sal a a posicao do cursor Posiciona o cursor antes de escre er Video re erso Restaura o ídeo ao normal De ol e o cursor à posição original
Papodebotequim
euteapresenteiquandofalávamosdo comando for,seráqueaindaacredita nisso?Vamostestar: $ oIFS="$IFS" $ IFS=: $ read ar1 ar2 ar3 Papo de Botequim $ echo $ ar1
paraqueo\nfosseentendidocomo umaquebradelinha(newline)enão comoumliteral.SoboBashexistemdiversasopçõesdoreadqueservemparafacilitarasuavida.Veja atabela 2. Eagoradiretoaosexemploscurtos parademonstrarestasopções.Paraler umcampo“Matrícula”:
Papo de Botequim $ echo $ ar2
# -n não salta linha
$ echo $ ar3
$ echo -n "(atricula: "; read (at
$ read ar1 ar2 ar3
(atricula: 12345
Papo:de:Botequim
$ echo $(at
$ echo $ ar1
12345
Papo
Podemossimplificarascoisasusando aopção-p:
$ echo $ ar2 de
LinuxUser
$ read -t2 -p "Digite seu nome completo: U " )om || echo 'Eita moleza!' Digite seu nome completo: Eita moleza! $ echo $)om
Oexemploacimafoiumabrincadeira, poiseusótinha2segundosparadigitaro meunomecompletoemaltivetempode teclarumJ(aquelecoladonoEita),mas eleserviuparamostrarduascoisas: P1)Ocomandoapósopardebarrasverticais(oou–or–lógico,lembra-se?)será executadocasoadigitaçãonãotenha sidoconcluídanotempoestipulado; P2)Avariável)ompermaneceuvazia.Ela sóreceberáumvalorquandooENTER forteclado. $ read -sp “Senha: “
$ echo $ ar3 Botequim
$ read -p "(atricula: " (at
Senha: $ echo $REPLY
$ IFS="$oIFS"
(atricula: 12345
segredo :
Viu?euestavafurado!Oreadlêuma lista,assimcomoo for,separadapelos caracteresdavariável $IFS.Vejacomo issopodefacilitarasuavida:
12345
$ echo $(at
$ grep julio /etc/pass d
Epodemoslerapenasumaquantidade pré-determinadadecaracteres: $ read -n5 -p"CEP: " )um ; read -n3 U
julio: :500:544:Julio C. )e es - 7070:U
-p- Compl
/home/julio:/bin/bash
CEP: 12345-678$
$ oIFS="$IFS"
$ echo $)um
# Sal a o IFS antigo.
$ IFS=:
12345
$ grep julio /etc/pass d | read lname U
$ echo $Compl
li o uid gid coment home shell
678
$ echo -e "$lname\n$uid\n$gid\n$comentU \n$home\n$shell" julio 500 544 Julio C. )e es - 7070 /home/julio /bin/bash $ IFS="$oIFS"
# Restaura o IFS
Comovocêviu,asaídadogrepfoi redirecionadaparaocomandoread, queleutodososcamposdeumasó tacada.Aopção-edoechofoiusada
Noexemploacimaexecutamosduas vezesocomandoread:umparaaprimeirapartedoCEPeoutraparaoseu complemento,destemodoformatando aentradadedados.Ocifrão($)logo apósoúltimoalgarismodigitadoé necessárioporqueo readnãoinclui por padrão um caractere new line implícito,comooecho. Paralersóduranteumdeterminado limitedetempo(tambémconhecido comotimeout):
Tabela 2: Opções do read Opção
Ação
-p prompt
Escreve “prompt” na tela antes de fazer a leitura
-n num
Lê até num caracteres
-t seg
Espera seg segundos para que a leitura seja concluída
-s
Não exibe na tela os caracteres digitados.
Aproveiteiumerronoexemploanteriorparamostrarummacete.Quando escreviaprimeiralinha,esquecidecolocaronomedavariávelqueiriarecebera senhaesónoteiissoquandoiaescrevêla.Felizmenteavariável$REPLYdoBash contémaúltimaseqüênciadecaracteres digitada–emeaproveiteidissoparanão perderaviagem.Testevocêmesmooque acabeidefazer. Oexemploquedei,naverdade,erapara mostrarqueaopção-simpedequeoque estásendodigitadosejamostradonatela. Comonoexemploanterior,afaltadonew linefezcomqueopromptdecomando ($)permanecessenamesmalinha. Agoraquesabemoslerdatela,vejamos comoselêemosdadosdosarquivos.
Lendo arquivos Comoeujáhavialhedito,evocêdevese lembrar,o hiletestaumcomandoeexecutaumblocodeinstruçõesenquantoesse comandoforbemsucedido.Ora,quando vocêestálendoumarquivoparaoqual vocêtempermissãodeleitura,oreadsó serámalsucedidoquandoalcançaroEOF (EndOfFile–FimdoArquivo).Portanto, podemoslerumarquivodeduasmaneiras. Aprimeiraéredirecionandoaentradado arquivoparaobloco hile,assim: hile read Linha do echo $Linha done < arqui o
abril2005 www.linuxmagazine.com.br
edição07
89
LinuxUser
Asegundaéredirecionandoasaída deum catparao hile,daseguinte maneira: cat arqui o | hile read Linha do echo $Linha done
Cadaumdosprocessostemsuasvantagensedesvantagens.Oprimeiroémais rápidoenãonecessitadeumsubshell paraassisti-lomas,emcontrapartida,o redirecionamentoficapoucovisívelem umblocodeinstruçõesgrande,oque porvezesprejudicaavisualizaçãodo código.Osegundoprocessotrazavantagemdeque,comoonomedoarquivo estáantesdowhile,avisualizaçãodo códigoémaisfácil.Entretanto,oPipe (|)chamaumsubshellparainterpretá-lo, tornandooprocessomaislentoepesado. Parailustraroquefoidito,vejaosexemplosaseguir:
Papodebotequim
Comovocêviu,oscriptlistasuasprópriaslinhascomumsinaldemenos(-) anteseoutrodepoisdecadaumae,no final,exibeoconteúdodavariável$Ultimo. Repare,noentanto,queoconteúdodessa variávelpermanecevazio.Ué,seráque avariávelnãofoiatualizada?Foi,eisso podesercomprovadoporquealinhaecho "-$Ultimo-"listacorretamenteaslinhas. Entãoporqueissoaconteceu? Comoeudisse,oblocodeinstruções redirecionadopelopipe(|)éexecutado emumsubshelle,lá,asvariáveissão atualizadas.Quandoessesubshelltermina,asatualizaçõesdasvariáveisvão paraasprofundezasdoinfernojuntocom ele.Reparequevoufazerumapequena mudançanoscript,passandooarquivo porredirecionamentodeentrada(<),e ascoisaspassarãoafuncionarnamais perfeitaordem:
#!/bin/bash
#!/bin/bash # Programa de teste para escre er # 10 linhas e parar para ler # Versão 1 hile read )um do let ContLin++
# Contando...
# -n para não saltar linha ContLin % 10
> /de /null || read
done < numeros
# redirread.sh
#!/bin/bash
Ultimo=" azio "
# readpipe.sh
# Passa o script $0 para o hile
# por um pipe.
$ cat 10porpag.sh
echo -n "$)um "
# por um pipe.
# E emplo de read passando um arqui o
$ seq 30 > numeros
$ cat redirread.sh
# E emplo de read passando o arqui o $ cat readpipe.sh
quevoumostrarcomumexemploprático. Suponhaquevocêqueiralistarumarquivo equerqueacadadezregistrosessalistagem pareparaqueooperadorpossaleroconteúdodatela,equeelasócontinuedepois deooperadorpressionarqualquertecla. Paranãogastarpapel(daLinuxMagazine), voufazeressalistagemnahorizontal.Meu arquivo(numeros)tem30registroscom númerosseqüenciais.Veja:
hile read Linha do
Ultimo=" azio "
Ultimo="$Linha"
# Passa o script $0 para o hile
echo "-$Ultimo-"
cat $0 | hile read Linha
done < $0
do
echo "Acabou, Último=:$Ultimo:"
Natentativadefazerumprograma genéricocriamosavariável$ContLin(na vidareal,osregistrosnãosãosomente númerosseqüenciais)e,quandotestamos seorestodadivisãoerazero,mandamos asaídapara/de /null,praqueelanão apareçanatela.Masquandofuiexecutar oprogramadescobriaseguintezebra: $ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 U
Ultimo="$Linha"
Vejacomoelerodaperfeitamente:
echo "-$Ultimo-"
18 19 20 21 23 24 25 26 27 28 29 30
done $ redirread.sh
echo "Acabou, Último=:$Ultimo:"
-#!/bin/bash-
Vamosveroresultadodesuaexecução:
-# redirread.sh-# E emplo de read passando o arqui o
$ readpipe.sh
-# por um pipe.-
-#!/bin/bash-
--
-# readpipe.sh-
-Ultimo=" azio "-
-# E emplo de read passando um arqui o
- hile read Linha-
-# por um pipe.-
-do-
$ cat 10porpag.sh
--
-Ultimo="$Linha"-
#!/bin/bash
-Ultimo=" azio "-
-echo "-$Ultimo-"-
# Programa de teste para escre er
-# Passa o script $0 para o hile-
-# Passa o script $0 para o hile-
# 10 linhas e parar para ler - Versão 2
-cat $0 | -
-done < $0-
- hile read Linha-
-echo "Acabou, Último=:$Ultimo:"-
hile read )um do
-do-
Acabou, Último=:echo "Acabou,U
let ContLin++
-Ultimo="$Linha"-
Último=:$Ultimo:":
# -n para não saltar linha
-echo "-$Ultimo-"-echo "Acabou, Último=:$Ultimo:"Acabou, Último=: azio :
abril2005
# Contando...
echo -n "$)um "
Bem,amigosdaRedeShell,parafinalizaraaulasobreocomandoreadsófalta maisumpequenoeimportantemacete
-done-
90
Reparequefaltouonúmero11ealistagemnãoparounoread.Todaaentradado loopestavaredirecionadaparaoarquivo numerosealeiturafoifeitaemcimadesse arquivo,perdendoonúmero11.Vamos mostrarcomodeveriaficarocódigopara queelepasseafuncionaracontento:
edição07 www.linuxmagazine.com.br
ContLin % 10 < /de /tt done < numeros
> /de /null || read U
Papodebotequim
Reparequeagoraaentradadoreadfoi redirecionadapara /de /tt ,quenada maisésenãooterminalcorrente,explicitandodestaformaqueaquelaleitura seriafeitadotecladoenãodoarquivo numeros.Ébomrealçarqueissonão acontecesomentequandousamosoredirecionamentodeentrada;setivéssemos usadooredirecionamentoviapipe(|),o mesmoteriaocorrido.Vejaagoraaexecuçãodoscript: $ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
Issoestáquasebom,masaindafalta umpoucoparaficarexcelente.Vamos melhoraroexemploparaquevocêo reproduzaeteste(mas,antesdetestar, aumenteonúmeroderegistrosemnumerosoureduzaotamanhodatela,para quehajaquebradelinha). $ cat 10porpag.sh #!/bin/bash # Programa de teste para escre er
LinuxUser
–Bemmeuamigo,porhojeésóporque achoquevocêjáestádesacocheio… clear –Numtônão,podecontinuar… hile read )um –Sevocênãoestivereuestou…Masjá do quevocêestátãoempolgadocomoshell, # Contando... voutedeixarumserviçobastantesimContLin++ plesparavocêmelhorarasuacdteca: Montetodaatelacomumúnico echo echo "$)um" ContLin % `tput lines` - 3 || edepoisposicioneocursoràfrentede { cadacampoparareceberovalorque # para ler qualquer caractere serádigitadopelooperador. Nãoseesqueçamque,emcasode read -n1 -p"Tecle Algo " < /de /tt qualquerdúvidaoufaltadecompanhia # limpa a tela após a leitura paraumchopeésómandarume-mail clear parajulio.neves@gmail.com.Vouaproveitar } tambémparafazerumapropaganda: done < numeros digamaosamigosquequemestivera Amudançasubstancialfeitanesteexem- fimdefazerumcurso“porreta”deproploécomrelaçãoàquebradepágina,já gramaçãoemshelldevemandarumequeelaéfeitaacadaquantidade-de-linhas- mailparajulio.neves@tecnohall.com.brpara da-tela(tput lines)menos(-)três,isto informar-se.Atémais! é,seatelatem25linhas,oprograma listará22registrosepararáparaleitura. Informações Nocomandoreadtambémfoifeitauma [1] Página oficial do Tput: http://www.cs.utah.edu/ alteração,inseridooparâmetro-n1para dept/old/texinfo/tput/tput.html#SEC4 lersomenteumcaracterequalquer,não [2] Página oficial do Bash: http://www.gnu.org/ necessariamenteumENTER,eaopção software/bash/bash.html -pparaexibirumamensagem. # 10 linhas e parar para ler # Versão 3
abril2005 www.linuxmagazine.com.br
edição07
91
LinuxUser
Papodebotequim
Curso de Shell Script
PapodeBotequim
ParteVIII
Dave Hamilton - www.sxc.hu
Chegou a hora de fazer como Jack e dividir os programas em pedacinhos. Com funções e chamadas externas os scripts ficam menores, a manutenção mais fácil e ainda por cima reaproveitamos código. porJúlioCezarNeves
E
posicionais($1, $2, …, $n).Todasasregrasqueseaplicam aê,cara,tudobem? –Tudobeleza!Euqueriatemostraroquefizmasjásei àpassagemdeparâmetrosparaprogramastambémvalempara quevocêvaiquerermolharobicoprimeiro,né? funções,masémuitoimportanterealçarqueosparâmetros – Sópratecontrariar,hojenãoquero.Vai,mostralogoaí passadosparaumprogramanãoseconfundemcomaqueles oquevocêfez. quesãopassadosparasuasfunções.Issosignifica,porexem–Poxa,oexercícioquevocêpassouémuitogrande.Dáuma plo,queo $1deumscriptédiferentedo $1deumadesuas olhadanalistagem 1evêcomoeuresolvi: funçõesinternas. – É,oprogramatálegal,tátodoestruturadinho,masgostaReparequeasvariáveis $(sg, $Tam(sge $Colsãodeuso riadefazeralgunspoucoscomentários:sópararelembrar, restritodessarotinae,porisso,foramcriadascomovariáveis asseguintesconstruções:[ ! $Album ] &&e[ $(usica ] locais.Arazãoésimplesmenteaeconomiadememória,jáque ||representamamesmacoisa,istoé:nocasodaprimeira, aosairdarotinaelasserãodevidamentedetonadas,coisaque testamosseavariável$Albumnão(!)temnadadentro,então nãoaconteceriaseeunãotivesseusadoesseartifício. (&&)…Nasegunda,testamosse $(usicatemalgumdado, Alinhadecódigoquecriaavariávellocal(sgconcatenaao senão(||)… textorecebido($1)umparêntese,arespostapadrão($2)em Sevocêreclamoudotamanhodoprograma,éporqueainda caixaalta,umabarra,aoutraresposta($3)emcaixabaixa nãotedeialgumasdicas.Reparequeamaiorpartedoscript efinalizafechandooparêntese.Usoessaconvençãopara,ao éparamostrarmensagenscentralizadasnapenúltimalinhadatela.Repare Listagem 1: musinc5.sh aindaquealgumasmensagenspedem 01 $ cat musinc5.sh umSouum)comorespostaeoutras 02 #!/bin/bash sãosódeadvertência.Issoéumcaso 03 # Cadastra CDs ersao 5 típicoquepedeousodefunções,que 04 # seriamescritassomenteumaveze 05 clear executadasemdiversospontosdo 06 Linha(esg=$ `tput lines` - 3 # Linha onde serão mostradas as msgs para o operador script.Voumontarduasfunçõespara 07 TotCols=$ tput cols # “td colunas da tela para enquadrar msgs resolveressescasosevamosincor08 echo porá-lasaoseuprogramaparavero Inclusão de (úsicas resultadofinal. ======== == ======= –Chico!Agoratrazdoischopes,um Título do Álbum: semcolarinho,paramedarinspira| Este campo foi ção.Evocê,deolhonalistagem 2. Fai a: < criado somente para Comopodemosver,umafunçãoé | orientar o preenchimento definidaquandodigitamos nome_da_ )ome da (úsica: função etodooseucorpoestá Intérprete: # Tela montada com um único echo entrechaves({}).Jáconversamosaqui 09 hile true nobotecosobrepassagemdeparâ10 do metroseasfunçõesosrecebemda 11 tput cup 5 38; tput el # Posiciona e limpa linha ➟ mesmaforma,istoé,sãoparâmetros
–
86
maio2005
edição08 www.linuxmagazine.com.br
papodebotequim
12 read Album 13 [ ! $Album ] && # Operador deu <E)TE”> 14 { 15 (sg= Deseja Terminar? S/n 16 Tam(sg=${#(sg} 17 Col=$ TotCols - Tam(sg / 2 # Centraliza msg na linha 18 tput cup $Linha(esg $Col 19 echo $(sg 20 tput cup $Linha(esg $ Col + Tam(sg + 1 21 read -n1 S) 22 tput cup $Linha(esg $Col; tput el # Apaga msg da tela 23 [ $S) = ) -o $S) = n ] && continue # $S) é igual a ) ou -o n? 24 clear; e it # Fim da e ecução 25 } 26 grep ^$Album\^ musicas > /de /null && 27 { 28 (sg= Este álbum já está cadastrado 29 Tam(sg=${#(sg} 30 Col=$ TotCols - Tam(sg / 2 # Centraliza msg na linha 31 tput cup $Linha(esg $Col 32 echo $(sg 33 read -n1 34 tput cup $Linha(esg $Col; tput el # Apaga msg da tela 35 continue # Volta para ler outro álbum 36 } 37 ”eg= $Album^ # $”eg receberá os dados para gra ação 38 oArtista= # Variá el que guarda artista anterior 39 hile true 40 do 41 Fai a++ 42 tput cup 7 38 43 echo $Fai a 44 tput cup 9 38 # Posiciona para ler música 45 read (usica 46 [ $(usica ] || # Se o operador ti er dado <E)TE”>... 47 { 48 (sg= Fim de Álbum? S/n 49 Tam(sg=${#(sg} 50 Col=$ TotCols - Tam(sg / 2 # Centraliza msg na linha 51 tput cup $Linha(esg $Col 52 echo $(sg 53 tput cup $Linha(esg $ Col + Tam(sg + 1 54 read -n1 S) 55 tput cup $Linha(esg $Col; tput el # Apaga msg da tela 56 [ $S) = ) -o $S) = n ] && continue # $S) é igual a ) ou -o n? 57 break # Sai do loop para gra ar 58 } 59 tput cup 11 38 # Posiciona para ler Artista 60 [ $oArtista ] && echo -n $oArtista # Artista anterior é default 61 read Artista 62 [ $Artista ] && oArtista= $Artista 63 ”eg= $”eg$oArtista~$(usica: # (ontando registro 64 tput cup 9 38; tput el # Apaga (úsica da tela 65 tput cup 11 38; tput el # Apaga Artista da tela 66 done 67 echo $”eg >> musicas # Gra a registro no fim do arqui o 38 sort musicas -0 musicas # Classifica o arqui o 69 done
LinuxUser
mesmotempo,mostrarasopçõesdisponíveiserealçararespostaoferecida comopadrão. Quasenofimdarotina,aresposta recebida($S))éconvertidaparacaixa baixa(minúsculas)deformaqueno corpodoprogramanãoprecisemos fazer esse teste. Veja na listagem 3 comoficariaafunçãoparaexibiruma mensagemnatela. Essaéumaoutraformadedefinir umafunção:nãoachamamos,como no exemplo anterior, usando uma construçãocomasintaxe nome_da_ função ,massimcomofunction nome_da_função.Emnadamaisela diferedaanterior,excetoque,como constadoscomentários,usamosa variável $*que,comojásabemos, representaoconjuntodetodosos parâmetrospassadosaoscript,para queoprogramadornãopreciseusar aspasenvolvendoamensagemque desejapassaràfunção. Paraterminarcomesseblá-blá-blá, vamosvernalistagem 4asalterações noprogramaquandousamosoconceitodefunções: Reparequeaestruturadoscript segueaordemVariáveisGlobais,FunçõeseCorpodoPrograma.EstaestruturaçãosedeveaofatodeShellScriptser umalinguageminterpretada,emque oprogramaélidodaesquerdapara adireitaedecimaparabaixo.Para servistapeloscriptesuasfunções, umavariáveldeveserdeclarada(ou inicializada,comopreferemalguns) antesdequalqueroutracoisa.Porsua vez,asfunçõesdevemserdeclaradas antesdocorpodoprogramapropriamentedito.Aexplicaçãoésimples:o interpretadordecomandosdoshell devesaberdoquesetrataafunção antesqueelasejachamadanoprogramaprincipal. Umacoisabacananacriaçãodefunçõeséfazê-lastãogenéricasquanto possível,deformaquepossamser reutilizadasemoutrosscriptseaplicativossemanecessidadedereinventarmosaroda.Asduasfunçõesque acabamosdeversãobonsexemplos, poisédifícilumscriptdeentradade dadosquenãouseumarotinacomo a(anda(sgouquenãointerajacom ooperadorpormeiodealgosemelhanteàPergunta.
maio2005 www.linuxmagazine.com.br
edição08
87
LinuxUser
Papodebotequim
Conselhodeamigo:crieumarquivoeanexeaelecadafunçãonovaquevocêcriar.Aofinaldealgumtempovocêterá umabelabibliotecadefunçõesquelhepouparámuitotempo deprogramação.
diretório/home.Sóqueassimqueaexecuçãodoscriptterminou,osub-shellfoiparaobeleléue,comele,todooambiente criado.Agorapresteatençãonoexemploabaixoevejacomoa coisamudadefigura:
O comando source
$ source script_bobo
Vejasevocênotaalgodediferentenasaídadolsaseguir:
jne es
juliana
paula
sil ie
$ p d $ ls -la .bash_profile -r -r--r-- 1 Julio unkno n
/home 4511 Mar 18 17:45 .bash_profile
$ cd /home/jne es
Nãoolhearespostanão,volteaprestaratenção!Bem,jáque $ . script_bobo vocêestámesmosemsacodepensareprefereleraresposta, jne es juliana paula sil ie voutedarumadica:achoquevocêjásabequeo.bash_pro$ p d fileéumdosscriptsquesãoautomaticamenteexecutados /home quandovocêse“loga”(ARRGGHH!Odeio essetermo!)nosistema.AgoraolhenovaListagem 2: Função Pergunta menteparaasaídadocomandolseme 01 Pergunta digaoquehádediferentenela. 02 { Comoeudisse,o.bash_profileéexe03 # A função recebe 3 parâmetros na seguinte ordem: cutadoduranteologon,masreparequeele 04 # $1 - (ensagem a ser mostrada na tela nãotemnenhumapermissãodeexecução. 05 # $2 - Valor a ser aceito com resposta padrão Issoaconteceporquesevocêoexecutasse 06 # $3 - O outro alor aceito comoqualqueroutroscriptcareta,nofim 07 # Supondo que $1=Aceita?, $2=s e $3=n, a linha a desuaexecuçãotodooambienteporele 08 # seguir colocaria em (sg o alor Aceita? S/n geradomorreriajuntocomoshellsobo 09 local (sg= $1 `echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z` qualelefoiexecutado(vocêselembrade 10 local Tam(sg=${#(sg} quetodososscriptssãoexecutadosem 11 local Col=$ TotCols - Tam(sg / 2 # Centra msg na linha sub-shells,né?).Poiséparacoisasassim 12 tput cup $Linha(esg $Col queexisteocomandosource,também conhecidopor“.”(ponto).Estecomando 13 echo $(sg fazcomqueoscriptquelheforpassado 14 tput cup $Linha(esg $ Col + Tam(sg + 1 comoparâmetronãosejaexecutadoem 15 read -n1 S) umsub-shell.Masémelhorumexemplo 16 [ ! $S) ] && S)=$2 # Se azia coloca default em S) queumaexplicaçãoem453palavras.Veja 17 echo $S) | tr A-Z a-z # A saída de S) será em minúscula oscriptzinhoaseguir: 18 tput cup $Linha(esg $Col; tput el # Apaga msg da tela 19 $ cat script_bobo
20
return
# Sai da função
}
cd .. ls
Elesimplesmentedeveriairparaodiretório acima do diretório atual. Vamos executar uns comandos envolvendo o script_boboeanalisarosresultados: $ p d /home/jne es $ script_bobo jne es
juliana
paula
sil ie
$ p d /home/jne es
Seeumandeielesubirumdiretório, porquenãosubiu?Opa,peraíquesubiu sim!Osub-shellquefoicriadoparaexecutaroscripttantosubiuquelistouos diretóriosdosquatrousuáriosabaixodo
88
maio2005
Listagem 3: Função MandaMsg 01 function (anda(sg 02 { 03 # A função recebe somente um parâmetro 04 # com a mensagem que se deseja e ibir. 05 # para não obrigar o programador a passar 06 # a msg entre aspas, usaremos $* todos 07 # os parâmetros, lembra? e não $1. 08 local (sg= $* 09 local Tam(sg=${#(sg} 10 local Col=$ TotCols - Tam(sg / 2 # Centra msg na linha 11 tput cup $Linha(esg $Col 12 echo $(sg 13 read -n1 14 tput cup $Linha(esg $Col; tput el # Apaga msg da tela 15 return # Sai da função 16 }
edição08 www.linuxmagazine.com.br
LinuxUser
papodebotequim
Listagem 4: musinc6.sh
52 echo Inclusão de (úsicas
01 $ cat musinc6
======== == =======
02 #!/bin/bash 03 # Cadastra CDs
Título do Álbum:
ersao 6
Fai a:
04 #
)ome da (úsica:
05 # Área de ariá eis globais
Intérprete:
06 # Linha onde as mensagens serão e ibidas
53 hile true
07 Linha(esg=$ `tput lines` - 3 08 # “uantidade de colunas na tela para enquadrar as mensagens 09 TotCols=$ tput cols 10 # Área de funções 11 Pergunta
54 do 55
tput cup 5 38; tput el
56
read Album
57
[ ! $Album ] &&
58
12
{
13
# A função recebe 3 parâmetros na seguinte ordem:
14
# $1 - (ensagem a ser dada na tela
15
# $2 - Valor a ser aceito com resposta default
16
# $3 - O outro alor aceito
17
# Supondo que $1=Aceita?, $2=s e $3=n, a linha
18
# abai o colocaria em (sg o alor Aceita? S/n
19
local (sg= $1 `echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`
20
local Tam(sg=${#(sg}
21
# Centraliza a mensagem na linha
22
local Col=$
23
tput cup $Linha(esg $Col
24
echo $(sg
25
tput cup $Linha(esg $ Col + Tam(sg + 1
26
read -n1 S)
27
[ ! $S) ] && S)=$2
28
echo $S) | tr A-Z a-z # A saída de S) será em minúsculas
29
tput cup $Linha(esg $Col; tput el
# Apaga msg da tela
30
return
# Sai da função
31
}
TotCols - Tam(sg / 2
# Se azia, coloca default em S)
32 function (anda(sg {
34
# A função recebe somente um parâmetro
35
# com a mensagem que se deseja e ibir;
36
# para não obrigar o programador a passar
37
# a msg entre aspas, usaremos $* todos
38
# os parâmetro, lembra? e não $1.
39
local (sg= $*
40
local Tam(sg=${#(sg}
41
# Centraliza mensagem na linha
42
local Col=$
59
Pergunta Deseja Terminar s n
60
# Agora só testo cai a bai a
61
[ $S) = n ] && continue
62
clear; e it
63
}
64
43
tput cup $Linha(esg $Col
44
echo $(sg
45
read -n1
46
tput cup $Linha(esg $Col; tput el # Apaga msg da tela
47
return
48
}
# Sai da função
49 # O corpo do programa propriamente dito começa aqui 50 clear 51 # A tela a seguir é montada com um único comando echo
# Fim da e ecução
grep -iq ^$Album\^ musicas 2> /de /null &&
65
{
66
(anda(sg Este álbum já está cadastrado
67
continue
68
}
# Volta para ler outro álbum
69
”eg= $Album^
# $”eg receberá os dados de gra ação
70
oArtista=
# Guardará artista anterior
71
hile true
72
do
73
Fai a++
74
tput cup 7 38
75
echo $Fai a
76
tput cup 9 38
77
read (usica
78
[ $(usica ] ||
# Posiciona para ler música # Se o operador teclou <E)TE”>...
{
80
Pergunta Fim de Álbum? s n
81
# Agora só testo a cai a bai a
82
[ $S) = n ] && continue
83
break
84
# Sai do loop para gra ar dados
}
85
TotCols - Tam(sg / 2
# Operador deu <E)TE”>
{
79
33
# Posiciona e limpa linha
tput cup 11 38
# Posiciona para ler Artista
86
# O artista anterior é o padrão
87
[ $oArtista ] && echo -n
88
read Artista
89
[ $Artista ] && oArtista= $Artista
90
”eg= $”eg$oArtista~$(usica: # (ontando registro
$oArtista
91
tput cup 9 38; tput el
# Apaga (úsica da tela
92
tput cup 11 38; tput el
# Apaga Artista da tela
93 94
done # Gra a registro no fim do arqui o
95
echo $”eg >> musicas
96
# Classifica o arqui o
97
sort musicas -o musicas
98 done
maio2005 www.linuxmagazine.com.br
edição08
89
LinuxUser
Papodebotequim
Listagem 5: musinc7.sh 01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
90
$ cat musinc7.sh #!/bin/bash # Cadastra CDs ersao 7 # # Área de ariá eis globais Linha(esg=$ `tput lines` - 3 # Linha onde serão mostradas as msgs para o operador TotCols=$ tput cols # “td colunas da tela para enquadrar msgs # O corpo do programa propriamente dito começa aqui clear echo Inclusão de (úsicas ======== == ======= Título do Álbum: | Este campo foi Fai a: < criado somente para | orientar o preenchimento )ome da (úsica: Intérprete: # Tela montada com um único echo hile true do tput cup 5 38; tput el # Posiciona e limpa linha read Album [ ! $Album ] && # Operador deu <E)TE”> { source pergunta.func Deseja Terminar s n [ $S) = n ] && continue # Agora só testo a cai a bai a clear; e it # Fim da e ecução } grep -iq ^$Album\^ musicas 2> /de /null && { . mandamsg.func Este álbum já está cadastrado continue # Volta para ler outro álbum } ”eg= $Album^ # $”eg receberá os dados de gra ação oArtista= # Guardará artista anterior hile true do Fai a++ tput cup 7 38 echo $Fai a tput cup 9 38 # Posiciona para ler música read (usica [ $(usica ] || # Se o operador ti er dado <E)TE”>... { . pergunta.func Fim de Álbum? s n [ $S) = n ] && continue # Agora só testo a cai a bai a break # Sai do loop para gra ar dados } tput cup 11 38 # Posiciona para ler Artista [ $oArtista ] && echo -n $oArtista # Artista anterior é default read Artista [ $Artista ] && oArtista= $Artista ”eg= $”eg$oArtista~$(usica: # (ontando registro tput cup 9 38; tput el # Apaga (úsica da tela tput cup 11 38; tput el # Apaga Artista da tela done echo $”eg >> musicas # Gra a registro no fim do arqui o sort musicas -o musicas # Classifica o arqui o done
maio2005
edição08 www.linuxmagazine.com.br
Ahh!Agorasim!Quandopassado comoparâmetrodocomandosource, o script foi executado no shell corrente,deixandoneletodooambiente criado.Agoravamosrebobinarafita atéoiníciodaexplicaçãosobreeste comando.Láfalamosdo .bash_profilee,aestaaltura,vocêjádevesaber quesuaincumbênciaé,logoapóso login,prepararoambientedetrabalho paraousuário.Agoraentendemosque éporissomesmoqueeleéexecutado usandoesseartifício. E agora você deve estar se perguntandoseésóparaissoqueesse comandoserve.Eulhedigoquesim, masissonostrazummontedevantagens–eumadasmaisusadasétratar funçõescomorotinasexternas.Veja nalistagem 5umaoutraformadefazer onossoprogramaparaincluirCDsno arquivomusicas. Agoraoprogramadeuumaboaencolhidaeaschamadasdefunçãoforam trocadasporarquivosexternoschamadospergunta.funcemandamsg.func, queassimpodemserchamadospor qualqueroutroprograma,dessaforma reutilizandooseucódigo. Pormotivosmeramentedidáticos,as chamadasapergunta.funcemandamsg.funcestãosendofeitasporsource epor.(ponto)indiscriminadamente, emboraeuprefiraosourceque,por sermaisvisível,melhoraalegibilidadedocódigoefacilitasuaposterior manutenção.Vejanalistagem6como ficaramessesdoisarquivos. Emambososarquivos,fizsomente duas mudanças, que veremos nas observaçõesaseguir.Porém,tenho maistrêsobservaçõesafazer: 1. Asvariáveisnãoestãosendomais declaradascomolocais,porqueessa éumadiretivaquesópodeserusada nocorpodefunçõese,portanto,essas variáveispermanecemnoambientedo shell,poluindo-o; 2. Ocomandoreturnnãoestámais presente,maspoderiaestarsemalteraremnadaalógicadoscript,uma vezquesóserviriaparaindicarum eventualerropormeiodeumcódigo deretornopreviamenteestabelecido (porexemploreturn 1,return 2,…), sendoqueo returne return 0são idênticosesignificamquearotinafoi executadasemerros;
papodebotequim
LinuxUser
3. Ocomandoqueestamosacostumados Listagem 6: pergunta.func e mandamsg.func ausarparagerarumcódigoderetorno 01 $ cat pergunta.func éoexit,masasaídadeumarotina 02 # A função recebe 3 parâmetros na seguinte ordem: externanãopodeserfeitadessaforma 03 # $1 - (ensagem a ser dada na tela porque,comoelaestásendoexecutada 04 # $2 - Valor a ser aceito com resposta default nomesmoshelldoscriptqueocha05 # $3 - O outro alor aceito mou,oexitsimplesmenteencerraria 06 # Supondo que $1=Aceita?, $2=s e $3=n, a linha esseshell,terminandoaexecuçãode 07 # abai o colocaria em (sg o alor Aceita? S/n todooscript; 08 (sg= $1 `echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z` 4. DeondeveioavariávelLinha(esg?Ela 09 Tam(sg=${#(sg} veiodoscriptmusinc7.sh,porquehavia 10 Col=$ TotCols - Tam(sg / 2 # Centraliza msg na linha sidodeclaradaantesdachamadadas 11 tput cup $Linha(esg $Col rotinas(nuncaesqueçaqueoshellque 12 echo $(sg estáinterpretandooscripteessasroti13 tput cup $Linha(esg $ Col + Tam(sg + 1 naséomesmo); 14 read -n1 S) 5. Sevocêdecidirusarrotinasexternas 15 [ ! $S) ] && S)=$2 # Se azia coloca default em S) nãoseenvergonhe,exagerenoscomen16 echo $S) | tr A-Z a-z # A saída de S) será em minúscula tários,principalmentesobreapassagem 17 tput cup $Linha(esg $Col; tput el # Apaga msg da tela dosparâmetros,parafacilitaramanu18 $ cat mandamsg.func tençãoeousodessarotinaporoutros 19 # A função recebe somente um parâmetro programasnofuturo. 20 # com a mensagem que se deseja e ibir; –Bem, agora você já tem mais um 21 # para não obrigar o programador a passar montedenovidadespara melhorar 22 # a msg entre aspas, usaremos $* todos osscriptsquefizemos.Vocêselem23 # os parâmetro, lembra? e não $1. bradoprogramalistartista.shnoqual 24 (sg= $* vocêpassavaonomedeumartista 25 Tam(sg=${#(sg} comoparâmetroeeledevolviaassuas 26 Col=$ TotCols - Tam(sg / 2 # Centraliza msg na linha músicas?Eleeracomoomostradoaqui 27 tput cup $Linha(esg $Col embaixonalistagem 7. 28 echo $(sg –Claroquemelembro! 29 read -n1 –Parafirmarosconceitosquetepas30 tput cup $Linha(esg $Col; tput el # Apaga msg da tela sei,faça-ocomatelaformatadaea execuçãoemloop,deformaqueele sóterminequandoreceberumEnternolugardonome Não se esqueça: em caso de dúvida ou falta de compadoartista.Suponhaqueatelatenha25linhas;acada22 nhiaparaumchope,ésómandarume-mailparaoendereço músicaslistadasoprogramadeverádarumaparadapara julio.neves@gmail.comquetereiprazeremlheajudar.Vouaproveitar queooperadorpossalê-las.Eventuaismensagensdeerro tambémparamandarminhapropaganda:digaaosamigosque devemserpassadasusandoarotina mandamsg.funcque quemestiverafimdefazerumcursoporretadeprogramação acabamosdedesenvolver.Chico,mandamaisdois!!Omeu emShelldevemandarume-mailparajulio.neves@tecnohall.com.br parainformar-se.Atémais! ■ écompoucapressão…
Informações
Listagem 7: listartista.sh $ cat listartista.sh #!/bin/bash # Dado um artista, mostra as suas musicas # ersao 2 if [ $# -eq 0 ] then echo Voce de eria ter passado pelo menos um parametro e it 1 fi IFS= : for Art(us in $ cut -f2 -d^ musicas do echo $Art(us | grep -i ^$*~ > /de /null && echo $Art(us | cut -f2 -d~ done
[1] Bash, página oficial: http://www.gnu.org/software/bash/bash.html [2] Manual de referência do Bash: http://www.gnu.org/software/bash/ manual/bashref.html
Sobreoautor
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. Pode ser contatado no e-mail julio.neves@gmail.com
maio2005 www.linuxmagazine.com.br
edição08
91
LinuxUser
PapodeBotequim
Curso de Shell Script
Dave Hamilton - www.sxc.hu
PapodeBotequim
ParteIX
Hoje vamos aprender mais sobre formatação de cadeias de caracteres, conhecer as principais variáveis do Shell e nos aventurar no (ainda) desconhecido território da expansão de parâmetros. E dá-lhe chope! porJúlioCezarNeves
T
ábom,jáseiquevocêvaiquererchopeantesdecomeçar, Bom,arespostaé"maisoumenos".Comestescomandos mastôtãoafimdetemostraroquefizquejávoupedira vocêescreve90%doqueprecisa,porémseprecisarescrever rodadaenquantoisso.AêChico,mandadois!Odeleésem algoformatadoeleslhedarãomuitotrabalho.Paraformatara colarinhopranãodeixarcheiroruimnessebigodão… saídaveremosagoraumainstruçãomuitomaisinteressante, Enquantoochopenãochega,deixaeutelembraroque aprintf.Suasintaxeéaseguinte: vocêmepediunaediçãopassada:erapararefazer Listagem 1: mandamsg.func e pergunta.func oprogramalistartistacomatelaformatadaeexecuçãoemloop,deformaqueelesóterminequando mandamsg.func receberum[ENTER]sozinhocomonomedoartista. 01 # A função recebe somente um parâmetro Eventuaismensagensdeerroeperguntasfeitasao 02 # com a mensagem que se deseja e ibir. 03 # Para não obrigar o programador a passar usuáriodeveriamsermostradasnaantepenúltima 04 # a msg entre aspas, usaremos $* todos linhadatela,utilizandoparaissoasrotinasexternas 05 # os parâmetro, lembra? e não $1. mandamsg.funcepergunta.funcquedesenvolvemos 06 (sg="$*" durantenossopaponaediçãopassada. 07 Tam(sg=${#(sg} Primeiramenteeudeiumaencolhidanasrotinas 08 Col=$ TotCols - Tam(sg / 2 # Centra msg na linha mandamsg.funcepergunta.func,queficaramcomo 09 tput cup $Linha(esg $Col nalistagem 1.Enalistagem 2vocêtemo“grandão”, 10 read -n1 -p "$(sg " nossaversãorefeitadolistaartista. pergunta.func –Puxa,vocêchegoucomacordatoda!Gosteida 01 # A função recebe 3 parâmetros na seguinte ordem: formacomovocêresolveuoproblemaeestruturou 02 # $1 - (ensagem a ser mostrada na tela oprograma.Foimaistrabalhoso,masaapresenta03 # $2 - Valor a ser aceito com resposta padrão çãoficoumuitolegalevocêexploroubastanteas 04 # $3 - O outro alor aceito opçõesdocomandotput.Vamostestaroresultado 05 # ‘upondo que $1=Aceita?, $2=s e $3=n, a linha comumálbumdoEmerson,Lake&Palmerque 06 # abai o colocaria em (sg o alor "Aceita? ‘/n " tenhocadastrado. 07 (sg="$1 `echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z` "
Envenenando a escrita Ufa!Agoravocêjásabetudosobreleituradedados, masquantoàescritaaindaestáapenasengatinhando. Jáseiquevocêvaimeperguntar:“Ora,nãoécomo comandoechoecomosredirecionamentosdesaída queseescrevedados?”.
84
junho2005
08 09 10 11 12 13 14
Tam(sg=${#(sg} Col=$ TotCols - Tam(sg / 2 # Centraliza msg na linha tput cup $Linha(esg $Col read -n1 -p "$(sg " ‘) [ ! $‘) ] && ‘)=$2 # ‘e azia coloca default em ‘) ‘)=$ echo $‘) | tr A-Z a-z # A saída de ‘) será em minúscula tput cup $Linha(esg $Col; tput el # Apaga msg da tela
edição09 www.linuxmagazine.com.br
PapodeBotequim
LinuxUser
Listagem 2: listaartista printf formato [argumento...]
Ondeformatoéumacadeia decaracteresquecontémtrês tipos de objeto: caracteres simples,caracteresparaespecificaçãodeformato(oude controle)eseqüênciadeescapenopadrãodalinguagem C.argumentoéacadeiade caracteresaserimpressasob ocontroledeformato. Cadaumdoscaracteresutilizadoséprecedidopelocaracter%e,logoaseguir,vem aespecificaçãodeformatode acordocomatabela 1. As seqüências de escape padrãodalinguagemCsão sempre precedidas pelo caracterecontra-barra(\).As reconhecidaspelocomando printfsãoasdatabela 2. Nãoacabaporaínão!Tem muitomaiscoisasobreessa instrução,mascomoesseé umassuntomuitocheiode detalhes e, portanto, chato paraexplicarepioraindapara lerouestudar,vamospassar diretoaosexemploscomcomentários.Vejasó: $ printf "%c" "1 caracter" 1$
Errado!Sólistou1caractere enãosaltoulinhaaofinal $ printf "%c\n" "1 caracter" 1
Saltoulinhamasaindanão listouacadeiainteira $ printf "%c caractere\n" 1 1 caractere
➟
$ cat listartista3.sh 01 #!/bin/bash 02 # Dado um artista, mostra as suas musicas 03 # ersao 3 04 Linha(esg=$ `tput lines` - 3 # Linha onde as msgs serão mostradas 05 TotCols=$ tput cols # Qtd de colunas na tela para enquadrar msgs 06 clear 07 echo " +–––––––––––––––––-----------------------------------+ | Lista Todas as (úsicas de um Determinado Artista | | ===== ===== == ======= == == =========== ======= | | | | Informe o Artista: | +––––––––––––––––----------------------------------–-+" 08 hile true 09 do 10 tput cup 5 51; tput ech 31 # ech=Erase chars 31 para não apagar barra ertical 11 read )ome 12 if [ ! "$)ome" ] # $)ome esta azio? 13 then 14 . pergunta.func "Deseja ‘air?" s n 15 [ $‘) = n ] && continue 16 break 17 fi 18 fgrep -iq "^$)ome~" musicas || # fgrep não interpreta ^ como e pressão regular 19 { 20 . mandamsg.func ")ão e iste música desse artista" 21 continue 22 } 23 tput cup 7 29; echo '| |' 24 LinAtual=8 25 IF‘=" 26 :" 27 for Art(us in $ cut -f2 -d^ musicas # E clui nome do album 28 do 29 if echo "$Art(us" | grep -iq "^$)ome~" 30 then 31 tput cup $LinAtual 29 32 echo -n '| " 33 echo $Art(us | cut -f2 -d~ 34 tput cup $LinAtual 82 35 echo '|' 36 let LinAtual++ 37 if [ $LinAtual -eq $Linha(esg ] 38 then 39 . mandamsg.func "Tecle Algo para Continuar..." 40 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7 41 tput cup 7 29; echo '| |' 42 LinAtual=8 43 fi 44 fi 45 done 46 tput cup $LinAtual 29; echo '| |' 47 tput cup $ ++LinAtual 29 48 read -n1 -p "+–––--Tecle Algo para )o a Consulta––––+" 49 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7 50 done
junho2005 www.linuxmagazine.com.br
edição09
85
LinuxUser
Tabela 1: Formatos de caractere Caractere c d e f g o s x %
A expressão será impressa como: Caractere simples Número no sistema decimal Notação científica exponencial Número com ponto decimal (float) O menor entre os formatos %e e %f com omissão dos zeros não significativos Número no sistema octal Cadeia de caracteres Número no sistema hexadecimal Imprime um %. Não há nenhum tipo de conversão
Opa,essaéaformacorreta!O%crecebeuovalor1,comoqueríamos:
PapodeBotequim
(%)significaotamanhoqueacadeiaterá apósaexecuçãodocomando.Vamosver aseguirmaisalgunsexemplos.Oscomandosabaixo:
O bcdevolveuduascasasdecimaise o printfcolocouozeroàdireita.Ocomandoaseguir: $ printf "%o\n" 10
$ printf "%d\n" 32
12
32 $ printf "%10d\n" 32 32
preenchemastringcomespaçosem brancoàesquerda(oitoespaçosmaisdois caracteres,10dígitos),nãocomzeros.Já nocomandoabaixo:
Converteuovalor 10parabaseoctal. Paramelhorarexperimente: $ printf "%03o\n" 27 033
Assimaconversãoficacommaisjeito deoctal,né?.Oqueesteaquifaz?
$ printf "%04d\n" 32 0032
$ printf "%s\n" Peteleca Peteleca
$ a=2
O04após%significa“formateastring emquatrodígitos,comzerosàesquerda senecessário”.Nocomando:
$ printf "%c caracteres\n" $a 2 caracteres
O%crecebeuovalordavariável$a. $ printf "%e\n" $ echo "scale=2 ; 100/6" | bc 1.666000e+01
$ printf "%10c caracteres\n" $a 2 caracteres
Opadrãodo %eéseiscasasdecimais. Jánocomando:
$ printf "%10c\n" $a caracteres 2
$ printf "%15s\n" Peteleca Peteleca
ImprimePetelecacom15caracteres. Acadeiadecaracteresépreenchidacom espaçosembrancoàesquerda.Jánocomando: $ printf "%-15s)e es\n" Peteleca Peteleca
c
)e es
$ printf "%.2e\n" `echo "scale=2 ; 100/6" | bc`
Repareque,nosdoisúltimosexemplos, emvirtudedousodo %c,sófoilistado umcaracteredecadacadeiadecaracteres passadacomoparâmetro.Ovalor 10à frentedocnãosignifica10caracteres.Um númeroseguindoosinaldepercentagem
1.67e+01
O.2especificouduascasasdecimais. Observeagora:
Omenos(-)colocouespaçosembranco àdireitadePetelecaatécompletaros15 caracterespedidos.Eocomandoabaixo, oquefaz?
$ printf "%f\n" 32.3
$ printf "%.3s\n" Peteleca
32.300000
Pet
Tabela 2: Seqüências de escape Seqüência
Efeito
a b f
Soa o beep Volta uma posição (backspace) Salta para a próxima página lógica ( form feed) Salta para o início da linha seguinte (line feed) Volta para o início da linha corrente (carriage return) Avança para a próxima marca de tabulação
n r t
Opadrãodo %féseiscasasdecimais. Enocomando: $ printf "%.2f\n" 32.3 32.30
junho2005
$ printf "%10.3sa\n" Peteleca Peta Pet
O.2especificouduascasasdecimais. Agoraobserve: $ printf "%.3f\n" `echo "scale=2 ; 100/6" | bc` 33.330
86
O .3mandatruncaracadeiadecaracteresapósastrêsprimeirasletras.E ocomandoaseguir:
edição09 www.linuxmagazine.com.br
Imprimeacadeiacom10caracteres, truncadaapósostrêsprimeiros,concatenadacomocaractere a(apóso s).E essecomandoaseguir,oquefaz?
PapodeBotequim
$ printf EXE(PLO % \n 45232 EXE(PLO b0b0
Eletransformouonúmero45232para hexadecimal(b0b0),masoszerosnão combinamcomoresto.Experimente:
trabalho,principalmenteeminstalações comestruturadediretóriosemmúltiplos níveis.Vejaoexemploaseguir: $ echo $CDPATH .:..:~:/usr/local $ p d
$ printf EXE(PLO %X\n 45232
/home/jne es/L(
EXE(PLO B0B0
$ cd bin $ p d
Assimdisfarçoumelhor!(repareno X maiúsculo).Praterminar,quetalocomandoabaixo: $ printf %X %XL%X\n 49354 192 10 C0CA C0LA
Esteaínãoémarketingeébastante completo,vejasócomofunciona: Oprimeiro%Xconverteu49354emhexadecimal,resultandoemC0CA(leia-se “cê”,“zero”,“cê”e“a”).Emseguidaveio umespaçoembrancoseguidoporoutro %XL.O %Xconverteuo 192dandocomo resultadoC0quecomoLfezC0L.Efinalmenteoúltimoparâmetro%Xtransformou onúmero10naletraA. Conformevocêspodemnotar,ainstruçãoébastantecompletaecomplexa.Aindabemqueoechoresolvequasetudo... Acerteiemcheioquandoresolviexplicaro printfatravésdeexemplos,pois nãosaberiacomoenumerartantasregrinhassemtornaraleituraenfadonha.
Principais variáveis do Shell OBashpossuidiversasvariáveisque servem para dar informações sobre oambienteoualterá-lo.Sãomuitase nãopretendomostrartodaselas,mas umapequenapartepodelheajudarna elaboraçãodescripts.Vejaaseguiras principaisdelas: CDPATH » Contém os caminhos que serãopesquisadosparatentarlocalizar umdiretórioespecificado.Apesardessa variávelserpoucoconhecida,seuuso deveserincentivadoporpouparmuito
/usr/local/bin
Como /usr/localestavanaminha variável$CDPATHenãoexistiaodiretório binemnenhumdosseusantecessores(., ..e~),ocomandocdfoiexecutadotendo comodestino/usr/local/bin. HI‘T‘IZE»Limitaonúmerodeinstruçõesquecabemdentrodoarquivode históricodecomandos(normalmente .bash_histor ,masnaverdadeéoque estáindicadonavariável$HI‘TFILE).Seu valorpadrãoé500. HO‘T)A(E»Onomedohostcorrente (quetambémpodeserobtidocomocomandouname -n). LA)G»Usadaparadeterminaroidioma faladonopaís(maisespecificamentecategoriadolocale).Vejaumexemplo: $ date Thu Apr 14 11:54:13 B”T 2005 $ LA)G=pt_B” date “ui Abr 14 11:55:14 B”T 2005 LI)E)O»Onúmerodalinhadoscript ou função que está sendo executada. Seuusoprincipalémostrarmensagens deerrojuntamentecomasvariáveis $0 (nomedoprograma)e$FU)C)A(E(nome dafunçãoemexecução). LOG)A(E»Estavariávelarmazenao nomedelogindousuário. (AILCHECK»Especifica,emsegundos,a freqüênciacomqueoShellverificaapresençadecorrespondêncianosarquivos indicadospelavariáveis $(AILPATHou $(AIL.Otempopadrãoéde60segundos
LinuxUser
(1minuto).AcadaintervalooShellfará averificaçãoantesdeexibiropróximo promptprimário($P‘1).Seessavariável estiversemvaloroucomumvalormenor ouigualazero,abuscapornovasmensagensnãoseráefetuada. PATH»Caminhosqueserãopesquisadosparatentarlocalizarumarquivoespecificado.Comocadascriptéumarquivo, casouseodiretóriocorrente(.)nasua variável$PATH,vocênãonecessitaráusar ocomando./scrpparaqueoscriptscrp sejaexecutado.Bastadigitar scrp.Este éomodoqueprefiro. PIPE‘TATU‘»Éumavariáveldotipo vetor(array)quecontémumalistade valoresdecódigosderetornodoúltimo pipelineexecutado,istoé,umarrayque abrigacadaumdos$?decadainstrução doúltimopipeline.Paraentendermelhor, vejaoexemploaseguir: $ ho jne es pts/0 Apr 11 16:26 10.2.4.144 jne es pts/1 Apr 12 12:04 10.2.4.144 $ ho | grep ^botelho $ echo ${PIPESTATUS[*]} 0 1
Nesteexemplomostramosqueousuário botelho não estava “logado”, em seguidaexecutamosumpipelineque procuravaporele.Usa-seanotação[*] emumarrayparalistartodososseus elementos;dessaforma,vimosqueaprimeirainstrução( ho)foibem-sucedida (códigoderetorno0)easeguinte(grep) não(códigoderetorno1). PRO(PT_CO((A)D»Seestavariávelreceberonomedeumcomando,todavez quevocêteclarum[ENTER]sozinhono promptprincipal($P‘1),essecomando seráexecutado.Émuitoútilquandovocê precisarepetindoconstantementeuma determinadainstrução. P‘1»Éopromptprincipal.No“Papo deBotequim”usamosospadrões$para usuáriocomume#pararoot,masémui-
junho2005 www.linuxmagazine.com.br
edição09
87
LinuxUser
tofreqüentequeeleestejapersonalizado. Umacuriosidadeéqueexisteatéconcurso dequemprogramao$P‘1maiscriativo. P‘2»Tambémchamado“promptde continuação”,éaquelesinaldemaior (>)queapareceapósum[ENTER]sem ocomandotersidoencerrado. PWD » Possui o caminho completo ($PATH) do diretório corrente. Tem o mesmoefeitodocomandop d. RA)DO(»Cadavezqueestavariávelé acessada,devolveuminteiroaleatórioentre0e32767.Parageraruminteiroentre 0e100,porexemplo,digitamos: $ echo $ ”A)DO(%101 73
Ouseja,pegamosorestodadivisãodo númerorandômicogeradopor101porque orestodadivisãodequalquernúmero por101variaentre0e100. REPLY»Useestavariávelpararecuperar oúltimocampolido,casoelenãotenha nenhumavariávelassociada.Exemplo:
PapodeBotequim
Expansão de parâmetros Bem,muitodoquevimosatéagorasão comandosexternosaoShell.Elesquebram omaiorgalho,facilitamavisualização, manutençãoedepuraçãodocódigo,mas nãosãotãoeficientesquantoosintrínsecos(built-ins).Quandoonossoproblema forperformance,devemosdarpreferência aousodosintrínsecos.Apartirdeagora voutemostraralgumastécnicasparao seuprogramapisarnoacelerador. Natabela 3enosexemplosaseguir, veremosumasériedeconstruçõeschamadasexpansão(ousubstituição)de parâmetros(ParameterExpansion),que substitueminstruçõescomoocut,oe pr, otr,osedeoutrasdeformamaiságil. Vamosveralgunsexemplos:seemuma perguntao ‘éoferecidocomovalordefault(padrão)easaídavaiparaavariável ‘),apóslerovalorpodemosfazer: S)=$ S):-S}
Parasaberotamanhodeumacadeia:
$ echo ${cadeia#*' '} de Botequim $ echo "Con ersa "${cadeia#*' '} Con ersa de Botequim
Noexemploanteriorfoisuprimidoà esquerdatudooque“casa”comamenor ocorrênciadaexpressão * ,ouseja, todososcaracteresatéoprimeiroespaço embranco.Essesexemplostambémpoderiamserescritossemprotegeroespaço dainterpretaçãodoShell(masprefiro protegê-loparafacilitaralegibilidade docódigo).Vejasó: $ echo ${cadeia#* } de Botequim $ echo "Con ersa "${cadeia#* } Con ersa de Botequim
Reparequenaconstruçãode e pré permitidoousodemetacaracteres. Utilizandoomesmovalordavariável cadeia,observecomofaríamosparater somenteBotequim :
$ read -p "Digite S ou ): "
$ cadeia=0123
Digite S ou ): )
$ echo ${#cadeia}
$ echo ${cadeia##*' '}
$ echo $”EPLY
4
Botequim $ echo "Vamos 'Chopear' no "${cadeia##*' '}
) ‘ECO)D‘»Estavariávelinforma,emse-
gundos,háquantotempooShellcorrente está“depé”.Use-aparademonstrara estabilidadedoLinuxeesnobarusuários daquelacoisacomjanelinhascoloridas quechamamdesistemaoperacional,mas quenecessitade“reboots”freqüentes. T(OUT»Seestavariávelcontiverum valor maior do que zero, esse valor seráconsideradootimeoutpadrãodo comandoread.Noprompt,essevalor éinterpretadocomootempodeespera porumaaçãoantesdeexpirarasessão. Supondoqueavariávelcontenhaovalor 30,oShellencerraráasessãodousuário (ouseja,farálogout)após30segundos semnenhumaaçãonoprompt.
88
$ cadeia="Papo de Botequim"
junho2005
Paraextrairdadosdecadeia,daposiçãoumatéofinalfazemos:
Vamos 'Chopear' no Botequim
Destavezsuprimimosàesquerdade $ cadeia=abcdef
cadeiaamaiorocorrênciadaexpressão
$ echo ${cadeia:1}
e pr.Assimcomonocasoanterior,ouso
bcdef
demetacaracteresépermitido. Outroexemplomaisútil:paraquenão apareçaocaminho(path)completodoseu programa($0)emumamensagemdeerro, inicieoseutextodaseguinteforma:
Reparequeaorigemézeroenãoum. Vamosextrair3caracteresapartirda2ª posiçãodamesmavariávelcadeia: $ echo ${cadeia:2:3}
echo Uso: ${0##*/} te to da mensagem de erro
cde
Reparequenovamenteaorigemdacontagemézeroenãoum.Parasuprimir tudoàesquerdadaprimeiraocorrência deumacadeia,faça:
edição09 www.linuxmagazine.com.br
Nesteexemploseriasuprimidoàesquerdatudoatéaúltimabarra(/)do caminho,restandosomenteonomedo programa.Ocaractere %ésimétricoao #,vejaoexemplo:
PapodeBotequim
Tabela 3: Tipos de expansão de parâmetros Expansão de parâmetros Resultado esperado ${var:-padrao} ${#cadeia} ${cadeia:posicao} ${cadeia:posicao:tamanho}
Se ar é vazia, o resultado da expressão é padrão Tamanho de $cadeia Extrai uma subcadeia de $cadeia a partir de posição. Origem zero Extrai uma subcadeia de $cadeia a partir de posição com tamanho igual a tamanho. Origem zero ${cadeia#expr} Corta a menor ocorrência de $cadeia à esquerda da expressão e pr ${cadeia##expr} Corta a maior ocorrência de $cadeia à esquerda da expressão e pr ${cadeia%expr} Corta a menor ocorrência de $cadeia à direita da expressão e pr ${cadeia%%expr} Corta a maior ocorrência de $cadeia à direita da expressão e pr ${cadeia/subcad1/subcad2} Troca a primeira ocorrência de subcad1 por subcad2 ${cadeia//subcad1/subcad2} Troca todas as ocorrências de subcad1 por subcad2 ${cadeia/#subcad1/subcad2} Se subcad1 combina com o início de cadeia, então é trocado por subcad2 ${cadeia/%subcad1/subcad2} Se subcad1 combina com o fim de cadeia, então é trocado por subcad2
LinuxUser
Háváriasformasdetrocarumasubcadeianoinícioounofimdeumavariável. Paratrocarnoiníciofazemos: $ echo $Passaro quero quero $ echo Como diz o sulista - ${PassaroU /#quero/não} Como diz o sulista - não quero
Paratrocarnofinalfazemos: $ echo Como diz o nordestino - U ${Passaro/%quero/não} Como diz o nordestino - quero não
$ echo ${cadeia/*po/Con ersa}
Papo de Botequim
Con ersa de Botequim
$ echo ${cadeia%' '*}
$ echo ${cadeia/????/Con ersa}
Papo de
Con ersa de Botequim
$ echo ${cadeia%%' '*} Papo
Paratrocarprimeiraocorrênciadeuma subcadeiaemumacadeiaporoutra:
Trocandotodasasocorrênciasdeuma subcadeiaporoutra.Ocomando: $ echo ${cadeia//o/a} Papa de Batequim
$ echo $cadeia Papo de Botequim
Ordenaatrocadetodosasletrasopor
$ echo ${cadeia/de/no}
a.Outroexemplomaisútiléparacontar
Papo no Botequim
aquantidadedearquivosexistentesno diretóriocorrente.Observeoexemplo:
$ echo ${cadeia/de /} Papo Botequim
$ ls | c -l
Presteatençãoquandoforusarmetacaracteres!Elessãogulososesemprecombinarãocomamaiorpossibilidade;No exemploaseguireuqueriatrocarPapode BotequimporConversadeBotequim:
30
O cpõeummontedeespaçosem brancoantesdoresultado.Paratirá-los: # “tdArqs recebe a saída do comando
$ echo $cadeia
$ “tdArqs=$ ls | c -l
Papo de Botequim
$ echo ${“tdArqs/ * /}
$ echo ${cadeia/*o/Con ersa}
30
Con ersatequim
Aidéiaerapegartudoatéoprimeiro o,masacabousendotrocadotudoatéo último o.Istopoderiaserresolvidode diversasmaneiras.Eisalgumas:
Nesseexemplo,eusabiaqueasaídaera compostadebrancosenúmeros,porisso monteiessaexpressãoparatrocartodosos espaçospornada.Notequeanteseapóso asterisco(*)háespaçosembranco.
–A gorajáchega,opapohojefoichato porquetevemuitadecoreba,masoque maisimportaévocêterentendidoo quetefalei.Quandoprecisar,consulte estesguardanaposonderabisqueias dicasedepoisguarde-osparaconsultas futuras.Masvoltandoàvacafria:tá nahoradetomaroutroeverojogodo Mengão.Prapróximavezvoutedar molezaesóvoucobraroseguinte:peguearotina pergunta.func(daqual falamosnoiníciodonossobate-papo dehoje,vejaalistagem 1)eotimize-a paraqueavariável$‘)recebaovalor padrãoporexpansãodeparâmetros, comovimos. Enãoseesqueça:emcasodedúvidas oufaltadecompanhiaparaum(oumais) chopeésómandarume-mailparajulio. neves@gmail.com.Edigaparaosamigos quequemestiverafimdefazerumcurso porretadeprogramaçãoemShelldeve mandarume-mailparajulio.neves@tecnohall. com.brparainformar-se.Valeu! ■
Sobreoautor
$ echo $cadeia
Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. Pode ser contatado no e-mail julio.neves@gmail.com
junho2005 www.linuxmagazine.com.br
edição09
89
LinuxUser
PapodeBotequim
Curso de Shell Script
Dave Hamilton - www.sxc.hu
PapodeBotequim
ParteX
Em mais um capítulo de nossa saga através do mundo do Shell Script, vamos aprender a avaliar expressões, capturar sinais e receber parâmetros através da linha de comando. porJúlioCezarNeves
E
aêamigo,tedeiamaiormolezana últimaaulané?Umexerciciozinho muitosimples… –É,masnostestesqueeufiz,ede acordocomoquevocêensinousobre substituiçãodeparâmetros,acheique deveriafazeralgumasalteraçõesnasfunçõesquedesenvolvemosparatorná-lasde usogeral,comovocêdissequetodasas funçõesdeveriamser.Querver? –Claro,né,mané,setepediparafazer éporqueestouafimdeteveraprender, masperaí,dáumtempo.Chico!Manda dois,umsemcolarinho!Vai,mostraaí oquevocêfez. –Bem,alémdoquevocêpediu,eu repareiqueoprogramaquechamavaa funçãoteriadeterpreviamentedefinidas alinhaemqueseriamostradaamensagemeaquantidadedecolunas.Oque fizfoiincluirduaslinhas–nasquais empregueisubstituiçãodeparâmetros –paraque,casoumadessasvariáveisnão fosseinformada,elarecebesseumvalor atribuídopelaprópriafunção.Alinha demensagemétrêslinhasantesdofim datelaeototaldecolunaséobtidopelo comandotput cols.Dêumaolhadana listagem 1evejacomoficou: –Gostei,vocêjáseantecipouaoqueeu iapedir.Sópragenteencerraressepapo desubstituiçãodeparâmetros,repareque
86
julho2005
alegibilidadedocódigoestá“horrorível”, masodesempenho,istoé,avelocidade deexecução,estáótimo.Comofunções sãocoisasmuitopessoais,jáquecada umusaassuasequasenãohánecessidadedemanutenção,eusempreopto pelodesempenho. Hojevamossairdaquelachaturaque foionossoúltimopapoevoltaràlógica, saindodadecoreba.Masvoltoatelembrar:tudoqueeutemostreidaúltimavez aquinoBotecodoChicoéválidoequebra umgalhão.Guardeaquelesguardanapos querabiscamosporque,maiscedoou maistarde,eleslhevãosermuitoúteis.
O comando eval outedarumproblemaqueeuduvido V quevocêresolva: $ var1=3 $ var2=var1
Tedeiessasduasvariáveisequeroque vocêmedigacomoeuposso,mereferindo apenasàvariávela ar2,listarovalorde ar1(que,nonossocaso,é3). –Ah,issoémole,mole!Ésódigitar essecomandoaqui: echo $`echo $var2`
Listagem 1: função pergunta.func 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
# A função recebe 3 parâmetros na seguinte ordem: # $1 - (ensagem a ser mostrada na tela # $2 - Valor a ser aceito com resposta padrão # $3 - O outro alor aceito # Supondo que $1=Aceita?, $2=s e $3=n, a linha # abai o colocaria em (sg o alor "Aceita? S/n " TotCols=${TotCols:-$ tput cols } # Se não esta a definido, agora está Linha(esg=${Linha(esg:-$ $ tput lines -3 } # Idem (sg="$1 `echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z` " Tam(sg=${#(sg} Col=$ TotCols - Tam(sg / 2 # Para centralizar (sg na linha tput cup $Linha(esg $Col read -n1 -p "$(sg " S) S)=${S):-$2} # Se azia coloca o padrão em S) S)=$ echo $S) | tr A-Z a-z # A saída de S) será em minúsculas tput cup $Linha(esg $Col; tput el # Apaga (sg da tela
edição10 www.linuxmagazine.com.br
PapodeBotequim
Reparequeeucoloqueioecho $ ar2entrecrases(`),porque dessaformaeleteráprioridadedeexecuçãoeresultaráem ar1. Eecho $ ar1produzirá3… –Ah,é?Entãoexecuteparaverseestácorreto. $ echo $`echo $var2`
LinuxUser
$ var2=ls $ $var2 10porpag1.sh alo2.sh
incusu
logado
10porpag2.sh ArqDoDOS.t t1 listamusica
logaute.sh
10porpag3.sh confuso
listartista
mandamsg.func
alo1.sh
listartista3 monbg.sh
contpal.sh
$var1
Agoravamoscolocarem ar2oseguinte: ls $ ar1;eem –Ué!Quefoiqueaconteceu?Omeuraciocíniomeparecia bastantelógico… –Oseuraciocíniorealmentefoilógico,oproblemaéquevocê esqueceudeumadasprimeirascoisasdequetefaleiaquino Botecoequevourepetir.OShellusaaseguinteordempara resolverumalinhadecomando: PResolveosredirecionamentos; PSubstituiasvariáveispelosseusvalores; PResolveesubstituiosmetacaracteres; PPassaalinhajátodaesmiuçadaparaexecução. Dessaforma,quandoointerpretadorchegounafasederesoluçãodevariáveis,quecomoeudisseéanterioràexecução, aúnicavariávelexistenteera ar2eporissoatuasolução produziucomosaída$ ar1.Ocomandoechoidentificouisso comoumacadeiadecaracteresenãocomoumavariável. Problemasdessetiposãorelativamentefreqüenteseseriam insolúveiscasonãoexistisseainstruçãoeval,cujasintaxeé e al cmd,onde cmdéumalinhadecomandoqualquer,que vocêpoderiainclusiveexecutardiretonopromptdoterminal. Quandovocêpõeoevalnafrente,noentanto,oqueocorreé queoShelltratacmdcomoumparâmetrodoevale,emseguida, oevalexecutaalinharecebida,submetendo-aaoShell.Ou seja,naprática cmdéanalisadoduasvezes.Dessaforma,se executássemosocomandoquevocêpropôscolocandooeval nafrente,teríamosasaídaesperada.Veja: $ eval echo $`echo $var2` 3
Esseexemplotambémpoderiatersidofeitodeoutramaneira. Dásóumaolhada: $ eval echo \$$var2 3
Naprimeirapassadaacontrabarra(\)seriaretiradae$ ar2 seriaresolvidoproduzindo ar1.Nasegundapassadateriasobradoecho $ ar1,queproduziriaoresultadoesperado.Agora voucolocarumcomandodentrode ar2eexecutar:
ar1vamoscolocarl*,vejamosoresultado: $ var2='ls $var1' $ var1='l*' $ $var2 ls: $var1: No such file or director $ eval $var2 listamusica listartista listartista3 logado logaute.sh
Novamente,notempodesubstituiçãodasvariáveis, $ ar1 aindanãohaviaseapresentadoaoShellparaserresolvida. Assim,sónosrestaexecutarocomandoevalparadarasduas passadasnecessárias. Umavezu mcolegadaexcelentel istaded iscussãogroups.yahoo.com/ group/shell-scriptcolocouumadúvida:queriafazerummenu quenumerasseelistassetodososarquivoscomextensão.she, quandoooperadorescolhesseumaopção,oprogramacorrespondentefosseexecutado.Vejaminhapropostanalistagem 2:
Listagem 2: fazmenu.sh 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21
#!/bin/bash # # Lista que enumera os programas com e tensão .sh no # diretório corrente e e ecuta o escolhido pelo operador # clear; i=1 printf "%11s\t%s\n\n" Opção Programa 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 "Informe a opção desejada: " opt echo e al "$CASE"
julho2005 www.linuxmagazine.com.br
edição10
87
LinuxUser
Parececomplicadoporqueuseimuitos printfparaformataçãodatela,masna
verdadeébastantesimples:oprimeiro printffoicolocadoparaimprimir ocabeçalhoelogoemseguidacomeceiamontardinamicamenteavariável $CASE,naqualaofinalseráfeitoumeval paraexecuçãodoprogramaescolhido. Reparenoentantoquedentrodoloop doforexistemdoisprintf:oprimeiro serveparaformataratelaeosegundo paramontarocase(seantesdocomando readvocêcolocarumalinha echo "$CASE",veráqueocomandocasemontadodentrodavariávelestátodoindentado.Frescura,né?:).Nasaídadofor,foi adicionadaumalinhaàvariável $CASE para,nocasodeumaescolhainválida, serexecutadaumafunçãoexternapara exibirmensagensdeerro.Vamosexecutaroscriptparaverasaídagerada: $ fazmenu.sh Opcao
PapodeBotequim
dospor)processosemexecução.Vamos, "limparaárea"aoseutérmino.Seseu deagoraemdiante,darumaolhadinha encerramentoocorrerdeformaprevista, nossinaisenviadosaosprocessosemais ouseja,setiverumtérminonormal,é àfrentevamosdarumapassadarápida muitofácilfazeressalimpeza;porém, pelos sinais gerados pelos processos. seoseuprogramativerumfimbrusco, Paramandarumsinalaumprocesso, muitacoisaruimpodeocorrer: usamosnormalmenteocomandokill, PÉpossívelqueemumdeterminadoescujasintaxeé: paçodetempo,oseucomputadoresteja cheiodearquivosdetrabalhoinúteis $ kill -sig PID PSeuprocessadorpoderáficaratolado deprocessoszombiesedefunctsgeraOnde PIDéoidentificadordoprocesdosporprocessosfilhosqueperderam so(ProcessIdentificationouProcessID). ospaiseestão“órfãos”; Alémdocomandokill,algumasseqüên- PÉnecessárioliberarsocketsabertospara ciasdeteclastambémpodemgerarsinais. nãodeixarosclientescongelados; Atabela 1mostraossinaismaisimpor- PSeusbancosdedadospoderãoficar tantesparamonitorarmos: corrompidosporquesistemasgerenciaAlémdesses,existeofamigeradosidoresdebancosdedadosnecessitam nal-9ouSIGKILLque,paraoprocesso deumtempoparagravarseusbuffers queoestárecebendo,equivaleameter emdisco(commit). odedonobotãodedesligardocompuEnfim,existemmilrazõesparanãousar tador–oqueéaltamenteindesejável, umkillcomosinal-9eparamonitoraro jáquemuitosprogramasnecessitam encerramentoanormaldeprogramas.
Programa
Listagem 3: Nova versão do fazmenu.sh 001
10porpag1.sh
002
10porpag2.sh
003
10porpag3.sh
03 # Lista enumerando os programas com e tensão .sh no
004
alo1.sh
04 # diretório corrente; e ecuta o escolhido pelo operador
005
alo2.sh
05 #
006
contpal.sh
06 clear; i=1
007
fazmenu.sh
07 printf "%11s\t%s\n\n" Opção Programa
008
logaute.sh
08 CASE='case $opt in'
009
monbg.sh
09 for arq in *.sh
010
readpipe.sh
10 do
011
redirread.sh
11
printf "\t%03d\t%s\n" $i $arq
12
CASE="$CASE
13
"$ printf "%03d \t %s;;" $i $arq
14
i=$ i+1
01 #!/bin/bash 02 #
Informe a opção desejada:
Seriainteressanteincluirumaopção paraterminaroprogramae,paraisso, serianecessáriaainclusãodeumalinha apósoloopdemontagemdatelaeaalteraçãodalinhanaqualfazemosaatribuiçãofinaldovalordavariável$CASE.Veja nalistagem 3comoeleficaria: ExistenoLinuxumacoisachamada sinal(signal).Existemdiversossinais quepodemsermandadospara(ougera-
88
julho2005
15 done 16 printf "\t%d\t%s\n\n" 999 "Fim do programa" # Linha incluída 17 CASE="$CASE 18
999
19
*
e it;; ./erro;;
20 esac" 21 read -n3 -p "Informe a opção desejada: " opt 22 echo 23 e al "$CASE"
edição10 www.linuxmagazine.com.br
# Linha alterada
PapodeBotequim
O comando trap Parafazeramonitoraçãodesinaisexiste ocomando trap,cujasintaxepodeser umadasmostradasaseguir: trap "cmd1; cmd2; cmdn" S1 S2 … SN trap 'cmd1; cmd2; cmdn' S1 S2 … SN
Casoatransferênciasejainterrompida porumkillouum[CTRL]+[C],certamentedeixarálixonodisco.Éexatamenteessaaformamaiscomumdeusodo comando trap.Comoissoétrechode umscriptdevemos,logonoiníciodele, digitarocomando:
LinuxUser
Tabela 1: Principais sinais Código
Nome
0 1
EXIT SIGHUP
2 3
Ondeoscomandoscmd1, cmd2, cmdn serãoexecutadoscasooprogramareceba ossinaisS1, S2…S).Asaspas(")ou asapóstrofes(')sósãonecessáriascaso otrappossuamaisdeumcomandocmd associado.Cadaumadelaspodesertambémumafunçãointerna,umaexterna ououtroscript. Paraentenderousodeaspas(")e apóstrofes (') vamos recorrer a um exemplo que trata um fragmento de umscriptquefazumatransferênciade arquivosviaFTPparaumamáquina remota($RemoComp),naqualousuário é $Fulano,suasenhaé $Segredoeo arquivoaserenviadoé $Arq.Suponha aindaqueessasquatrovariáveisforam recebidasporumarotinaanteriorde leituraequeessescriptsejamuitousado pordiversaspessoas.Vejamosotrecho decódigoaseguir: ftp -ivn $RemoComp << FimFTP >> /tmp/$$ U 2>> /tmp/$$ user $Fulano $Segredo binar get $Arq FimFTP
ReparequetantoassaídasdosdiálogosdoFTPcomooserrosencontrados estãosendoredirecionadospara/tmp/$$, oqueéumaconstruçãobastantecomum paraarquivostemporáriosusadosem scriptscommaisdeumusuário,porque $$éavariávelquecontémonúmerodo processo(PID),queéúnico.Comesse tipodeconstruçãoevita-sequedoisou maisusuáriosdisputemaposseeosdireitossobreumarquivo.
trap "rm -f /tmp/$$ ; e it" 0 1 2 3 15
Dessaforma,casohouvesseumainterrupçãobrusca(sinais1,2,3ou15)antes doprogramaencerrar(noe itdentrodo comandotrap),ouumfimnormal(sinal 0),oarquivo/tmp/$$seriaremovido. Casonãohouvesseainstrução e it nalinhadecomandodo trap,aofinal daexecuçãodessalinhaofluxodoprogramaretornariaaopontoemqueestava quandorecebeuosinalqueoriginoua execuçãodessetrap. NotetambémqueoShellpesquisaa linhadecomandoumavezquandootrap éinterpretado(eéporissoqueéusual colocá-lonoiníciodoprograma)enovamentequandoumdossinaislistadosé recebido.Então,noúltimoexemplo,ovalorde$$serásubstituídonomomentoem queocomandotrapélidopelaprimeira vez,jáqueasaspas(")nãoprotegemo cifrão($)dainterpretaçãodoShell. Sevocêquisessefazerasubstituição somenteaoreceberosinal,ocomando deveriasercolocadoentreapóstrofes('). Assim,naprimeirainterpretaçãodotrap, oShellnãoveriaocifrão($),asapóstrofes(')seriamremovidase,finalmente,o Shellpoderiasubstituirovalordavariável.Nessecaso,alinhaficariaassim: trap 'rm -f /tmp/$$ ; e it' 0 1 2 3 15
Suponha dois casos: você tem dois scriptsquechamaremosdescript1,cuja primeiralinhaseráumtrap,escript2, colocadoemexecuçãoporscript1.Por seremdoisprocessosdiferentes,terão doisPIDsdistintos.
15
Fim normal do programa Quando o programa recebe um kill -HUP SIGINT Interrupção pelo teclado. ([CTRL]+[C]) SIGQUIT Interrupção pelo teclado ([CTRL]+[\]) SIGTERM Quando o programa recebe um kill ou kill -TER(
1º Caso:Ocomando ftpencontra-se emscript1.Nessecaso,oargumentodo comandotrapdeveriavirentreaspas(") porque,casoocorresseumainterrupção ([CTRL]+[C]ou[CTRL]+[\])noscript2, alinhasóseriainterpretadanessemomentoeoPIDdoscript2seriadiferente doencontradoem/tmp/$$(nãoesqueça que$$éavariávelquecontémoPIDdo processoativo); 2º Caso:Ocomando ftpencontra-se em script2.Nessecaso,oargumento docomando trapdeveriaestarentre apóstrofes('),poiscasoainterrupção sedesseduranteaexecuçãodescript1, oarquivonãoteriasidocriado;casoela ocorresseduranteaexecuçãodescript2, ovalorde$$seriaoPIDdesseprocesso, quecoincidiriacomode/tmp/$$. Ocomando trap,quandoexecutado semargumentos,listaossinaisqueestão sendomonitoradosnoambiente,bem comoalinhadecomandoqueseráexecutadaquandotaissinaisforemrecebidos. Sealinhadecomandosdotrapfornula (vazia),issosignificaqueossinaisespecificadosdevemserignoradosquando recebidos.Porexemplo,ocomandotrap "" 2especificaqueosinaldeinterrupção([CTRL]+[C])deveserignorado.No últimoexemplo,notequeoprimeiroargumentodeveserespecificadoparaqueo sinalsejaignoradoenãoéequivalentea escrevertrap 2,cujafinalidadeéretornar osinal2aoseuestadopadrão. ➟ julho2005
www.linuxmagazine.com.br
Gerado por:
edição10
89
LinuxUser
Sevocêignorarumsinal,todosossubshellsirãoignorá-lo.Portanto,sevocê especificarqualaçãodevesertomada quandoreceberumsinal,todosossubshellsirãotomaramesmaaçãoquando receberemessesinal.Ouseja,ossinais sãoautomaticamenteexportados.Parao sinalmostrado(sinal2),issosignificaque ossub-shellsserãoencerrados.Suponha quevocêexecuteocomandotrap "" 2e entãoexecuteumsub-shell,quetornaráa executaroutroscriptcomoumsub-shell. Seforgeradoumsinaldeinterrupção, estenãoteráefeitonemsobreoShell principalnemsobreossub-shellporele chamados,jáquetodoselesignorarão osinal. Emkornshell(ksh)nãoexisteaopção -sdocomandoreadparalerumasenha. Oquecostumamosfazeréusarusaro comando stt comaopção -echo,que inibeaescritanatelaatéqueseencontreum stt echopararestauraressa escrita.Então,seestivéssemosusandoo interpretadorksh,aleituradasenhateria queserfeitadaseguinteforma: echo -n "Senha: " stt -echo read Senha stt echo
Oproblemacomessetipodeconstruçãoéque,casoooperadornãosoubesseasenha,eleprovavelmenteteclaria [CTRL]+[C]ouum[CTRL]+[\]durante ainstruçãoreadparadescontinuaroprogramae,casoagissedessaforma,oseu terminalestariasemecho.Paraevitarque issoaconteça,omelhorafazeré: echo -n "Senha: " trap "stt echo e it" 2 3 stt -echo read Senha stt echo trap 2 3
90
julho2005
PapodeBotequim
Paraterminaresseassunto,abraum consolegráficoeescrevanopromptde comandooseguinte: $ trap "echo Mudou o tamanho da janela" 28
Emseguida,pegueomouseearraste-o deformaavariarotamanhodajanela corrente.Surpreso?ÉoShellorientado aeventos…Maisunzinho,porquenão consigoresistir.Escrevaisto: $ trap "echo já era" 17
Emseguidadigite: $ sleep 3 &
Vocêacaboudecriarumsub-shellque irá dormir durante três segundos em background.Aofimdessetempo,você receberáamensagem“jáera”,porqueo sinal17éemitidoacadavezemqueum sub-shellterminaasuaexecução.Para devolveressessinaisaoseucomportamentopadrão,digite:trap 17 28. Muitolegalessecomando,né?Sevocê descobriralgummaterialbacanasobre usodesinais,porfavormeinformepor email,porqueémuitoraraaliteratura sobreoassunto.
Comando getopts Ocomandogetoptsrecuperaasopçõese seusargumentosdeumalistadeparâmetrosdeacordocomasintaxePOSIX.2, istoé,letras(ounúmeros)apósumsinal demenos(-)seguidasounãodeum argumento;nocasodesomenteletras (ounúmeros),elaspodemseragrupadas.Vocêdeveusaressecomandopara "fatiar"opçõeseargumentospassados paraoseuscript. Asintaxeégetopts cadeiadeopcoes nome.Acadeiadeopcoesdeveexplicitar umacadeiadecaracterescomtodasas opçõesreconhecidaspeloscript;assim, seelereconheceasopções -a -be –c,
edição10 www.linuxmagazine.com.br
cadeiadeopcoesdeveser abc.Sevocê
desejarqueumaopçãosejaseguidapor umargumento,ponhaumsinaldedois pontos(:)depoisdaletra,comoema:bc. Issodizaogetoptsqueaopção-atema forma-a argumento.Normalmenteum oumaisespaçosembrancoseparamo parâmetrodaopção;noentanto,getopts tambémmanipulaparâmetrosquevêm coladosàopçãocomoem -aargumento. cadeiadeopcoesnãopodeconterumsinaldeinterrogação(?). Onomeconstantedalinhadesintaxe acimadefineumavariávelquereceberá, acadavezqueocomandogetoptsfor executado,opróximodosparâmetros posicionaiseocolocaránavariávelnome. getoptscolocaumainterrogação(?)na variáveldefinidaemnomeseacharuma opçãonãodefinidaemcadeiadeopcoes ousenãoacharoargumentoesperado paraumadeterminadaopção. Comojásabemos,cadaopçãopassada porumalinhadecomandostemumíndicenumérico;assim,aprimeiraopção estarácontidaem$1,asegundaem$2e assimpordiante.Quandoogetoptsobtém umaopção,elearmazenaoíndicedo próximoparâmetroaserprocessadona variávelOPTI)D. Quandoumaopçãotemumargumento associado(indicadopelo:nacadeiadeopcoes),getoptsarmazenaoargumento navariável OPTARG.Seumaopçãonão possuirargumentoouseoargumento esperadonãoforencontrado,avariável OPTARGserá"apagada"(comunset).Ocomandoencerrasuaexecuçãoquando: PEncontraumparâmetroquenãocomeçacomumhífen(-). POparâmetroespecial--indicaofim dasopções. PQuandoencontraumerro(porexemplo, umaopçãonãoreconhecida). Oexemplodalistagem 4émeramente didático,servindoparamostrar,emum pequenofragmentodecódigo,ousoplenodocomando.
PapodeBotequim
Paraentendermelhor,vamosexecutaroscript:
LinuxUser
OPTARG eh 'impressora' Dispensando os primeiros $OPTIND-1 = 3 argumentos
$ getoptst.sh -h -Pimpressora arq1 arq2
O que sobrou da linha de comandos foi 'arq1 arq2'
getopts fez a variavel OPT_LETRA igual a 'h' OPTARG eh '' getopts fez a variavel OPT_LETRA igual a 'P' OPTARG eh 'impressora'
Repare,noexemploaseguir,quesepassarmosumaopçãoinválidaa variável$OPT_LETRAreceberáumpontodeinterrogação(?)ea$OPTARG será"apagada"(unset).
Dispensando os primeiros $OPTIND-1 = 2 argumentos O que sobrou da linha de comandos foi 'arq1 arq2'
$ getoptst.sh -f -Pimpressora arq1 arq2 # A opção –f não é valida ./getoptst.sh: illegal option -- f
Dessaforma,semtermuitotrabalho,separei todasasopçõescomseusrespectivosargumentos, deixandosomenteosparâmetrosqueforampassadospelooperadorparaposteriortratamento.Repare que,setivéssemosescritoalinhadecomandocom oargumento(impressora)separadodaopção(-P), oresultadoseriaexatamenteomesmo,excetopelo OPTI)D,jáquenessecasoeleidentificaumconjuntodetrêsopções(ouargumentos)e,noanterior, somentedois.Vejasó:
getopts fez a variavel OPT_LETRA igual a '?' OPTARG eh '' getopts fez a variavel OPT_LETRA igual a 'P' OPTARG eh 'impressora' Dispensando os primeiros $OPTIND-1 = 2 argumentos O que sobrou da linha de comandos foi 'arq1 arq2'
–Medizumacoisa:vocênãopoderiaterusadoumcondicionalcom caseparaevitarogetopts? –Poderiasim,masparaquê?Oscomandosestãoaíparaseremusados… Oexemplofoididático,masimagineumprogramaqueaceitassemuitas opçõesecujosparâmetrospoderiamounãoestarcoladosàsopções,sen$ getoptst.sh -h -P impressora arq1 arq2 doqueasopçõestambémpoderiamounãoestarcoladas:iaserumcase getopts fez a variavel OPT_LETRA igual a 'h' infernal!Comgetopts,ésóseguirospassosacima. OPTARG eh '' getopts fez a variavel OPT_LETRA igual a 'P' –É…Vendodessaforma,achoquevocêtemrazão.Éporqueeujáestou meiocansadocomtantainformaçãonovanaminha Listagem 4: getoptst.sh cabeça.Vamostomarasaideiraouvocêaindaquer explicaralgumaparticularidadedoShell? 01 $ cat getoptst.sh –Nemumnemoutro,eutambémjácanseimas 02 #!/bin/sh hojenãovoutomarasaideiraporqueestouindo 03 04 # E ecute assim: daraulanaUniRIO,queéaprimeirauniversidade 05 # federalqueestápreparandoseusalunosdocurso 06 # getoptst.sh -h -Pimpressora arq1 arq2 degraduaçãoemInformáticaparaousodeSoft07 # wareLivre.Masantesvoutedeixarumproblema 08 # e note que as informações de todas as opções são e ibidas parateencucar:quandovocêvariaotamanhode 09 # umajaneladoterminal,nocentrodelanãoaparece 10 # A cadeia 'P:h' diz que a opção -P é uma opção comple a dinamicamente,emvídeoreverso,aquantidadede 11 # e requer um argumento e que h é uma opção simples que não requer linhasecolunas?Então!Euqueroquevocêrepro12 # argumentos. 13 duzaissousandoalinguagemShell.Chico,traz 14 hile getopts 'P:h' OPT_LETRA rapidinhoaminhaconta!Voucontaratéumese 15 do vocênãotrouxereumemando! 16 echo "getopts fez a aria el OPT_LETRA igual a '$OPT_LETRA'" Nãoseesqueça,qualquerdúvidaoufaltadecompa17 echo " OPTARG eh '$OPTARG'" nhiaparaumchopeésómandarumemailparajulio. 18 done neves@gmail.com.Vouaproveitartambémparamandar 19 used_up=`e pr $OPTI)D – 1` omeujabá:digaparaosamigosquequemestivera 20 echo "Dispensando os primeiros \$OPTI)D-1 = $used_up argumentos" fimdefazerumcursoporretadeprogramaçãoem 21 shift $used_up 22 echo "O que sobrou da linha de comandos foi '$*'" Shellquemandeume-mailparajulio.neves@tecnohall. com.brparainformar-se.Valeu! ■
julho2005 www.linuxmagazine.com.br
edição10
91
LinuxUser
PapodeBotequim
Curso de Shell Script
Dave Hamilton - www.sxc.hu
Papodebotequim Partefinal A conversa está boa, mas uma hora eles tem que sair do bar. Na última parte do nosso papo, falamos sobre pipes e sincronização entre processos. porJúlioCezarNeves
E
aírapaz,tudobom? –Beleza.Vocêselembradequedaúltimavezvocême pediuparafazerumprogramaqueimprimissedinamicamente,nocentrodatela,aquantidadedelinhasecolunas deumterminalsemprequeotamanhodajanelavariasse? Poisé,euatéquefiz,masmesmodepoisdequebrarmuitoa cabeçaaaparêncianãoficouigual.
Listagem 1: tamtela.sh 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
86
#!/bin/bash # # Coloca no centro da tela, em ideo re erso, # a quantidade de colunas e linhas # quando o tamanho da tela eh alterado. # trap (uda 28 # 28 = sinal gerado pela mudanca no tamanho # da tela e (uda eh a funcao que fara isso. Bold=$ tput bold Re =$ tput re )orm=$ tput sgr0
–Nãoestounemaíparaaaparência,oqueeuqueriaéque vocêexercitasseoqueaprendemos.Medáalistagem 1 praeu veroquevocêfez. – Perfeito!Quesedaneaaparência,depoisvouteensinar unsmacetesparamelhorá-la.Oquevaleéqueoprogramaestá funcionandoeestábemenxuto. –Pôxa,eeuqueperdiomaiortempotentandodescobrir comoaumentarafonte… –Deixeissoparalá!Hojevamosverumascoisas bastanteinteressanteseúteis.
# (odo de enfase # (odo de ideo re erso # Restaura a tela ao padrao default
(uda { clear Cols=$ tput cols Lins=$ tput lines tput cup $ $Lins / 2 $ Cols - 7 / 2 echo $Bold$Re $Cols X $Lins$)orm }
moutrotipodepipeéonamedpipe,quetambém U échamadodeFIFO.FIFOéumacrônimodeFirstIn FirstOutqueserefereàpropriedadeemqueaordem dosbytesentrandonopipeéamesmaqueadasaída. Onameemnamedpipeé,naverdade,onomedeum arquivo.Osarquivostiponamedpipessãoexibidos pelocomandolscomoqualqueroutro,compoucas diferenças,veja: $ ls -l pipe1 pr -r-r--
# Centro da tela
clear read -n1 -p "(ude o tamanho da tela ou tecle algo para terminar "
agosto2005
Dando nomes aos canos
edição11 www.linuxmagazine.com.br
1
julio
dipao
0 Jan 22 23:11 pipe1|
o pnacolunamaisàesquerdaindicaque fifo1 éumnamedpipe.Orestodosbitsdecontrolede permissões,quempodelerougravaropipe,funcionamcomoumarquivonormal.Nossistemasmais modernosumabarravertical(|),oupipe,nofimdo nomedoarquivoéoutradicae,nossistemasLINUX, ondeo lspodeexibircores,onomedoarquivoé escritoemvermelhoporpadrão.
PapodeBotequim
Nossistemasmaisantigos,osnamedpipessãocriadospelo utilitáriomknod,normalmentesituadonodiretório/etc.Nos sistemasmaismodernos,amesmatarefaéfeitapelomkfifo, querecebeumoumaisnomescomoargumentoecriapipes comessesnomes.Porexemplo,paracriarumnamedpipecom onomepipe1,digite: $ mkfifo pipe1
LinuxUser
Sincronização de processos Suponhaquevocêdispareparalelamentedoisprogramas(processos),chamadosprograma1eprograma2,cujosdiagramasde blocosdesuasrotinassãocomomostradonatabela 1.Osdois processossãodisparadosemparaleloe,nobloco1doprograma1, astrêsclassificaçõessãodisparadasdaseguintemaneira: for Arq in BigFile1 BigFile2 BigFile3 do
Comosempre,amelhorformademostrarcomoalgofunciona édandoexemplos.Suponhaquenóstenhamoscriadoonamed pipemostradoanteriormente.Vamosagoratrabalharcomduas sessõesoudoisconsolesvirtuais.Emumdelesdigite:
if sort $Arq then (anda= a else (anda=pare break
$ ls -l > pipe1 fi
eemoutrofaça:
done echo $(anda > pipe1
$ cat < pipe1
[ $(anda = pare ] && {
Voilà!Asaídadocomandoexecutadonoprimeiroconsole foiexibidanosegundo.Notequeaordememqueoscomandos ocorreramnãoimporta. Sevocêprestouatenção,reparouqueoprimeirocomando executadopareciater"pendurado".Istoaconteceporqueaoutra pontadopipeaindanãoestavaconectada,eentãoosistema operacionalsuspendeuoprimeiroprocessoatéqueosegundo "abrisse"opipe.Paraqueumprocessoqueusapipenãofique emmododeespera,énecessárioqueemumapontadopipe hajaumprocesso"falante"enaoutraum"ouvinte".Noexemplo anterior,olserao"falante"eocaterao"ouvinte". Umusomuitoútildosnamedpipesépermitirqueprogramas semnenhumarelaçãopossamsecomunicarentresi.Osnamed pipestambémsãousadosparasincronizarprocessos,jáque emumdeterminadopontovocêpodecolocarumprocessopara "ouvir"oupara"falar"emumdeterminadonamedpipeeeledaí sósairáseoutroprocesso"falar"ou"ouvir"aquelepipe. Vocêjádeveternotadoqueessaferramentaéótimapara sincronizarprocessosefazerbloqueioemarquivosdeformaa evitarperda/corrupçãodedadosdevidoaatualizaçõessimultâneas(afamosaconcorrência).Vamosveralgunsexemplos parailustrarestescasos.
echo Erro durante a classificação dos arqui os e it 1 } …
Assimsendo,ocomandoiftestacadaclassificaçãoqueestá sendoefetuada.Casoocorraqualquerproblema,asclassificações seguintesserãoabortadas,umamensagemcontendoastring pareéenviadapelopipe1eprograma1édescontinuadocom códigodesaídasinalizandoumencerramentoanormal. Enquantooprograma1executavaoseuprimeirobloco(as classificações),oprograma2executavaoseubloco1,processandoassuasrotinasdeaberturaemenuparalelamenteao programa1,ganhandodessaformaumbomtempo.Ofragmento decódigodoprograma2aseguirmostraatransiçãodoseu bloco1paraobloco2: OK=`cat pipe1` if [ $OK = a ] then … Rotina de impressão
Tabela 1 Bloco 1 Bloco 2
Programa1
Programa2
Rotina de classificação de três grandes arquivos Acertos finais e encerramento
Rotina de abertura e geração de menus Impressão dos dados classificados pelo programa 1
agosto2005 www.linuxmagazine.com.br
edição11
87
LinuxUser
PapodeBotequim
… else e it 1 fi
Apósaexecuçãodeseuprimeirobloco,oprograma2passará a"ouvir"opipe1,ficandoparadoatéqueasclassificaçõesdo Programa1terminem,testandoaseguiramensagempassada pelopipe1paradecidirseosarquivosestãoíntegrosparaserem impressosouseoprogramadeveráserdescontinuado.Dessa formaépossíveldispararprogramasdeformaassíncronae sincronizá-losquandonecessário,ganhandobastantetempo deprocessamento.
Então,oquefazer?Pararesolveroproblemadeconcorrência,vamosutilizarumnamedpipe.Criamososcriptna listagem 2queseráodaemonquereceberátodosospedidos paraincrementarocontador.Notequeelevaiserusadopor qualquerpáginanonossositequeprecisedeumcontador. Comoapenasestescriptalteraosarquivos,nãoexisteoproblemadeconcorrência. Estescriptseráumdaemon,istoé,rodaráemsegundoplano. Quandoumapáginasofrerumacesso,elaescreveráasuaURL nopipe.Paratestar,executeestecomando: echo "teste_pagina.html" > /tmp/pipe_contador
Paraevitarerros,emcadapáginaaquequisermosadicionar ocontadoracrescentamosaseguintelinha:
Bloqueio de arquivos SuponhaquevocêtenhaescritoumCGI(CommonGatewayInterface)emShellScriptparacontarquantoshitsumadeterminada URLrecebeearotinadecontagemestádaseguintemaneira: Hits="$ cat page.hits 2> /de /null " || Hits=0 echo $ Hits=Hits++
> page.hits
Dessaforma,seapáginareceberdoisoumaisacessossimultâneos,umoumaispoderáserperdido,bastandoqueosegundo acessosejafeitoapósaleituradoarquivo page.hitseantes dasuagravação,istoé,apósoprimeiroacessoterexecutadoa primeiralinhadoscripteantesdeexecutarasegunda.
<!--#e ec cmd="echo $REQUEST_URI > /tmp/pipe_contador"-->
Notequeavariável$REQUEST_URIcontémonomedoarquivo queobrowserrequisitou.Esseexemploéfrutodeumatroca deidéiascomoamigoemestreemShellThobiasSalazarTrevisan,queescreveuoscriptecolocou-oemseuexcelentesite (www.thobias.org).Aconselhoatodososquequeremaprender Shelladarumapassadalá. Vocêpensaqueoassuntonamedpipesestáesgotado?Enganou-se.Voumostrarumusodiferenteapartirdeagora.
Listagem 2: contahits.sh 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 15 17 18 19 20
88
#!/bin/bash PIPE="/tmp/pipe_contador" # arqui o named pipe # dir onde serao colocados os arqui os contadores de cada pagina DIR="/ ar/ /contador" [ -p "$PIPE" ] || mkfifo "$PIPE"
VoumostrarqueoShelltambémusaosnamed pipesdeumamaneirabastantesingular,queéa substituiçãodeprocessos(processsubstitution).Uma substituiçãodeprocessosocorrequandovocêpõe um <ouum >grudadonafrentedoparênteseda esquerda.Digitarocomando: $ cat < ls -l
hile : do for URL in $ cat < $PIPE do FILE="$DIR/$ echo $URL | sed 's,.*/,,' " # quando rodar como daemon comente a pro ima linha echo "arqui o = $FILE" n="$ cat $FILE 2> /de /null " || n=0 echo $ n=n+1 > "$FILE" done done
agosto2005
Substituição de processos
edição11 www.linuxmagazine.com.br
Resultaránocomando ls -lexecutadoemum sub-shell,comonormal,porémredirecionaráasaída paraumnamedpipetemporário,queoShellcria, nomeiaedepoisremove.Entãoocatteráumnome dearquivoválidoparaler(queseráestenamedpipe ecujodispositivológicoassociadoé /de /fd/63). Oresultadoéamesmasaídaqueageradapelo ls -l,porémdandoumoumaispassosqueousual. Praquesimplificar? Comopoderemosnoscertificardisso?Fácil…Veja ocomandoaseguir:
PapodeBotequim
LinuxUser
$ ls -l > cat
#!/bin/bash
l- –– 1 jne es jne es 64 Aug 27 12:26 /de /fd/63 -> pipe:[7050]
LIST=""
# Criada no shell principal
ls | hile read FILE
# Inicio do subshell
É…Realmenteéumnamedpipe.Vocêdeveestarpensando queistoéumamaluquicedenerd,né?Entãosuponhaquevocê tenhadoisdiretórios,chamadosdiredir.bkp,edesejasaber seosdoissãoiguais.Bastacompararoconteúdodosdiretórios comocomandocmp:
do LIST="$FILE $LIST"
# Alterada dentro do subshell
done
# Fim do subshell
echo $LIST
Noiníciodesteexemploeudissequeeleerameramente didáticoporqueexistemformasmelhoresdefazeramesma tarefa.Vejasóestasduas:
$ cmp < cat dir/* < cat dir.bkp/* || echo backup furado
ou,melhorainda: $ ls | ln $ cmp < cat dir/* < cat dir.bkp/* >/de /null || echo backup furado
ouentão,usandoaprópriasubstituiçãodeprocessos: Esteéumexemplomeramentedidático,massãotantosos comandosqueproduzemmaisdeumalinhadesaídaqueele servecomoguiaparaoutros.Euquerogerarumalistagemdos meusarquivos,numerando-os,eaofinalmostrarototalde arquivosnodiretóriocorrente:
$ cat -n < ls
Umúltimoexemplo:vocêdesejacomparararq1earq2usando ocomandocomm,masessecomandonecessitaqueosarquivos estejamclassificados.Entãoamelhorformadeprocederé:
hile read arq $ comm < sort arq1 < sort arq2
do i++
# assim nao eh necessario inicializar i
Essaformaevitaquevocêfaçaasseguintesoperações:
echo "$i: $arq" done < < ls
$ sort arq1 > /tmp/sort1
echo ")o diretorio corrente `p d` e istem $i arqui os"
$ sort arq2 > /tmp/sort2
Tálegal,euseiqueexistemoutrasformasdeexecutara mesmatarefa.Usandoocomandowhile,aformamaiscomum deresolveresseproblemaseria:
$ comm /tmp/sort1 /tmp/sort2 $ rm -f /tmp/sort1 /tmp/sort2
i++
# assim nao eh necessario inicializar i
echo "$i: $arq" done echo ")o diretorio corrente `p d` e istem $i arqui os"
Aoexecutaroscript,tudopareceestarbem,porémnocomando echoapósodone,vocêveráqueovalorde$ifoiperdido. Issodeve-seaofatodestavariávelestarsendoincrementadaem umsub-shellcriadopelopipe(|)equeterminounocomando done,levandocomeletodasasvariáveiscriadasnoseuinterior easalteraçõesláfeitasporvariáveiscriadasexternamente. Somenteparatemostrarqueumavariávelcriadaforado sub-shellealteradaemseuinteriorperdeasalteraçõesfeitas quandoosub-shellseencerra,executeoscriptaseguir:
Sobreoautor
ls | hile read arq do
Pessoal,onossopapodebotequimchegouaofim.Curtimuito erecebidiversoselogiospelotrabalhodesenvolvidoaolongo dedozemesese,omelhordetudo,fizmuitasamizadesetomei muitoschopesdegraçacomosleitoresqueencontreipelos congressosepalestrasqueandofazendopelonossoquerido Brasil.Medespeçodetodosmandandoumgrandeabraçoaos barbudosebeijosàsmeninaseagradecendoosmaisde100 emailsquerecebi,todoselogiososedevidamenterespondidos. Àsaúdedetodosnós:Tim,Tim. ■ -Chico,fechaaminhacontaporquevoupracasa! Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. É autor do livro Programação Shell Linux, publicado pela editora Brasport. Pode ser contatado no e-mail julio.neves@gmail.com
agosto2005 www.linuxmagazine.com.br
edição11
89