curso de shell

27
Curso de Shell - Aula I Introdução O que são os shell scripts? Shell scripts são conjuntos de comandos armazenados em um arquivo texto que são executados seqüencialmente. Nesta primeira parte, faremos uma introdução sobre o shell, o formato desses arquivos e variáveis de ambiente. O que é Shell Shell, ou interpretador de comandos, é o programa disparado logo após o login responsável por "pegar" os comandos do usuário, interpretá-los e executar uma determinada ação. Por exemplo, quando você escreve no console "ls" e pressiona , o shell lê essa string e verifica se existe algum comando interno (embutido no próprio shell) com esse nome. Se houver, ele executa esse comando interno. Caso contrário, ele vai procurar no PATH por algum programa que tenha esse nome. Se encontrar, ele executa esse programa, caso contrário, ele retorna uma mensagem de erro. Para cada terminal ou console aberto, existe um shell sendo executado. Quando você entra no seu Linux, ele apresenta o login, para digitar o usuário e a senha. Ao digitar um par usuário/senha correto, o Linux abre automaticamente um shell, que vai ficar esperando seus comandos. Veja o exemplo abaixo: Welcome to Linux Slackware 7.1 kernel 2.2.16. matrix login: neo Password: Linux 2.2.16. Last login: Mon Sep 25 10:41:12 -0300 2000 on tty1. No mail. neo@matrix:~$ Essa ultima linha é o shell. Se você der o comando "ps", vai ver que um dos programas rodando é o seu shell: neo@matrix:~$ ps PID TTY TIME CMD 164 tty2 00:00:00 bash 213 tty2 00:00:00 ps neo@matrix:~$ Ou seja, o shell utilizado nesse console é o bash, que está rodando com PID 164. Existem diversos tipos de shell: bash, csh, ksh, ash, etc. O mais utilizado atualmente é o bash (GNU Bourne-Again SHell). Por isso, tomaremos ele como referência. Resumindo essa seção, é importante saber que para cada terminal ou console aberto, tem-se um shell rodando. Assim, se você tem 3 xterms abertos na interface gráfica, vai ter um shell para cada xterm. Ao executar o shell script, o shell atual (no qual você deu o comando) abre um novo shell para executar o script. Assim, os scripts são executados em um shell próprio (a menos que se especifique, ao chamar o script, para executá-lo no shell atual). Isso será importante quando formos tratar de variáveis de ambiente. Variáveis do ambiente Uma variável é onde o shell armazena determinados valores para utilização posterior.

Upload: tiago

Post on 09-Jan-2017

14 views

Category:

Education


0 download

TRANSCRIPT

Curso de Shell - Aula I

IntroduçãoO que são os shell scripts? Shell scripts são conjuntos de comandos armazenados em um arquivo texto que são executados seqüencialmente. Nesta primeira parte, faremos uma introdução sobre o shell, o formato desses arquivos e variáveis de ambiente.

O que é Shell

Shell, ou interpretador de comandos, é o programa disparado logo após o login responsável por "pegar" os comandos do usuário, interpretá-los e executar uma determinada ação.

Por exemplo, quando você escreve no console "ls" e pressiona , o shell lê essa string e verifica se existe algum comando interno (embutido no próprio shell) com esse nome. Se houver, ele executa esse comando interno. Caso contrário, ele vai procurar no PATH por algum programa que tenha esse nome. Se encontrar, ele executa esse programa, caso contrário, ele retorna uma mensagem de erro. Para cada terminal ou console aberto, existe um shell sendo executado. Quando você entra no seu Linux, ele apresenta o login, para digitar o usuário e a senha. Ao digitar um par usuário/senha correto, o Linux abre automaticamente um shell, que vai ficar esperando seus comandos. Veja o exemplo abaixo:

Welcome to Linux Slackware 7.1 kernel 2.2.16.

matrix login: neoPassword:

Linux 2.2.16.Last login: Mon Sep 25 10:41:12 -0300 2000 on tty1.No mail.

neo@matrix:~$

Essa ultima linha é o shell. Se você der o comando "ps", vai ver que um dos programas rodando é o seu shell:

neo@matrix:~$ psPID TTY TIME CMD 164 tty2 00:00:00 bash 213 tty2 00:00:00 ps neo@matrix:~$

Ou seja, o shell utilizado nesse console é o bash, que está rodando com PID 164.

Existem diversos tipos de shell: bash, csh, ksh, ash, etc. O mais utilizado atualmente é o bash (GNU Bourne-Again SHell). Por isso, tomaremos ele como referência.

Resumindo essa seção, é importante saber que para cada terminal ou console aberto, tem-se um shell rodando. Assim, se você tem 3 xterms abertos na interface gráfica, vai ter um shell para cada xterm.

Ao executar o shell script, o shell atual (no qual você deu o comando) abre um novo shell para executar o script. Assim, os scripts são executados em um shell próprio (a menos que se especifique, ao chamar o script, para executá-lo no shell atual). Isso será importante quando formos tratar de variáveis de ambiente.

Variáveis do ambiente

Uma variável é onde o shell armazena determinados valores para utilização posterior.

Toda variável possui um nome e um valor associado a ela, podendo ser este último vazio. Para listar as variáveis atualmente definidas no shell digite o comando set.

Para se definir uma variável, basta utilizar a síntaxe: nome_da_variável=valor. Por exemplo, queremos definir uma variável chamada "cor" com o valor de "azul":

neo@matrix:~$ cor=azul

Para utilizar o valor de uma variável, é só colocar um sinal de "$" seguido do nome da variável - o shell automaticamente substitui pelo valor da variável:

neo@matrix:~$ echo cor cor neo@matrix:~$ echo $cor azul

Em alguns casos, é aconselhável colocar o nome da variável entre chaves ({}). Por exemplo, se eu quero imprimir "azul-escuro", como faria? Simplesmente echo $cor-escuro ?? Não funcionaria, pois o bash vai procurar uma variável de nome "cor-escuro". Portanto, temos que colocar o nome "cor" entre chaves para delimitar o nome da variável:

neo@matrix:~$ echo ${cor}-escuro azul-escuro

Algumas variáveis já são predefinidas no shell, como o PATH, que, como foi dito antes, armazena o caminho dos programas. Por exemplo, a minha variável PATH contém:

neo@matrix:~$ echo $PATH/usr/local/bin:/usr/bin:/bin: /usr/X11R6/bin:/usr/openwin/bin:/usr/games:/opt/kde/bin:/usr/share/texmf/bin:/etc/script

Ou seja, quando digito um comando, como "ls", o shell vai começar a procurá-lo em /usr/local/bin, se não encontrá-lo, vai procurar em /usr/bin e assim por diante. Repare que os diretórios são separados por um sinal de dois pontos (:).

É importante destacar que o shell possui várias variáveis pré-definidas, ou seja, que possuem um significado especial para ele, entre elas: PATH, PWD, PS1, PS2, USER e UID.

Assim, quando iniciamos um novo shell (ao executar o nosso script), essas variáveis especiais são "herdadas" do shell pai (o que executou o shell filho). Outras variáveis definidas pelo usuário, como a variável "cor" não são passadas do shell pai para o filho.

Quando o script terminar, o seu shell (shell filho) simplesmente desaparece e com ele também as suas variáveis, liberando o espaço ocupado na memória.

Formato dos arquivos de Shell Script

A primeira linha de todo shell script deve começar com algo do tipo: #!/bin/bash, a qual indica com qual shell deverá ser executado o script. Nesse exemplo, estamos falando para o shell atual executar o script com o shell /bin/bash.Se quisermos que o nosso script seja executado com o shell csh, devemos colocar nessa primeira linha: #!/bin/csh. Como usaremos o bash como nosso shell de referência, todas as linhas dos nossos scripts começarão com #!/bin/bash.

Digamos que você executa freqüentemente o comando: find / -name file -print, que procura na raiz (/) por um arquivo de nome "file". Só que é chato ficar digitando esse comando toda vez que se quer procurar um arquivo.

Então vamos criar um shell script que contenha esse comando. Vamos chamar esse shell script de "procura". Seu conteúdo fica assim:

#!/bin/bash find / -name file -print

Pronto. Tornemos agora o arquivo executável: chmod 755 ./procura. Porém, ao tentar executar o nosso script, teremos um problema.

./procura

Este script irá procurar por um arquivo chamado "file". Como especificar qual arquivo queremos procurar? Seria ideal executarmos o nosso script seguido do nome do arquivo que queremos procurar.

Por exemplo, queremos saber onde está o arquivo "netscape": ./procura netscape. É ai que entram as "variáveis de parâmetro". Vamos substituir no nosso script a linha find / -name file -print por find / -name $1 -print. Quando o bash lê a variável "$1", ele a substitui pelo primeiro parâmetro passado na linha de comando para o nosso script. Então, se executamos ./procura netscape, a variável "$1" será substituída por "netscape", como a gente queria. Repare que a variável "$2" conteria o segundo parâmetro passado para o script e assim por diante. Sendo assim, qualquer comando colocado abaixo de find seria executado após ele. Esse é o essencial do shell script: poder automatizar a execução de programas e comandos como se estivessem sendo digitados diretamente no console ou terminal.

Conclusão

Na próxima aula do nosso curso de shell script iremos aprender alguns comandos especiais para tornar nossos script mais poderosos, fazendo coisas mais elaboradas do que apenas executar programas seqüencialmente. Entre eles, podemos destacar os comandos de laço, como "if", "for", "while", "case" etc.

[por Alex Borro]

Curso de Shell - Aula II

IntroduçãoNa aula de hoje vamos falar sobre execução de programas em primeiro plano (fg - foreground) e em segundo plano (bg - background), redirecionamento de saídas e também sobre os códigos de escape dos programas.

Execução em foreground e background

Quando executamos um programa, o shell fica esperando o mesmo terminar para depois nos devolver a linha de comando. Por exemplo, quando executamos o comando cp arquivo1 arquivo2,o shell executa esse comando, fica esperando ele terminar, para depois nos retornar a linha de comando. Isso chama-se execução em primeiro plano, ou foreground, em inglês.Agora digamos que o arquivo a ser copiado seja muito grande, digamos, 50Mb. Enquanto o comando cp é executado, vamos ficar com o shell preso a ele todo esse tempo, ou seja, cerca de 1 ou 2 minutos. Somente após isso vamos ter a linha de comando de volta para podermos continuar trabalhando.

E se pudéssemos dizer ao shell para executar um programa e nos retornar a linha de comando sem ficar esperando o tal programa terminar? Seria muito melhor, não? Em alguns casos seria.

E podemos fazer isso.

Uma maneira fácil é colocar o sinal de & depois de um comando. No exemplo acima ficaria:

neo@matrix:~$ cp arquivo1 arquivo2 &[1] 206 neo@matrix:~$

Pronto, assim que teclarmos [enter], teremos a nossa linha de comando de volta e mais duas informações: "[1] 206".

A primeira informação ([1]) chama-se job. Ela identifica os programas que estão sendo executados em bg (background). Por exemplo, se executarmos mais um programa em bg enquanto este "cp" está sendo executado, o novo programa recebe o job de número 2 ([2]) e assim por diante.

A segunda informação (206) é o pid do programa, ou seja, o número de processo que o kernel dá a esse programa.

Mas aí temos um pequeno problema. Digamos que você queira trazer o programa para fg, por exemplo, se o programa executado em bg for um tocador de mp3 e você quiser mudar a música. Como fazemos? Usamos o comando fg [job] (intuitivo, não ?) do bash. No exemplo acima, digamos que eu execute o "cp" em bg, mas depois queira trazê-lo para fg para cancelá-lo. Seria simples:

neo@matrix:~$ cp arquivo1 arquivo2 & [1] 206 neo@matrix:~$ fg 1 cp arquivo1 arquivo2

Assim trazemos o "cp" para primeiro plano e a linha de comando só retorna quando ele terminar.

Opcional: Uma outra maneira de colocar um programa para rodar em bg é utilizando um truque do bash (não sei se esta opção está disponível em outros shells).Digamos que você execute o comando "cp" e esqueça de colocar o sinal "&". E aí? Tem que ficar esperando acabar ou cancelar o comando? Não, podemos teclar: Ctrl + Z.

Ao fazer isso, paramos a execução do programa. Então é só dar o comando bg [job] (intuitivo também, né?):

neo@matrix:~$ cp arquivo1 arquivo2

* aqui teclamos Ctrl + Z * [1]+ Stopped cp -i arquivo1 arquivo2 neo@matrix:~$ bg 1 [1]+ cp -i arquivo1 arquivo2 & neo@matrix:~$

Quando teclamos o Ctrl + Z, o bash nos diz que o job 1 foi parado ([1]+ Stopped) e quando executamos "bg 1" ele nos diz que o comando voltou a ser executado, mas em bg (repare o sinal de "&" no final da linha): [1]+ cp -i arquivo1 arquivo2 &.

Redirecionamento de saídas

Muitos programas que executamos geram saídas no console, ou sejam, emitem mensagens para os usuários. Por exemplo, queremos achar quantos arquivos no nosso sistema tem "netscape" no nome:

neo@matrix:~$ find / -name netscape /usr/lib/netscape /usr/lib/netscape/netscape /usr/lib/netscape/nethelp/netscape /usr/local/bin/netscape

Agora se quisermos procurar quantos arquivos compactados com o gzip (extensão .gz) tem no nosso sistema para depois analisá-los e possivelmente apagar os repetidos ou inúteis, teremos uma lista muito grande. Aqui no meu sistema deu mais de 1000 arquivos. Então, seria útil podermos enviar as mensagens que vão para o console, para um arquivo. Assim, poderíamos analisá-lo depois de executar o comando. Isso é extremamente útil ao trabalharmos com shell script - enviar as saídas dos comandos para arquivos para depois analisá-las. Exemplificando:

neo@matrix:~$ find / -name "*.gz" > lista.txt find: /home/vera: Permissão negada find: /home/pri: Permissão negada find: /root: Permissão negada neo@matrix:~$

Notamos que nem tudo foi para o arquivo lista.txt. As mensagens de erro foram para o console.

Isso porque existem dois tipos de saída: a saída padrão e a saída de erro.A saída padrão é a saída normal dos programas, que no caso acima, seria os arquivos encontrados. E a saída de erro, como o próprio nome diz, são os erro encontrados pelo programa durante sua execução, que devem ser informados ao usuário, como os erros de "permissão negada".

E aí, como selecionar uma ou outra? É simples, apenas devemos indicar o "file descriptor" (fd) delas. Não vamos entrar em detalhes sobre o que é "descritor de arquivos" por fugir do nosso escopo e ser um pouco complicado, apenas temos que saber que o fd 1 corresponde a saída padrão e o fd 2 a saída de erro. Assim, no exemplo acima, para enviar os erro para o arquivo erros.txt e a saída padrão para o arquivo lista.txt, usaríamos:

neo@matrix:~$ find / -name "*.gz" 1> lista.txt 2> erros.txt

Ou seja, é só por o número da saída desejada antes do sinal de ">".

Agora digamos que queremos ver o conteúdo do arquivo lista.txt. Podemos abrí-lo com um editor de textos ou usar o "cat". Optando pela última opção:

neo@matrix:~$ cat lista.txt /home/neo/gkrellm-0.10.5.tar.gz /home/neo/linuxcall-interface-beta.tar.gz ... <<< mais de 1000 linhas neo@matrix:~$

Temos um problema. O arquivo tem mais de 1000 linhas e não conseguimos vê-lo inteiro. Podemos redirecionar a saída do comando "cat" para o comando "more", que dá pausa entre as telas:

neo@matrix:~$ cat lista.txt | more .........

Veja que utilizamos o caractere "|", chamado "pipe". Não poderíamos ter utilizado o sinal de ">"? Não, o sinal de ">" é somente para enviar a saída para um ARQUIVO. Para enviar a saída de um comando para a entrada de outro, usamos o pipe.

As vezes queremos que além da saída ir para um arquivo, ela também vá para a tela. Temos um programinha que faz isso. Ele chama-se "tee". Veja o exemplo a seguir:

neo@matrix:~$ find / -name "*.gz" 2> erros.txt | tee lista.txt

O que fizemos???Primeiro pegamos a saída de erro e enviamos para o arquivo erros.txt. O restante, ou seja, a saída padrão, estamos enviando para a entrada do comando tee (usando o pipe). O tee simplesmente pega o que ele recebe (através do pipe) e joga no arquivo lista.txt e na tela. Assim, além de gravarmos a saída num arquivo, podemos mostrar ao usuário o que está acontecendo. Isso é muito útil quando escrevemos alguns scripts.

Resumindo: aprendemos que podemos redirecionar a saída padrão ou de erro de programa para um arquivo usando respectivamente "1>" ou "2>" ou também enviar a saída para a entrada de outro programa usando o pipe "|".

Códigos de Escape

Toda vez que executamos um programa em Unix, ele retorna um código de escape ao finalizar. Esse código reflete a condição em que o programa finalizou. Se ocorreu tudo certo e o programa terminou normalmente, ele retorna 0. Se ocorreu algum problema, o programa retorna um código diferente de 0, geralmente variando com o problema ocorrido.

Esse código de retorno é extremamente importante em shell script, pois é assim que testamos se uma certa ação ocorreu bem ou teve problemas.Esse código é armazenado pelo bash numa variável chamada "?" (isso mesmo, somente o sinal de interrogação ;-)).

Por exemplo, vamos executar um "ls" em um diretório que existe e ver o código de retorno:

neo@matrix:~$ ls /boot System.map boot.0300 boot.b boot_message.txt chain.b config map os2_d.b neo@matrix:~$ echo $? 0 neo@matrix:~$

Ou seja, o "ls" foi executado normalmente, retornando 0. Agora vamos executá-lo num diretório que não existe:

neo@matrix:~$ ls /diretorio_invalido /bin/ls: /diretorio_invalido: Arquivo ou diretório não encontrado neo@matrix:~$ echo $? 1 neo@matrix:~$

Como esperado, obtemos o retorno de erro 1.

Alguns programas tem muitos códigos de retorno. Por exemplo, os códigos de retorno do "pppd" vão até o 19. Assim é possível saber porque ele foi finalizado. Se você colocar uma senha errada no pppd e tentar conectar, ele vai terminar com o código 19.

man pppd ... 17 The PPP negotiation failed because serial loopback was detected. 18 The init script failed (returned a non-zero exit status). 19 We failed to authenticate ourselves to the peer. ...

Um detalhe importante: quando executamos um programa em background, ele sempre retorna um código 0 para o shell, mesmo que durante sua execução ocorra algum problema. Assim, quando executamos um programa em bg, perdemos essa facilidade de testar como foi seu término.

neo@matrix:~$ ls /diretorio_invalido & [1] 230 neo@matrix:~$ /bin/ls: /diretorio_invalido: Arquivo ou diretório não encontrado

[1]+ Exit 1 /bin/ls $LS_OPTIONS /diretorio_invalido neo@matrix:~$ echo $? 0

Como vemos, ao terminar, ele emite uma mensagem dizendo que finalizou com código 1 ([1]+ Exit 1) mas quando testamos a variável "?", o bash nos diz "0".

Conclusão

Como todos os programas tem que terminar com um código de retorno que tenha algum significado, nossos shell scripts também terão que finalizar indicando o que aconteceu, se ocorreu tudo bem ou se ouve erro. Mas isso discutiremos melhor mais pra frente.

O importante aqui é saber que todos os programas terminam com um código de retorno, os quais usaremos nos nossos scripts para testar o término dos programas.

Curso de Shell - Aula III

IntroduçãoNesta terceira parte do nosso curso de shell script, vamos tratar de "condicionais". Condicionais são comandos que avaliam uma expressão. Se ela for verdadeira, uma determinada rotina é executada. Caso contrário, outra rotina pode ser executada.

O famoso "if"

O bash nos oferece, entre outros, o comando IF (ele é um comando embutido no bash e não um programa como o "ls", o "cp" etc.). A forma mais simples de representar uma condicional utilizando o IF, é da seguinte forma básica:

if [condição]; then comandos1; else comandos2; fi;

Se [condição] for verdadeira, os comandos1 são executados. Se for falsa, os comandos2 são executados.Mas para o IF, o que é uma condição verdadeira ou uma falsa?

Como foi explicado na aula anterior, TODO comando em Unix tem um código de retorno. Geralmente o código "0" (zero) significa que o comando foi executado perfeitamente e terminou bem. Códigos maiores que zero significam que alguma coisa de errado ocorreu.

É assim que o IF verifica uma condição. Se o seu código de retorno for zero, então ela é considerada verdadeira. Caso contrario, ela é falsa.Mas então a [condição] tem que ser um comando, certo? Exatamente. Vamos exemplificar:

neo@matrix:~$ if ls /boot; then echo "O diretório existe."; else echo "Diretório inválido.";

fi; System.map boot.0300 boot.b boot_message.txt chain.b config map os2_d.b O diretório existe.

O que fizemos? Logo após o if, nós colocamos um comando: "ls /boot". O que o IF faz? Ele executa esse comando (por isso que temos a segunda linha começada por "System.map", que é o conteúdo do meu diretório /boot) e avalia o seu código de saída. Como o "ls" foi executado corretamente, ele retorna zero, significando verdadeiro para o IF, que executa o comando logo após o "then", ou seja, o echo "O diretório existe.", mostrando essa mensagem no console.

Agora vamos colocar um diretório que não existe:

neo@matrix:~$ if ls /dir_invalido;then echo "O diretório existe.";else echo "Diretório inválido.";fi; /bin/ls: /dir_invalido: Arquivo ou diretório não encontrado Diretório inválido.

A lógica é a mesma. Executa o "ls /dir_invalido", que retorna um código maior que zero. O IF avalia como falso e executa o comando após o else: echo "Diretório inválido".

Nós poderíamos omitir a segunda linha dos dois exemplo (a que mostra o conteúdo de /boot no primeiro exemplo e a mensagem de erro emitida pelo ls dizendo que /dir_invalido não existe no segundo), apenas redirecionando as saídas para /dev/null, ou seja:

neo@matrix:~$ ls /boot 1> /dev/null 2> /dev/null

Nota: Tem um macete que possui o mesmo efeito. Em vez de colocar: "1> /dev/null 2> /dev/null" podemos colocar "2&>1", que é menor e mais simples.

O comando "test"

Bom, aprendemos que o IF avalia a código de retorno de um comando. Mas muitas vezes, para não dizer a maioria, nós queremos avaliar "expressões", ou seja, verificar se uma variável é igual a outra, se ela esta vazia etc.

Para isso, nós temos outro comando chamado "test" (intuitivo o nome, não?). Ele funciona da seguinte maneira: test [expressão].

O test pode testar operações de três tipos: strings, arquivos e aritméticas.

Expressões usando strings:

O test pode apenas comparar strings, ou seja, verificar se uma é igual a outra, e verificar se uma string é vazia ou não. Vamos aos exemplos para facilitar o entendimento:

neo@matrix:~$ test "a" = "a" neo@matrix:~$ echo $? 0 neo@matrix:~$ test "a" = "b" neo@matrix:~$ echo $? 1 neo@matrix:~$ test "a" != "b" neo@matrix:~$ echo $? 0

Aqui comparamos a string "a" com "b". Como era de se esperar, o primeiro retornou verdadeiro (zero), pois a = a e o segundo retornou falso. No terceiro, o símbolo "!=" significa "diferente".

neo@matrix:~$ test -z "neo" neo@matrix:~$ echo $? 1 neo@matrix:~$ test -z "" neo@matrix:~$ echo $? 0 neo@matrix:~$ test -n "neo" neo@matrix:~$ echo $? 0 neo@matrix:~$ test -n "" neo@matrix:~$ echo $? 1

Acima temos os testes de vazio. A opção "-z" verifica se é vazio, e "-n" se não é vazio. No primeiro caso, ele testa se "neo" é uma string vazia, retornando falso. Já no segundo caso, como "" é vazia, retorna verdadeiro. O terceiro e quarto são semelhantes aos primeiros, mas com "-n".

Expressões com arquivos:

Os testes que podem ser feitos com arquivos são para verificar determinadas caracteristicas, como se ele existe, se é executavel, se é um link simbólico, se é um diretório etc.

Alguns exemplos:

A opção "-e" verifica apenas se um arquivo existe e a opção "-d" verifica se o arquivo é um diretório.

A opção "-nt" verifica se o primeiro arquivo é mais novo que o segundo (nt = newer than) e "-ot" verifica se o primeiro é mais velho que o segundo (od = older than):

neo@matrix:~$ test -e /vmlinuz neo@matrix:~$ echo $? 0 neo@matrix:~$ test -d /vmlinuz neo@matrix:~$ echo $? 1 neo@matrix:~$ test -e /usr neo@matrix:~$ echo $? 0 neo@matrix:~$ test -d /usr neo@matrix:~$ echo $? 0 neo@matrix:~$ test /usr -nt /vmlinuz neo@matrix:~$ echo $? 0 neo@matrix:~$ test /usr -ot /vmlinuz neo@matrix:~$ echo $? 1

A seguir, temos uma lista de várias opções disponíveis:

-b arquivo - Verdadeiro se arquivo é um arquivo de bloco, como /dev/hda.-c arquivo - Verdadeiro se arquivo é um arquivo de caracter, como /dev/tty1.

-d arquivo - Verdadeiro se arquivo é um diretório. -e arquivo - Verdadeiro se arquivo existe. -f arquivo - Verdadeiro se arquivo existe e é um arquivo comum. -s arquivo - Verdadeiro se arquivo existe e não é vazio. -h arquivo - Verdadeiro se arquivo é um link simbólico. -p arquivo - Verdadeiro se arquivo é um "named pipe" (fifo, lifo, etc). -S arquivo - Verdadeiro se arquivo é um "socket". -k arquivo - Verdadeiro se arquivo tem seu "sticky bit" ligado. -r arquivo - Verdadeiro se arquivo pode ser lido pelo usuário atual.-w arquivo - Verdadeiro se arquivo pode ser escrito pelo usuário atual. -x arquivo - Verdadeiro se arquivo pode ser executado pelo usuário atual. -O arquivo - Verdadeiro se arquivo pertence ao usuário atual. -G arquivo - Verdadeiro se arquivo pertence ao grupo do usuário atual. -N arquivo - Verdadeiro se arquivo foi modificado desde a ultima vez que foi lido.

Expressões Aritméticas

Expressões aritméticas consistem com comparar dois números, verificando se são iguais, ou se o primeiro é maior que o segundo etc.

Infelizmente não podemos apenas utilizar os símbolos conhecidos para igual (=), maior que (>), menor que (<) etc. Temos que usar operadores reconhecidos pelo "test". Assim, não podemos fazer: "test 1 = 1", devemos utilizar o operador "-eq" (equal): "test 1 -eq 1". A seguir, temos uma lista dos operadores:

-eq (equal): Igual; -ne (not-equal): Não Igual (diferente); -lt (less than): Menor que (<); -le (less than or equal): Menor ou igual ( <= ); -gt (greater than): Maior que (>); -ge (greater than or equal): Maior ou igual (>=);

Alguns exemplos:

neo@matrix:~$ test 1 -lt 2 neo@matrix:~$ echo $? 0 neo@matrix:~$ test 1 -gt 2 neo@matrix:~$ echo $? 1 neo@matrix:~$ test 2 -gt 1 neo@matrix:~$ echo $? 0 neo@matrix:~$ test 2 -ge 2 neo@matrix:~$ echo $? 0

Para finalizar, vamos fazer duas considerações. A primeira é de que o comando "test" pode ser substituido por um par de colchetes [ ]. Assim, o comando test "a" = "b" pode ser escrito como [ "a" = "b" ].

Essa segunda nomenclatura fica mais fácil e simples, principalmente quando estamos utilizando IF:

if [ -e /vmlinuz ]; é mais intuitivo e simples que if test -e /vmlinuz;.

A segunda consideração é que, obviamente, podemos utilizar variáveis no lugar dos argumentos (caso contrário, ficaria difícil utilizar comandos de condicionais em shell script). Assim, se tivermos duas variaveis, valor1=5 e valor2=8:

[ "$valor1" = "$valor2" ] , [ "$valor1" -lt "$valor2" ] etc.

É importante colocarmos os valores entre aspas duplas ("), pois assim o bash pode substituir o que se encontra dentro dessas aspas. Se colocarmos entre aspas simples ('), impedimos o bash de alterar o que se encontra dentro delas. Se não utilizarmos as aspas duplas, vamos ter problemas, principalmente ao trabalharmos com string.

Por exemplo, queremos comparar dois nomes, que se encontram em duas variaveis:nome1="fulano de tal" e nome2="ciclano".Montando nossa expressão condicional: [ "$nome1" = "$nome2" ].O bash irá expandir isso para: [ "fulano de tal" = "ciclano" ], ficando claramente definidos o primeiro e o segundo argumento.

Agora, se não usarmos aspas: [ $nome1 = $nome2 ]. O bash irá expandir isso para: [ fulano de tal = ciclano ]. Perceba que os argumentos se misturam e o bash não vai saber o que comparar.Por isso é importante colocarmos os argumentos entre aspas duplas.

Conclusão

Resumindo, aprendemos na aula de hoje como utilizar comandos condicionais. Isso será fundamental nas proximas aulas, onde iremos tratar os comandos de laço, como o while. Não perca!

Curso de Shell - Aula IV

IntroduçãoNesta aula vamos aprender sobre comandos de laço como o while, for, case e select. Eles nos permitem executar alguns comandos diversas vezes, sob determinadas condições e até montar menuzinhos para interagir com o usuário.

While

Este é provavelmente o comando de laço mais utilizado em programação. Seu entendimento é simples. Ele testa uma condição (como faz o IF) e executa um conjunto de comandos se esta condição for verdadeira. Após a execução desses comandos, ele torna a testar a condição e se esta for verdadeira, ele reexecuta os comandos e assim por diante.

Sua sintaxe é a seguinte:

while [ condição ]; docomando 1;comando 2;...done;

O parâmetro [ condição ] funciona exatamente igual ao do IF. Não voltarei a abortar os parâmetros condicionais pois eles já foram explicados na aula 3. Em caso de dúvida sobre isso, uma rápida revisão na aula 3 é suficiente.

Bom, vamos ao exemplo mais simples do While: um contador.

x = 0While [ "$x" -le 10 ];do

echo "Execução número: $x"?;x = $((x+1));done;

Analisando:

Na primeira linha temos a condição: enquanto o valor da variável x ( $x ) for menor-igual ( -le ) a 10, faça: mostre "Execução número: " e o valor de x ($x). Faça x = x + 1. Isso no bash é feito pelo comando $(( )). Ele realiza operações algébricas com variáveis. No caso acima, estamos somando x + 1 e colocando o resultado no próprio x.

Preste atenção, que as linhas do While precisam de um ";" para terminar, exceto a que contém o "do", pois ele representa o começo do bloco de comandos.

Quando executamos o script acima, temos uma contagem de 1 até 10:

neo@matrix:~$ x=0; while [ "$x" -lt 10 ]; do x=$((x+1)); echo "Execução número: $x";done;Execução número: 1Execução número: 2...Execução número: 10neo@matrix:~$

O While tem muita utilidade em programação, mas fica difícil dar exemplos usando somente ele, pois geralmente ele está associado a execução de outros programas e rotinas mais complexas.

Eu costumo usar ele, por exemplo, quando quero que o napster fique tentando conectar, pois quando ele vai tentar conectar o servidor e não consegue, ele simplesmente termina. Ai eu coloco o while testando a código de retorno dele (lembram da variável $? ) e enquanto estiver diferente de zero (o napster terminou com erro), ele fica executando o napster:

? = 1; while [ "$?" -ne "0" ]; do ./nap; done;

O comando "? = 1" tenta atribuir 1 a variável ?. Isso provoca um erro, pois a variável ? É somente de leitura. Isso é necessário para que ? Contenha algo diferente de 0, pois senão o While não executa a primeira interação. For

O for é semelhante ao while usado como um contador. É necessário fornecer uma lista de nomes e ele executa os comandos para cada nome na lista. Parece meio confuso, mas é simples. Veja a sintaxe:

for <var> in <lista>; docomandosdone;

O For associa o primeiro item da lista de nomes à variável <var> e executa os comandos. Em seguida, associa novamente o segundo item da lista à <var> e executa novamente os comandos... e assim por diante, até acabar a lista.

Veja o exemplo:

neo@matrix:~$ for x in Compra Venda Aluguel;do echo $x;done;CompraVendaAluguel

Ou seja, primeiro ele coloca "Compra" na variável x e executa os comandos, no caso, "echo $x", e assim por diante.

Podemos facilmente implementar um contador, usando em conjunto com o for, o programinha "seq". Ele simplesmente gera uma seqüência de números. Por exemplo, "seq 10" gera uma seqüência de 1 até 10. Assim, podemos usá-lo no for:

for x in $(seq 10);do echo $x;done;

Esse comando é semelhante ao contador implementado com o While. Primeiro o x vale 1 e o for executa os comandos. Depois x vale 2 e ele reexecuta os comandos...

Vamos usar o For para renomear os arquivo de um diretório, mudando todos os arquivo terminados em ".mp3" para ".mp3.bak".

for x in *; do mv "$x" "${x}.bak"; done;

for x in *.mp3; do if [ -e "$x" ]; then mv "$x" "${x}.bak"; fi; done;

No local de <lista> nos colocamos "*.mp3". Isso diz ao bash para expandir (transformar) ele na lista de arquivos terminados com ".mp3". Senão houver nenhum arquivo com essa terminação, o bash não faz nada, ficando para o for a string "*.mp3". Por isso precisamos do IF para testar se o arquivo existe.

Experimente no seu sistema.

echo *.mp3

Vai mostrar os arquivos no diretório atual com terminação mp3, mas sem quebra de linha entre eles, ou seja, um nome após o outro. Se não houver nenhum arquivo com terminação mp3, ele apenas vai mostrar "*.mp3".

Bom, voltando ao For, ele vai atribuir a "x" cada nome na lista de arquivos com terminação ".mp3" e executar os comandos seguintes.

Primeiro ele testa para ver se o arquivo existe ( if [ -e "$x" ]; ), e se existir, renomeia ele para o seu próprio nome acrescido de ".bak" ( ${x}.bak ).

Resumindo, o For faz isso: você fornece uma lista de nomes para ele e ele vai atribuindo esses nomes, em ordem e um por vez, à variável <var> e executa os comandos entre o "do" e o "done;".

Case

O Case está mais para um comando condicional do que para comando de laço, visto que ele não executa "loopings" como o While e o For.

Ele geralmente é utilizado como substituição de vários IFs.. Um exemplo clássico e muito utilizado é quando você precisa testar um parâmetro fornecido na linha de comando. O Case é utilizado desta forma em scripts de inicialização de serviços do sistema.

Vou mostrar a sintaxe em em seguida um script que inicialize/ reinicialize ou pare algum serviço (como o sendmail, apache, bind, etc).

Sintaxe:

case <parâmetro> in<opção 1>) <comandos 1> ;;[opção 2])<comandos 2> ;;* )<comandos se não for nenhuma das opções> ;;esac

Vamos as explicações. O Case pega a string fornecida em <parâmetro> e compara com <opção 1>. Se forem iguais, ele executa <comandos 1> e sai fora. Caso contrario, ele compara <parâmetro> com <opção 2> e assim por diante.

Caso <parâmetro> não seja igual a nenhuma das opções, ele executa os comandos da opção "*", se este existir.Prestem atenção a alguns detalhes na sintaxe. Deve existir um ")" após cada opção e também um ";;" após todos os comandos de cada opção. Vejam o exemplo abaixo:

case "$1" in'start' )echo "Iniciando o serviço..."<comandos para iniciar o serviço> ;;'restart' )echo "Reinicializando o serviço..."<comandos para reinicializar o serviço> ;;'stop' )echo "Parando o serviço..."<comandos para parar o serviço> ;;*)echo "Opção invalida!"echo "As opções válidas são: start, stop e restart";;esac

O Case serve exatamente para isso, ou seja, evitar o teste de vários Ifs. No caso acima, teríamos que utilizar 3 Ifs. Mesmo se o primeiro já fosse verdadeiro, o bash iria testar o segundo e o terceiro, ou seja, ia perder tempo desnecessariamente. Já no case isso não acontece. Após entrar em uma opção e executar seus comandos, ele já pula fora do case sem testar as outras opções abaixo.

Select

O Select é um comando de laço que nos permite mostrar um pequeno menuzinho de opções

para o usuário. Cada opção possui um número e para escolher, o usuário digita o número correspondente a opção desejada. Vejamos a sintaxe a seguir:

select <var> in <lista de opções>;do<comandos>done;

Como disse acima, o select vai mostrar as opções contidas em <lista de opções>, uma por linha, com um número na frente e espera que o usuário digite a opção desejada. Ao digitar a opção, o select atribui o nome da opção a variável <var> e executa os comandos. Para sair do select, é necessário executar o comando "break". Vamos a um exemplo:

select x in Iniciar Reiniciar Parar Sair; doecho "Opção Escolhida: $x"if [ "$x" == "Sair" ]; then break; fi;done;

Ou seja, se o usuário escolher alguma opção diferente de "Sair", o script apenas escreve a opção. Se for escolhida Sair, ele mostra a opção, entra no IF e executa o break, saindo do select.

É interessante combinar o Select com o Case. Vamos mostrar como ficaria aquele exemplo do case, de iniciar um serviço, utilizando o Select, para tornar o script interativo:

select x in Iniciar Reiniciar Parar Sair; docase "$x" in'Iniciar' )echo "Iniciando o serviço..." ;;'Reiniciar' ) echo "Reinicializando o serviço..." ;;'Parar' ) echo "Parando o serviço..." ;;'Sair' ) echo "Script encerrado." break;;*) echo "Opção inválida!" ;; esacdone;

Primeiramente o Select mostra um menuzinho:

1) Iniciar2) Reiniciar3) Parar4) Sair#?

Quando o usuário digitar um número de opção, o select executa o case e este compara a variável x com suas opções. Se achar alguma, executa seus comandos, no caso um echo. Se o usuário escolher Sair, além do echo, ele executa o "break", que interrompe o select:

neo@matrix:~/test$ ./t1) Iniciar2) Reiniciar3) Parar4) Sair#? 1Iniciando o serviço...1) Iniciar

2) Reiniciar3) Parar4) Sair#? 5Opção inválida!1) Iniciar2) Reiniciar3) Parar4) Sair#? 4Script encerrado.neo@matrix:~/test$

Bom pessoal, acho que por hoje é só. ;-) Espero que vocês tenham pego a idéia de como funciona comandos de laços. Pelos menos sabendo a utilidade, sintaxe e como funcionam, quando surgir a necessidade, vocês já vão saber quais ferramentas usar.

Qualquer dúvida, sugestão, etc, podem me enviar um e-mail. Terei o maior prazer em responder. Até a próxima aula!

Curso de Shell - Aula V

IntroduçãoNesta aula teremos um breve tutorial sobre o Grep. Ele não é somente um dos comandos mais úteis, mas o seu domínio abre portas para dominar outros poderosos comandos, como o sed (que trataremos na próxima aula), awk, perl, etc.

Eu fiz uma adaptação/modificação de um tutorial que eu tenho, e por sinal é excelente, sobre o Grep e Expressões Regulares em inglês. Espero que tenha ficado legal e vocês gostem.

O que ele faz?

O grep basicamente faz buscas. Mais precisamente:

grep <palavra> <file>

Retorna todas as linhas do arquivo <file> que contenham <palavra>

Outro jeito de usar o grep é atraves de pipe (lembram dos pipes?). Por exemplo:

ls | grep <palavra>

Lista todos os arquivos que contenham <palavra> em seu nome. Ou seja, a entrada do grep é uma lista de arquivos (gerada pelo ls) que será filtrada, sendo impressas somente as linhas que contenham <palavra>. Usando caracteres coringas

Suponho que todos saibam o que são caracteres coringas. Caso contrário, coringas são caracteres especiais que substituem outros. Geralmente o caracter "*" é um coringa que significa "qualquer caracter em qualquer quantidade". Por isso se a gente executar "ls *", onde "*" entra no lugar do "nome do arquivo", significando qualquer string de qualquer tamanho. Por isso ele lista todos os arquivos.

Mas agora voltemos ao grep. Será que ele aceita coringas ??? A resposta é mais do que sim. O grep suporta algo que vai além de coringas, ele suporta Expressões Regulares. Mas vamos começar apenas com coringas. Um dos mais usados com o grep é o "." Vamos a um exemplo:

>cat filefilebigbad bugbiggerboogy

>grep b.gbigbad bugbigger

Note que boogy não casa, desde que "." significa "qualquer e apenas um caracter". Para significar strings arbitrárias utilizamos "*", que funciona da seguinte maneira:

"A expressão consistindo de um caracter seguido por um * casa com qualquer número (inclusive zero) de repetições desse caracter. Em particular, ".*" significa qualquer string."

Para compreendermos vamos a mais exemplos:

>cat filebigbad bugbagbiggerboogy

>grep b.*g filebigbad bugbagbiggerboogy

>grep b.*g. filebiggerboogy

>grep ggg* filebigger

Avançando para expressões regulares

Os coringas são o começo, mas a idéia vai mais longe. Por exemplo, suponha que queremos uma expressão que case com Frederic Smith ou Fred Smith, ou seja, a string "eric" é opcional.

Primeiro, introduzimos o conceito de um "caracter escapado (escaped character)".

"Um caracter escapado é um caracter precedido por uma barra invertida ( \ ). Essa barra invertida faz o seguinte: (a) Remove qualquer significado especial do caracter. (b) acrescenta um significado especial a um caracter que não tenha um significado especial."

Parece complicado, mas veja nos exemplo que é um tanto quanto simples:

Para procurar uma linha contento o texto "hello.gif", o comando correto seria:

grep 'hello\.gif' file

Desde que "grep 'hello.gif' file" iria resultar linhas contendo: hello-gif , hello1gif , helloagif , etc.

Ou seja, a barra invertida remove o significado especial do ".", passando esse a significar um simples ponto ao invés de um coringa.

Agora vamos passar para expressões agrupadas, a fim de encontrar um jeito de criar uma expressão que case com Frederic ou Fred. Primeiro vamos começar com o operador "?":

"Uma expressão consistindo de caracter seguido por uma interrogação escapada ( \? ) casa com zero ou uma instância daquele caracter".

Exemplo:

"bugg\?y" casa com o seguinte: bugy , buggy mas não com bugggy

neo@matrix:~$ echo bugy | grep "bugg\?y"bugyneo@matrix:~$ echo bugggy | grep "bugg\?y"neo@matrix:~$

Agora vamos para expressões agrupadas. No nosso exemplo, queremos tornar opcional a string "eric" após "Fred", ou seja, não apenas um caracter mas sim um conjunto de caracteres (string).

"Uma expressão dentro de parênteses escapados é tratada como um único caracter"

Exemplos:

"Fred\(eric\)\?" Smith casa com "Fred Smith" or "Frederic Smith" \(abc\)* casa com abc , abcabcabc, etc (isto é, qualquer número de repetições da string "abc", incluindo zero).

Note que temos que tomar cuidado quando nossas expressões contém espaços em branco. Quando eles aparecem, precisamos colocar a expressão entre aspas, para que o shell não interprete mal nosso comando. Para o exemplo acima:

grep "Fred\(eric\)\? Smith" file

Eu aconselho fortemente a sempre usar aspas, mesmo que não usemos espaços em brancos. Já tive muita dor de cabeça porque expressões e comandos não funcionavam simplesmente por não estarem entre aspas. Um dos exemplo acima (o do bugg\?y) não funciona no meu sistema se não estiver entre aspas. Veja:

neo@matrix:~$ echo bugy | grep "bugg\?y"bugyneo@matrix:~$ echo bugy | grep bugg\?yneo@matrix:~$

Outros operadores úteis

Para casar algum caracter de uma lista, use [ ] Veja:

"[Hh]ello" casa com linhas contendo "hello" ou "Hello"

Faixas de caracteres também são permitidos:

[0-3] é o mesmo que [0123] [a-k] é o mesmo que [abcdefghijk]

[A-C] é o mesmo que [ABC][A-Ca-k] é o mesmo que [ABCabcdefghijk]

Existem também algumas formas alternativas:

[[:alpha:]] é o mesmo que [a-zA-Z][[:upper:]] é o mesmo que [A-Z][[:lower:]] é o mesmo que [a-z][[:digit:]] é o mesmo que [0-9][[:alnum:]] é o mesmo que [0-9a-zA-Z][[:space:]] casa com qualquer quantidade de espaços, inclusive tabulações

Essas formas alternativas, como [[:digit:]] é preferível ao método direto, [0-9].

Os [ ] podem ser usado para indicar caracteres que NÃO devem estar na expressão. É só colocar o sinal ^ na primeira posição da lista. Veja:

neo@matrix:~$ echo hello | grep "[Hh]ello"helloneo@matrix:~$ echo hello | grep "[^Hh]ello"neo@matrix

Outro exemplo, um pouco mais complicado:

grep "([^()]*)a" file(hello)a(aksjdhaksj d ka)a

Retorna qualquer linha contendo um par de parentes seguido por "a" e que NÃO contenham outros parênteses dentro.

Mas não com:

x=(y+2(x+1))a

Casando com um número especifico de repetições

Suponha que você queira casar um número específico de repetições de uma expressão. Um bom exemplo são números de telefones. Você pode procurar por um número de telefone com sete dígitos, assim:

grep "[:digit:]\{3\}[ -]\?[:digit:]\{4\}" file

Ou seja, três dígitos, opcionalmente um espaço ou um hífen e mais 4 dígitos.

Procurando por começo e fim de linha: Isso é muito interessante. Digamos que você queira procurar por linhas que contendo espaços em brancos no começo da linha, a palavra hello e então o fim da linha. Vamos começar com este exemplo:

>cat filehellohello worldhhello

>grep hello file

hellohello worldhhello

Isso não é o que nós queremos. O que está errado ? O problema é que o grep procura por linhas contendo a string hello e todas as linhas especificadas contem ela. Para contornar isso, introduzimos os caracteres de começo e fim de linha:

"O caracter ^ significa começo de linha e o $ significa fim da linha"

Retornando ao nosso exemplo:

grep "^[[:space:]]*hello[[:space:]]*$" file

Ou seja, o começo da linha, qualquer quantidade de espaço em branco (inclusive zero), a palavra hello, qualquer quantidade de espaço em branco e o fim da linha. Essa expressão faz o que a gente quer, retornando somente a linha que contém a palavra hello. Outro exemplo:

grep "^From.*Alex" /var/spool/mail/neo

Procura no meu inbox por mensagens de uma pessoa em particular (no caso, Alex). Esse tipo de expressão regular é extremamente útil e filtros de e-mail, como o procmail, utilizam isso para fazerem tudo.

Isso ou Aquilo: Procurando uma coisa OU outra:

"A expressão consistindo de duas expressões separadas pelo operador OU \| casa linhas contendo uma das duas expressões"

Note que você DEVE colocar a expressão dentro de aspas simples ou duplas:

grep "cat\|dog" file casa com linhas contendo a palavra "cat" ou a palavra "dog" grep "I am a \(cat\|dog\)" casa com linhas contendo a string "I am a cat" ou a string "I am a dog".

Usando backreference (referencia anterior)

Digamos que você queira procurar strings que contenham uma substring em mais de um lugar. Um exemplo é as tags de cabeçalhos de HTML. Suponha que eu queira procurar por "<H1>alguma string</H1>". Isto é fácil de se fazer. Mas suponha que eu queira fazer o mesmo, mas permita H2 H3 H4 H5 e H6 no lugar de H1. A expressão .* não é boa, desde que casa com "<H1>alguma string</H3>" e nos queremos que a tag de abertura seja igual a de fechamento. Para fazermos isso, usamos backreference:

"A expressão \n onde n é um número, casa com o conteúdo do n-ésimo conjunto de parênteses na expressão".

Nossa... isso realmente precisa de um exemplo!!!!

.* faz o que nos queríamos fazer acima..."O Senhor \(dog\|cat\) e a senhora \1 foram visitar o Senhor \(dog\|cat\) e a senhora \2"

Esse é outro exemplo bobo. Os casais tem sempre que serem iguais. Ou um casal de cachorro ou de gato.

Alguns detalhes cruciais: caracteres especiais e aspas

Caracteres Especiais: Aqui nós descrevemos os caracteres especiais para RegExp (expressões regulares) no grep. Note que no "egrep", que usa expressões regulares estendidas (atualmente não tem nenhuma funcionalidade a mais que as expressões regulares normais do GNU grep), a lista de caracteres especiais são os mesmos, diferindo somente que alguns não precisar estar "escapados".

Os seguintes caracteres são considerados especiais, e precisam estar escapados:

? \ . [ ] ^ $

Note que o sinal de $ perde seu sentido se tiver caracteres depois dele, do mesmo jeito que o sinal ^ perde seu sentido se tiver caracteres antes dele. Os colchetes [ ] comportam-se um pouco diferente. A baixo segue as regras para eles:- O colchete direito ( ] ) perde seu sentido especial se colocado no começo de uma lista, por exemplo: "[]12]" casa com ] , 1, or 2. - Um hífen perde seu significado especial se colocado por último. Assim, [15-] casa com 1, 5 ou -- O caracter ^ perde seu sentido se não for colocado em primeiro lugar. - A maioria dos caracteres especiais perdem seu significado especial se forem colocados dentro de colchetes.

Aspas: Em primeiro lugar, aspas simples são mais seguras de usar porque elas protegem suas expressões regulares de serem alteradas pelo bash (como foi visto nas aulas anteriores). Por exemplo, grep "!" file vai produzir um erro, já que o shell pensa que "!" está se referindo ao comando de histórico do shell, enquanto grep '!' file funciona perfeitamente.

Quando você deve usar aspas simples ? A resposta é: se você precisa usar variáveis do shell, use aspas duplas. Caso contrário, use aspas simples. Por exemplo:

grep "$HOME" file

Procura em file pelo nome do seu diretório pessoal, enquanto grep '$HOME' file procura pela string $HOME.

Sintaxe das expressões regulares estendidas

Agora vamos ver a sintaxe do egrep em contraste com a sintaxe do grep. Ironicamente, apesar do nome "estendido", o egrep atualmente tem menos funcionalidade do que quando foi criado, para manter a compatibilidade com o tradicional grep. A melhor maneira de fazer um grep estendido é utilizar grep -E que usa a sintaxe de expressões regulares estendidas sem perda de funcionalidade.

Bom, espero que vocês tenham tido uma boa idéia de como funcionam as expressões regulares. Elas são extremamente importante, principalmente para utilizar o grep e o poderosíssimo sed, do qual trataremos na próxima aula. Não se preocupem se estiverem confuso com as expressões, quando começarem a utilizá-las, as idéias vão clareando. Até a próxima.

Curso de Shell - Aula VI

IntroduçãoNesta aula continuaremos nosso tutorial, desta vez falando sobre o Sed. Vale lembrar que este tutorial dará uma breve introdução ao Sed, ajudando os iniciantes a entender como ele funciona. Portanto muitos comandos mais complexos serão omitidos, já que seria necessário um livro inteiro para ensinar tudo sobre o Sed. Mas não se preocupem, ensinaremos o suficiente sobre ele para utilização em shell script.

Resumo de RegExp

Vamos a um resumo sobre as expressões regulares, explicadas na aula anterior e agora aplicadas ao Sed:

^ casa com o começo de linha$ casa com o fim de linha. casa com qualquer caracter simples (apenas um)(caracter)* casa com qualquer ocorrência, em qualquer quantidade, de (caracter)(caracter)? casa com zero ou uma ocorrência de (caracter)[abcdef] casa com qualquer caracter dentro dos [ ] (neste caso, a b c d e ou f), faixas de caracteres como [a-z] são permitidas.[^abcdef] casa com qualquer caracter NÃO incluído em [] (neste caso, qualquer caracter que não seja a b c d e ou f)(caracter)\{m,n\} casa com m-n repetições de (caracter)(caracter)\{m,\} casa com m ou mais repetições de (caracter)(caracter)\{,n\} casa com n ou menos (também zero) repetições de (caracter)(caracter)\{n\} casa com exatamente n repetições de (caracter)\(expressão\) operador de grupo. \n backreference - casa com o n-ésimo grupoexpressão1\|expressão2 casa com expressão1 ou expressão2. Funciona com o GNU sed, mas essa característica pode não funcionar com outros Seds.

Caracteres Especiais

Os caracteres especiais no Sed são os mesmo do Grep, com uma diferença: a barra normal / é um caracter especial no sed. A razão disso ficará clara mais para frente quando estudarmos os comandos do sed.

Como funciona: Uma breve introdução

O Sed funciona assim: ele lê da entrada padrão, uma linha de cada vez. Para cada linha, ele executa uma série de comandos de edição e então a linha é escrita na saída padrão. Um exemplo que mostra como ele funciona: Nós usamos o comando "s", que significa "substitute" (substituir) ou "search and replace" (procurar e trocar). O formato é:

s/expressão-regular/texto-substituto/{flags}

Nós não vamos discutir todas as flags ainda. A única que usamos abaixo é a "g", que significa "substitua todas as ocorrências":

>cat fileEu tenho três cachorros e dois gatos>sed -e 's/cachorros/gatos/g' -e 's/gatos/elefantes/g' fileEu tenho três elefantes e dois elefantes

OK, então o que aconteceu? Primeiro o sed leu a linha do arquivo "file" e executou:

s/cachorros/gatos/g

que produziu o seguinte texto:

Eu tenho três gatos e dois gatos

e então o segundo comando foi executado na linha já editada e resultou:

Eu tenho três elefantes e dois elefantes

Nós atualmente damos um nome para o texto (geralmente uma linha) que o sed leu e está processando (editando): ele chama-se "pattern space" (uma boa tradução seria "área de edição").

O sed lê da entrada padrão e joga na sua área de edição, executando nela uma seqüência de comandos de edição e então ele escreve o resultado na saída padrão.

Comandos de Substituição e Deleção

Primeiro, as maneiras mais usuais do sed é a seguinte:

>sed -e 'comando1' -e 'comando2' -e 'comando3' arquivo>{comando shell} | sed -e 'comando1' -e 'comando2'>sed -f sedscript.sed arquivo>{comando shell} | sed -f sedscript.sed

Então, o sed pode ler do arquivo ou da entrada padrão, e os comandos podem ser especificados em um arquivo de script ou na linha de comando. Esse arquivo, chamado sedscript.sed é um arquivo que contém todos os comandos do sed, ao invés de serem especificados na linha de comando. Esses sed's scripts são úteis quando precisamos de um processamento de texto mais complexo e refinado.

Note o seguinte: se os comandos são lidos de um arquivo (sed script), espaços em branco podem ser fatais. Eles podem fazer o script falhar sem explicação aparente. Eu recomendo editar os arquivos de comandos do sed com um editor como o VIM que pode mostrar o final da linha e você pode ver se existem espaços em branco entre os comandos e o fim da linha.

Comando de Substituição

O formato para o comando de substituição é o seguinte:

[endereço1[,endereço2]]s/procura/substituto/[flags]

As flags podem ser as seguintes:

n - troca a n-ésima ocorrência (na linha) do texto "procura" por "substituto"g - troca todas as ocorrências (na linha) do texto "procura" por "substituto"p - imprime a "área de edição" para a saída padrão se ocorrer uma substituição com sucessow arquivo - imprime a "área de edição" para arquivo se ocorrer uma substituição com sucesso

Se nenhuma flag for especificada, somente a primeira ocorrências na linha é substituída. Note que nós quase sempre usamos o comando "s" ou com a flag "g" ou sem nenhuma flag.

Se um endereço é dado, então a substituição é aplicada a linhas que contenham aquele endereço. Um endereço pode ser ou uma expressão regular dentro de barras normais /regexp/ , ou um número de linha. O símbolo $ pode ser usado no lugar do número da linha para denotar a última linha.

Se dois endereços são fornecidos, separados por uma vírgula, então a substituição é aplicada a todas as linhas entre duas linhas que casam com os endereços fornecidos.

Isto requer algum esclarecimento. Mais precisamente, a substituição ocorre em todas as linhas desde a primeira ocorrência de "endereço1" até a primeira ocorrência de "endereço2".

Não se preocupe se isso tudo parece meio confuso. Os exemplos vão esclarecer melhor. O comando de Deleção

A sintaxe desse comando é muito simples. Ai vai:

[endereço1[,endereço2]]d

Isto deleta o conteúdo da "área de edição" (se esta casar com os endereços fornecidos). Todos os comandos seguintes serão pulados (já que não a nada a fazer com uma área de edição em branco) e uma nova linha será lida e jogada na área de edição e todo o processo se repete.

Exemplos:

Exemplo 1:

>cat fileO gato preto foi caçado por um cachorro marrom.

>sed -e 's/preto/branco/g' fileO gato branco foi caçado por um cachorro marrom.

Exemplo 2:

>cat fileO gato preto foi caçado por um cachorro marrom.O gato preto não foi caçado por um cachorro marrom.

>sed -e '/não/s/preto/branco/g' fileO gato preto foi caçado por um cachorro marrom.O gato branco não caçado por um cachorro marrom.

Neste caso, a substituição é aplicada somente a linhas que casam com a expressão regular "/não/". Portanto, ela não é aplicada a primeira linha, pois esta não contem a palavra "não".

Exemplo 3:

>cat filelinha 1 (um)linha 2 (dois)linha 3 (três)

Exemplo 3a:

>sed -e '1,2d' filelinha 3 (três)

Exemplo 3b:

>sed -e '3d' file linha 1 (um)linha 2 (dois)

Exemplo 3c:

>sed -e '1,2s/linha/LINHA/' fileLINHA 1 (um)LINHA 2 (dois)linha 3 (três)

Exemplo 3d:

>sed -e '/^linha.*um/s/linha/LINHA/' -e '/linha/d' fileLINHA 1 (um)

3a : Este foi bem simples: Nós apenas deletamos as linhas de 1 até 23b : Isto também foi simples: Nós deletamos a linha 3.3c : Neste exemplo, nós fizemos uma substituição nas linhas 1 até 2.3d : Agora este é mais interessante e merece algumas explicações.O primeiro comando vai procurar "^linha.*um" e substituir "linha" por "LINHA", ou seja, somente a primeira linha casa com essa expressão regular. O segundo comando diz para o sed deletar linhas que contenham a palavra "linha". Assim, somente as duas últimas linhas serão deletadas, já que a primeira teve palavra "linha" substituída por "LINHA".

Exemplo 4:

>cat fileoláEste texto será cortadoolá (também será retirado)ReTiRaDo Também!!!tchau(1) Este texto não foi apagado(2) nem este ... ( tchau )(3) nem esteolámas este seráe este tambéme a menos que nós encontremos outro tch*ucada linha até o final do texto será apagada

>sed -e '/olá/,/tchau/d' file

(1) Este texto não foi apagado(2) nem este ... ( tchau )(3) nem este

Isto mostra como o endereçamento funciona quando dois endereços são especificados. O sed encontra o primeiro casamento da expressão "olá" e deleta todas as linhas lidas na área de edição até ele encontrar a primeira linha que contém a expressão "tchau" (está também será apagada). Ele não aplica mais o comando de deleção para nenhuma linha até encontrar novamente a expressão "olá". Desde que a expressão "tchau" não ocorre mais em nenhuma linha subseqüente, o comando de deleção é aplicado até o final do texto.

Resumindo, é simples: Quando ele encontra o primeiro endereço ("olá") ele passa a executar os comandos até encontrar o segundo endereço ("tchau") ou o fim do texto. E isso se repete até acabar o texto.

Exemplo 5:

>cat filehttp://www.kernel.org/

>sed -e 's@http://www.kernel.org@http://www.metalab.unc.edu@' filehttp://www.metalab.unc.edu/

Note que nós usamos um delimitador diferente, @ para o comando de substituição. O Sed permite diversos delimitadores para o comando de substituição, incluindo @ % , ; : Esses delimitadores alternativos são bons para substituições que incluem string como nome de arquivos e outras que contém barra normal /, o que torna o código do sed muito mais legível.

Mais alguns comandos

Aqui veremos mais alguns comandos sobre o sed, a maioria deles mais complexos. Dependendo da aplicação em que vocês forem usar o sed, dificilmente usarão esses recursos, a menos que precisem de um processamento de texto mais refinado e complexo. Veja que eles são muito usado nos scripts do sed (não confunda com shell-script). Backreference no Sed: Uma das coisas legais sobre backreference no sed é que você pode usar não apenas em procura de textos mas também na substituição de textos. O comando Quit: O comando quit ou "q" é muito simples. Ele simplesmente termina o processamento. Nenhuma outra linha é lida para a área de edição ou impressa na saída padrão. Sub-rotinas: Nós agora introduzimos o conceito de sub-rotinas no sed:

No sed, as chaves { } são usadas para agrupar comandos. Elas são usadas dessa maneira:

endereço1[,endereço2]{comandos}

Exemplo: Encontrar uma palavra de uma lista num arquivo

Este exemplo faz um bom uso dos conceitos descritos acima.

Para isto, nos usamos um shell-script, desde que os comandos são muito longos e precisaríamos escrever a longa string X várias vezes. Note que usamos aspas duplas, já que a variável $X precisa ser expandida pelo shell. A sintaxe para rodar esse script é: "script arquivo", onde "script" é o nome dado ao script e "arquivo" é o nome do arquivo a procurar uma palavra da lista.

#!/bin/shX='word1\|word2\|word3|\word4|\word5'sed -e "/$X/!d/$X/{s/\($X\).*/\1/s/.*\($X\)/\1/q}" $1

Uma nota importante: é tentador pensar que:

s/\($X\).*/\1/s/.*\($X\)/\1/

é redundante e tentar encurta-la para:

s/.*\($X\).*/\1/

Mas isto não funciona. Por que? Suponha que temos a linha:

word1 word2 word3

Nós não temos como saber se $X vai casar com word1, word2 ou word3, então quando nós citamos ele (\1), nós não sabemos quais dos termos está sendo citado. O que está sendo usado para certificar-se que não há problemas na correta implementação, é isto:

"O operador * é guloso. Ou seja, quando há ambigüidade sobre qual (expressão)* pode casar, ele tentar casar o máximo possível."

Então neste exemplo, s/\($X\).*/\1/ , .* tenta engolir o máximo da linha possível, em particular, se a linha contém isso:

"word1 word2 word3"

Então nós podemos ter certeza que .* casa com " word2 word3" e portanto $X seria word1. Não se preocupem se não entenderam muito bem este tópico. Ele é complicado mesmo e pouco usado. O importante é entender como ele funciona, o uso de regexp e do comando de substituição e deleção. Para maiores informações sobre o sed, dê uma olhada na sua man page: "man sed"

Espero que vocês tenham gostado desse pequeno tutorial sobre sed. Como disse na aula anterior sobre o Grip, é praticando que se aprende a usa-lo.

O fim...

Bom pessoal, acho que nosso curso termina por aqui. Claro que não deu pra cobrir tudo sobre shell script, se não nosso curso não teria fim. Tentei abordar os topicos mais importantes, de modo que vcs tenham uma boa noção de como funciona as coisas no shell e possam dar os proprios passos. Desculpem se esqueci de falar sobre alguma coisa que vocês estavam esperando. Como disse, estou a disposição para resolver qualquer dúvidas.