Universidade Federal do Rio Grande do Norte por Allan Robson Silva Venceslau Helio Batista de Araujo Junior Rafael Mederiros Teles Natal, 2012 Sumário 1 Introdução 2 2 Breve introdução ao Linux 3 3 Arquitetura do Sistema e interação com Shell 4 4 Primeiros passos 4.1 Comandos úteis e estruturas built-in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Declarando variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Algumas variáveis especiais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 6 7 5 Condicionais 5.1 If . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 8 9 6 Iteradores 6.1 For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 While . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 10 11 7 Recursão 12 8 Algumas ferramentas de todo shelleiro 8.1 Aurelio’s tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2 ImageMagick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 13 1 1 Introdução Essa apostila tem o intuito de realizar uma breve introdução ao sistema Linux e a linguagem Shell Script. Não há intenção do leitor tornar-se um programador profissional nesta linguagem durante este curso, visto que a mesma é muito vasta para ser coberta em poucas horas. Pensamos no entanto que serão dados as bases necessárias para que o mesmo possa prosseguir sozinho e atingir nı́veis cada vez maiores de proficiência nessa linguagem extremamente poderosa que paira sobre os sistemas Unix-like. Alguns trechos foram retirados do sı́tio www.jneves.wordpress.com/, uma das melhores referências em ShellScript no paı́s. 2 Breve introdução ao Linux O Linux (Linus + Unix) originalmente foi desenvolvido por Linus Torvalds usando uma implementação de código-aberto feita por Andrew S. Tanenbaum chamada MINIX (http://www.minix3.org/) encontrada no livro Operating Systems: Design and Implementation. Desde a criação do sistema a aceitação vem aumentando e várias distribuições vem sendo criadas objetivando um maior contato com o usuário comum. Embora hoje se possa usar variações do Linux como o Ubuntu (www.ubuntu.com) sem mesmo saber que existe o bash, o conhecimento de tais ferramentas torna o convı́vio com o ambiente muito mais prático e mais simples também, facilitando tarefas como: administração do sistema com a criação de pequenos scripts, realização de tarefas rotineiras que normalmente necessitariam de um software auxiliar, além de outras tarefas que em outros contextos são dispendiosas e mecânicas para o usuário. 3 Arquitetura do Sistema e interação com Shell A Figura 1 mostra um diagrama de camadas que representa a interação desde o nı́vel de hardware até o nı́vel de “script”. O Shell representa portanto a abordagem mais “alto nı́vel” que um sistema Linux Figura 1: Camadas do SO até o Shell comum possui. Quando é executado um comando que vai criar um arquivo no Bash, temos de passar por todas essas camadas até chegar no nı́vel das unidades de disco (que corresponde a uma parte do hardware do sistema). Essa representa uma caracterı́stica muito boa e ao mesmo tempo muito ruim do Shell. Boa, pois permite: • uso da sintaxe cheia de recursos, • acesso a diversos programas que realizam tarefas simples (mas em conjunto bastante poderosas), • interação com arquivos de maneira praticamente transparente, • interação entre processos usando “túneis”, etc. Ao mesmo tempo, tentar realizar tarefas que são onerosas ao processador ou ao disco (como rotinas de computação numérica ou manipulação de arquivos muito grandes) pode ser uma tarefa frustrante, pois o Shell está repleto de system calls 1 , além de ter seu código sempre interpretado. Portanto que fique claro que nem toda tarefa pode ser feita com essa ferramenta, logo é preciso discernimento sobre o uso da mesma2 . 4 Primeiros passos Agora que sabemos a filosofia do Shell, vamos a um pouco de prática. Abrindo o terminal uma tela como a da Figura 2 se apresenta. Os comandos Shell podem ser digitados diretamente nessa interface ou então o usuário pode optar por usar um editor como Kate ou Gedit para elaborar os scripts. No caso do editor, o usuário deve lembrar de colocar a linha: #!/bin/bash no inı́cio de cada script e dar permissão de execução ao mesmo com o comando chmod +x arquivoscript. Comentários são iniciados por “#” na linha3 . 4.1 Comandos úteis e estruturas built-in Vamos usar alguns comandos comuns do Linux para execução dos scripts, vamos relembrar alguns. O comando ls é responsável por listar arquivos e diretórios. Assim caso você digite: $ls 1 Chamadas ao sistema operacional “great powers have great responsabilities” 3 A única exceção é a instrução #!/bin/bash, :P 2 Remember, Figura 2: Terminal Shell os arquivos do diretório corrente serão listados. Pode-se tambem usar o ls para mostrar o conteúdo de uma pasta, fazendo: $ls folder2 O comando cat é responsável por mostrar o conteúdo de um arquivo, assim para visualizar rapidamente um .txt basta fazer: $cat arquivo1 Podemos usar uma boa caracterı́stica do Shell que são os caracteres coringa, assim caso queiramos mostrar todos os arquivos podemos fazer: $cat * Caso queiramos mostrar todos os arquivos chamados arquivo1,arquivo2,arquivo3,arquivon, basta fazer: $cat arq* # Ou ent~ ao, cat arquivo? Para mostrar somente o arquivo 1 e 2, você pode fazer: $cat arquivo{1,2} A contrução {a,b}{c,d} realiza o produto cartesiano, entre essas duas contruções do Shell. Lembre sempre disto, pode facilitar sua vida, :p. Um comando obrigatório para quem usa Shell é o grep, esse comando realiza uma análise de expressões regulares e devolve como resposta as linhas que ele conseguiu realizar um match, iremos explorar o grep daqui a pouco. Aproveitemos para conhecer mais uma estrutura do Shell, o pipe: “|”. Nossa intenção é mostrar as primeiras 6 linhas de um arquivo com o poema do Vinı́cius de Moraes “Pela luz dos olhos teus”. Fazendo um cat poema|head -n 6 4 : Quando a luz dos olhos meus E a luz dos olhos teus Resolvem se encontrar Ai que bom que isso é meu Deus Que frio que me dá o encontro desse olhar Mas se a luz dos olhos teus 4O comando head possui várias funções, para aprofundamente faça no bash man head O que fizemos foi usar a estrutura “|” para realizar um “túnel” entre processos (um pipe 5 ). Scripts Shell usam “túneis” a todo momento, e tal caracterı́stica dá muito poder a capacidade de linkar a saı́da de um processo a entrada de outro. Podemos agora com o comando grep fazer uma busca pela palavra “luz” nas primeiras 6 linhas do arquivo poema por exemplo, basta fazer: $cat poema|head -n 6|grep luz Se quisermos procurar no arquivo todo, as linhas que não tem a palavra luz (numerando as linhas), podemos fazer: $nl poema|grep -v luz Há muito que ler sobre o grep6 , fica ai a dica de bons sites sobre ele e expressões regulares: http://guia-er. sourceforge.net e http://www.aurelio.net/er/. Por ultimo, suponha que queiramos pegar o resultado de um comando ou de uma sequência de pipes e colocar em um arquivo. Para isso basta que no final da declaração você coloque > file. Suponha que temos um arquivo cheio de tags XML e há uma série de e-mails no meio. Podemos usar o comando grep para pegar somente os que tem o servidor smtp domain.com, fazendo: $cat emails|grep ’@domain\.com’ <mail>[email protected]<\mail> <mail>[email protected]<\mail> Mas veja só, estamos com as tags em volta, tudo bem. Vamos tira-las com o comando cut, cuja sintaxe é cut -d <separador> -f<lista de campos>, logo teremos: $cat emails |grep -i ’@domain\.com’|cut -d ’>’ -f2-|cut -d ’<’ -f1 >emails_do_domain.com E nosso arquivo está pronto, cheque fazendo: $cat emails_do_domain.com [email protected] [email protected] 4.2 Declarando variáveis Para declarar uma variável no Shell, faça: $ variavel=valor É importante que tome cuidado com espaços, pois declarações como: $ variavel = valor ou $ variavel= valor não serão interpretadas como atribuições de variável e sim como nomes de comandos e passagem de parâmetros. Para imprimir o conteúdo de uma variável, faça: $ echo $variavel Podemos querer guardar o conteúdo de um comando numa variável, isso pode ser bem útil para algumas coisas que faremos adiante. Para fazer isso basta usar a seguinte declaração: variavel=$(comando) 5 Do 6 não inglês, cano esqueça de consultar man grep (afinal é a documentação oficial, :P) lembrando que comando pode ser uma série de comandos separados por pipe, ou comandos separados por “;”7 . Usando “;” como separador, a variável irá receber a saı́da dos dois comandos. No caso do “|”, a variável irá receber somente a saı́da do ultimo comando. Vamos então guardar numa variável o nome dos diretórios da pasta folder : diretorios=$(ls) Podemos agora mostrar fazendo: echo $diretorio #Note uma diferença fazendo, echo "$diretorio" Usar variáveis é importante para fazer interpolações, suponha que temos uma lista de nomes usuários e queremos fazer um script genérico para mostrar informações sobre as contas dos mesmos. Isso nos vai ser bem útil quando usarmos iterações. Por enquanto vamos nos satisfazer entendendo a seguinte sequência de comandos: numeroDeTerminais=$(who|wc -l) echo "Há $numeroDeTerminais terminais abertos" que vai mostrar quantos terminais estão abertos no sistema. Somente usando aspas duplas é possı́vel fazer interpolações, ao usar aspas simples o conteúdo da declaração não é interpretado pelo shell. 4.3 Algumas variáveis especiais O Shell possui algumas variáveis especiais, que permitem que o usuário realize alguns procedimentos como descobrir o PID do ultimo processo em background, descobrir o retorno do último processo, entre outras coisas, alguns exemplos estão listados abaixo: Variável $0 $1 ... $9 $10 ... $# $* $@ Variável $$ $! $ $? Parâmetros Posicionais Parâmetro número 0 (nome do comando ou função) Parâmetro número 1 (da linha de comando ou função) Parâmetro número N ... Parâmetro número 9 (da linha de comando ou função) Parâmetro número 10 (da linha de comando ou função) Parâmetro número NN ... Número total de parâmetros da linha de comando ou função Todos os parâmetros, como uma string única Todos os parâmetros, como várias strings protegidas Miscelânia Número PID do processo atual (do próprio script) Número PID do último job em segundo plano Último argumento do último comando executado Código de retorno do último comando executado Tabela 1: Variáveis especiais Agora podemos fazer coisas como: $ $ $ $ $ $ echo $$ #Pid do terminal em que o echo está sendo executado echo $? #Deve mostrar 0, pois o echo foi executado com ^ exito qualquer #Um nome de rotina qualquer que n~ ao existe no path echo $? #Vai imprimir um código de erro diferente de zero gedit & #Ou kate &, dependendo da distribuiç~ ao que voc^ e está usando echo $! #Vai imprimir o pid do gedit (ou do kate) Com isso podemos tomar várias decisões relativas a processos que estão rodando na nossa máquina e customizar diversas aplicações. 7O “;” é a forma de se fazer uma lista de comandos, ou seja por uma série de comandos numa mesma linha 5 Condicionais Para realizarmos decisões é necessário o uso de comandos condicionais. Para isso temos o comando if e o comando case. 5.1 If O if tem a seguinte estrutura geral: if listadecomandos1 then comando1 elif listadecomandos2 then condicao2 ... elif listadecomandosN then comandoN else comandoelse fi O if do jeito que está escrito não avalia diretamente condições, ele avalia se o ultimo comando foi bem sucedido ou não. Para aqueles que programam em C/C++, lembram que no final do main tem sempre um return 0;? Pois é, é exatamente pra ajudar tomadas de decisão como essas que esse retorno existe. Caso o programa retorne 0 para o interpretador Shell, o if correspondente terá o seu respectivo comando executado. Por exemplo, façamos uma aplicação simples que decide se um arquivo possui determinada string. #Arquivo procura-string #!/bin/bash #Parametro 1 - arquivo #Parametro 2 - string a ser procurada if nl $1|grep $2 --color=auto #Dá uma corzinha then echo O arquivo contem a string else echo String n~ ao encontrada fi Para testar condições mais familiares deve-se usar um outro comando para auxiliar o if, esse comando é o test. Algumas opções possı́veis são listadas abaixo na Tabela 2: Opção -e arq -s arq -f arq -d arq -r arq -w arq -x arq Significado arq existe arq existe e tem tamanho maior que zero arq existe e é um arquivo regular arq existe e é um diretório arq existe e com direito de leitura arq existe e com direito de escrita arq existe e com direito de execução Tabela 2: Parâmetros do comando test Algumas opções para números na Tabela 3: Agora podemos fazer coisas como: Opção n1 -eq n2 n1 -ne n2 n1 -gt n2 n1 -ge n2 n1 -lt n2 Verdadeiro se: n1 e n2 são iguais n1 e n2 não são iguais n1 é maior que n2 n1 é maior ou igual a n2 n1 é menor que n2 Significado equal not equal greater than greater or equal less than Tabela 3: Mais alguns parâmetros do comando test #Arquivo if-com-test #!/bin/bash read opc #L^ e da entrada padr~ ao if [ $opc -eq 1 ] then echo "Opç~ ao 1" elif [ $opc -eq 2 ] then echo "Opç~ ao 2" elif [ $opc -eq 3 ] then echo "Opç~ ao 3" elif [ $opc -eq 4 ] then echo "Opç~ ao 4" else echo Digite uma opç~ ao entre 1 e 4 fi O Shell é sensı́vel a formatação, coloque espaços como indicado acima. Uma tabela com outros exemplos de uso do if segue abaixo: Exemplos de uso do if if [ -f ”$arquivo”]; then echo ’Arquivo encontrado’; fi if [ ! -d ”$dir”]; then echo ’Diretório não encontrado’; fi if [ $i -gt 5 ]; then echo ’Maior que 5’; else echo ’Menor que 5’; fi if [ $i -ge 5 -a $i -le 10 ]; then echo ’Entre 5 e 10, incluindo’; fi if [ $i -eq 5 ]; then echo ’=5’; elif [ $i -gt 5 ]; then echo ’¿5’; else echo ’¡5’; fi if [ ”$USER”= ’root’ ]; then echo ’Oi root’; fi Tabela 4: Exemplos rápidos sobre if 5.2 Case O comando case fornece outra alternativa de comando condicional, ele tem a seguinte forma: case $var in padrao1) cmd1 cmd2 cmdn ;; padrao2) cmd1 cmd2 cmdn ;; padraon) cmd1 cmd2 cmdn ;; esac O conteúdo de var é comparado com os padrões, cada padrão pode ser um como o da lista abaixo: Caractere * ? [...] Caracteres Para Formação de Padrões Significado Qualquer caractere ocorrendo zero ou mais vezes Qualquer caractere ocorrendo uma vez Lista de caracteres Tabela 5: Tabela de padrões do case Para mostrar como fica melhor, vamos repetir o exemplo anterior, só que desta vez usaremos o case e não o if...elif ... else ... fi: #Arquivo case #!/bin/bash case $1 in 1) echo Opç~ ao 1;; 2) echo Opç~ ao 2 ;; 3) echo Opç~ ao 3;; 4) exit Opç~ ao 4;; *) echo ""Digite uma opç~ ao entre 1 e 4"" esac 6 Iteradores Vamos agora explorar alguns comandos que possibilitam fazer iterações. 6.1 For Se você está habituado a programar, certamente já conhece o comando for, mas o que você não sabe é que o for, que é uma instrução intrinseca do Shell (isto significa que o código fonte do comando faz parte do código fonte do Shell, ou seja em bom programês é um built-in), é muito mais poderoso que os seus correlatos das outras linguagens. Há várias sintaxes para o for, uma delas é: for var in val1 val2 ... valn do cmd1 cmd2 cmdn done Outra sintaxe possı́vel é a seguinte: for ((var=ini; cond; incr)) do cmd1 cmd2 cmdn done Essa sintaxe é muito prática para se fazer daemons 8 , por exemplo um monitor da carga do sistema: #Arquivo for-monitor #!/bin/bash #Monitor de processo simples for ((x=1;;x++)) 8 Processos de “longa vida” dentro do computador que gerenciam tarefas do y=$(ps aux|grep $USER|wc -l)#Conta quantos processos do usuario existem echo "$x $y">>processos sleep 2 done& sleep 1 gnuplot plot #Script com um pequeno codigo gnuplot #Arquivo plot - Sintaxe gnuplot reset #Limpa o gráfico set yrange [0:400] #Seta a escala no eixo y set title "Número de processos do sistema" #Coloca tı́tulo plot ’processos’ with lines #Plota com linhas contı́nuas reread #Volta ao inı́cio A ainda há uma outra forma (a minha predileta) que é a seguinte: for var in $(cmd1|cmd2|...|cmdn) do outrocomando1 ... outrocomandoz done Ou seja, podemos pegar o resultado de uma cadeia de piples e usar numa iteração. Mas como o for decide onde começa e onde termina cada “elemento de iteração”? Para isso existe a variável IFS (Inter-FieldSeparator), que é a entidade responsável por “sinalizar” onde começa e onde termina cada elemento que vai ser iterado. O valor default é o do espaço em branco (ou seja “ ”), mas pode ser mudado a qualquer instante para outro valor que você julgue mais correto. Para diversas aplicações, é melhor colocar o IFS com o valor da “quebra de linha” já que vários programas escrevem dados nesse formato. Rode o seguinte script na pasta folder e veja a diferença que o IFS faz: #!/bin/bash #Usando o IFS sem quebra de linha for var in $(ls) do du -sh $var done #Usando o IFS com quebra de linha BACK=$IFS IFS=’ ’ for var in $(ls) do du -sh $var done IFS=$BACK 6.2 While O while é outro iterador e possui uma sintaxe alternativa para se fazer laços, a sua forma é a seguinte: while comando do cmd1 cmd2 ... cmdn done Podemos com ele fazer facilmente um monitor de processos: #!/bin/bash # Executa e monitora um # processo em background $1 & # Coloca em backgroud while ps | grep -q $! do sleep 5 done echo Fim do Processo $1 7 Recursão Assim como em muitas linguagens, o Shell possibilita o uso de estruturas recursivas. Não há nada de misterioso nisto, mas para executar tais coisas, temos de aprender a usar funções. Uma função em shell é declarada da seguinte forma: nomedafuncao(){ comandos } Para fazer uma função recursiva, então terı́amos de fazer algo como: funcao(){ comandos ... funcao ... } Um exemplo é uma função que imprime a sequência Fibonacci: fibonacci(){ if [ $1 -eq 1 ]#$1 representa o primeiro argumento passado para a funç~ ao then echo 1 elif [ $1 -eq 2 ] then echo 2 else anterior=$(fibonacci $[$1-1])# O uso de $[] é para se realizar operaç~ oes aritméticas anterior2=$(fibonacci $[$1-2]) echo $[$anterior+$anterior2] fi } O uso de recursões torna o código muito limpo, mas ao mesmo tempo bem mais lento. Experimente pedir o termo de número 40, e veja que o cálculo demora um tempo bastante significativo. Deve-se realizar uma boa avaliação antes de se usar recursões, mas para pequenos cálculos e rotinas simples é uma solução bastante “enxuta”. 8 Algumas ferramentas de todo shelleiro 8.1 Aurelio’s tools Existem muitos bons aplicativos escritos em Shell, um bastante famoso na comunidade e cheio de funcionalidades é o funçõeszz(http://funcoeszz.net/) do Aurélio Marinho Jargas9 (um dos grandes programadores em Shell) do Brasil. Para instala-lo basta baixar o fonte e dentro do seu .bashrc, fazer: source funcoeszz.sh Agora você tem acesso a uma caixinha de ferramentas bastante útil, veja só: zzajuda #Mostra ajuda sobre os comandos zzgoogle shellscript #Pesquisa no google zzdicbabelfish shell #Traduz do ingl^ es para portugu^ es zztempo Brazil SBNT #Mostra condiç~ oes temporais em Natal-RN zzipinternet #Mostra o seu ip visı́vel na internet zzconverte cf 36 #Realiza várias convers~ oes, no caso de Celsius para Fahrenheit zzdolar #Mostra o valor do dolar comercial, paralelo, turismo Além de usar as funçõeszz, olhe o código-fonte, está bastante legı́vel e comentado. Existem alguns varios aplicativos escritos em Sed (Stream EDitor, um editor de texto que trabalha em batchmode), o Aurelio criou uma versão do jogo Sokoban que usa Sed, dê uma olhada no screenshot: Figura 3: Jogo Sokoban escrito em Sed O Aurélio é um dos grandes incentivadores e popularizadores do uso de ShellScript e conhecido por explorar bem os poderes desta linguagem, vale a pena conferir e acompanhar o trabalho deste “linuxer”. 8.2 ImageMagick A suı́te ImageMagick é um conjunto de aplicativos para manipular imagens via ShellScript. Existem várias implementações de bibliotecas para diversas linguagens e é uma biblioteca bastante usada devido a suas facilidades. Vamos ver alguns pequenos truques. $convert imagens/tux.jpg -monochrome imagens/tux-mono.jpg #coloca a foto em cores preto e branco $convert imagens/tux.jpg -flip imagens/tux-flip.jpg #Cria uma imagem espelho $convert imagens/tux.jpg -flop imagens/tux-flop.jpg #Dá um giro de 180 graus na imagem Pode-se criar pequenas fotos com dizeres via linha de comando rapidamente: 9 www.aurelio.net $convert -background white -fill black -font ./Loki_Cola.ttf \ -pointsize 120 label:’Shell Script’ shell.gif Uma pequena animação resultante da montagem de outras 3 imagens: $convert -delay -page -page -loop 100 -size 100x100 xc:SkyBlue \ +5+10 imagens/balloon.gif -page +35+30 imagens/medical.gif +62+50 imagens/present.gif -page +10+55 imagens/shading.gif 0 imagens/animation.gif \ \ :(){:|:};: “Great powers have great responsabilities” Referências [blu08] Linux, Command Line and Shell Scripting. Wiley Publishing, Inc, www.wiley.com, 2008. [Jar] Aurélio Marinho Jargas. Portal do Shell. www.aurelio.net/shell. [Nev] Júlio Cezar Neves. Shell e Chope: A dupla perfeita. www.julioneves.com.