Tutorial Linux – Parte 6

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 6. Expressões regulares

Expressões regulares são uma maneira fácil de manipular texto de maneira concisa e rápida, e folgamos em dizer que elas são o recurso mais subutilizado de qualquer sistema que o suporte justamente porque a maioria das pessoas não se preocupa em aprendê-los. Expressões regulares têm um visual pouco convidativo, mas são simples de aprender e economizam muito tempo ao serem usadas. É possível economizar rotinas inteiras de shell script e loops complicados conhecendo apenas alguns poucos metacaracteres e construindo expressões regulares. Expressões regulares são suportadas por praticamente todos os programas Unix que suportam manipulação de texto, como vi, grep e sed. O próprio shell tem seus globs, que nada mais são que expressões regulares simplificadas. Linguagens de programação como awk, Perl e JavaScript trazem expressões regulares como parte da linguagem, outras linguagens como Python colocam a manipulação de expressões regulares na biblioteca padrão e ainda existem bibliotecas de manipulação de ERs para praticamente todas as linguagens de programação sérias, e também para algumas não sérias.

Isso dito, é necessário apenas um certo pensamento programático e conhecer os metacaracteres que fazem uma ER para poder usar esse recurso de forma eficiente. Um programador que não conhece ER pode, por exemplo, fazer um loop que busca várias variantes de uma palavra, ou ainda que analisa uma certa palavra caractere por caractere. O que ele está fazendo quando realiza esse tipo de operação é nada mais que reimplementando algo que já está implementado de forma menos genérica, mais lenta e mais trabalhosa.

A primeira parte do uso de expressões regulares é saber como encontrar texto. A partir disso, é possível realizar substituções simples, e com o uso de agrupamentos de partes da expressão, partir para substituições mais complicadas.

Vamos ver um exemplo simples: temos uma lista de pessoas, e no começo dessa lista queremos acrescentar o tratamento “Sr.”. Eis a lista:

A. Antônio
J. da Silva
V. Corleone
M. Andrade
H. Lekter

Sistematicamente, o que temos que fazer é acrescentar “Sr. ” ao começo de cada linha (com um espaço após o “Sr.”) para ter a lista que desejamos. Nenhuma substituição simples faria isso, é necessário usar metacaracteres das expressões regulares.

O primeiro metacaractere a ser usado é o ^, que significa “começo de linha- qualquer semelhança com o comando do vi para começo de linha não é coinscidência. Nossa expressão fica assim:

s/^/Sr. /

Para executar no vi, digite % e a expressão acima, lembrando que o % significa “em todo o arquivo”.

Isso quer dizer “substitua o começo da linha por ‘Sr. ‘ťť. O resultado é o desejado.

Outros exemplos típicos de ERs seriam “acrescente ‘Sra.’ na frente de nomes que terminam com ‘a'”, “coloque determinada string no fim da linha”, “substituia a ordem nome-sobrenome por sobrenome-nome”, etc. Para fazer esse tipo de tarefa, basta ter pensamento programático e conhecer os metacaracteres apresentados abaixo.

6.1. Metacaracteres

  • . O metacaractere . casa com qualquer um caracter, seja pontuação, alfanumérico, espaço, tab, etc.
  • * Encontra zero ou mais vezes o caractere anterior. Por exemplo, ba* casa com b, ba, baa, baaa, baaaa.
  • [] Um grupo de caracteres. Por exemplo, mal[au] casa com “mala”e “malu”. Também é possível especificar “ranges”, como [a-f], que casa com qualquer letra entre a e f, e [a-fA-F], que casa também com maiúsculas.
  • ^ Começo de linha. Adicionalmente, dentro de colchetes, ganha um significado completamente diferente: negação. Assim, [^a-f] significa “qualquer caractere exceto as letras de a até f.
  • $ Fim de linha
  • Escape. Se quiser casar o texto com um cifrão literal, use $ (caso contrário, você estará procurando um fim de linha.

Esse é o básico de expressões regulares, e isso é suportado em programas como o vi, o sed, o awk, o grep, perl e muitos outros. Alguns programas fazem uso de expressões regulares extendidas, como o egrep e o vi e o perl, que contém alguns metacaracteres úteis mas não essenciais.

6.2. Exemplos

AVISO: Como avisamos anteriormente, esta parte está incompleta!

6.3. sed e expressões regulares

O sed é um editor de texto de linha de comando baseado em expressões regulares muito útil para automatizar tarefas. A maneira básica de usá-lo é assim:

sed 'expressão-regular' arquivo

A saída do comando é enviada para a saída padrão. Como expressões regulares têm muitos caracteres que conflitam com os metacaracteres do shell, nós incluímos a expressão regular, por conservadorismo, sempre entre aspas simples.

Ao contrário do vi, o sed assume que todas as expressões devem ser realizadas em todas as linhas por padrão, então não é necessário especificar o caractere % antes da expressão.

6.4. Grupos

Uma operação muito comum é buscar algum texto desconhecido e depois realizar operações sobre esse texto. Vejamos o exemplo de uma lista de nomes:

Silva, José
Asdrubal, Antônio
Reis, Márcia

Como podemos usar uma expressão regular que inverte a ordem “Sobrenome, Nome”para “Nome Sobrenome”? Nós precisamos, em primeiro lugar, aprender a usar grupos.

Grupos são uma forma de preservar o texto casado para uso posterior em um comando de substituição. Por exemplo, nós precisamos criar uma ER que case com o sobrenome e coloque esse valor em um grupo e também case com o nome e coloque esse valor em outro grupo. Precisamos, em seguida, inverter a representação desses grupos para termos a ordem direta que precisamos.

A marcação desses grupos é feita colocando os trecos das ERs entre (). Porém, em uma ER, () têm seus próprios valores, então precisamos escapar os parênteses com uma barra invertida. Vejamos um exemplo de ER com marcação:

s/([a-z]*)[1-3]*/1/

Essa ER marca a primeira parte da expressão ([a-z]*]) em um grupo, casa com um número de 1 a 3 nenhuma ou mais vezes e depois troca isso tudo pela primeira parte apenas do grupo (na prática, eliminando os números). Note que o primeiro grupo marcado vira1, o segundo vira 2, etc.

Então como fazemos para inverter a ordem “Sobrenome, Nome”? Temos que casar o texto alfabética até a vírgula, sem incluir a vírgula, e botar em um grupo. Logo em seguida, devemos casar com qualquer texto alfabético e botar em um outro grupo. A ER fica assim:

s/([A-Za-z]*), ([A-Za-z]*)/2 1/

6.5. Classes de caracteres POSIX

Se você rodar a maioria dos exemplos acima com uma massa de dados maior, vai notar que as ERs falham quando encontram caracteres acentuadas. Isso acontece porque [a-z] [A-Z] casam apenas com caracteres não acentuados.

Para facilitar o gerenciamento de caracteres especiais, foram criadas classes de caracteres que contém uma série de caracteres de um tipo. Note, porém. que algumas características de caracteres são dependentes de fatores culturais. “á” é um caractere acentuado para um brasileiro, mas nem sequer existe para um inglês, então o funcionamento das classes de caracteres depende da configuração de localização do sistema operacional.

As classes de caracteres POSIX mais comuns são:

  • [:alnum:] – Caracteres alfanuméricos
  • [:alpha:] – Letras
  • [:upper:] – Letras maiúsculas
  • [:lower:] – Letras minúsculas
  • [:digit:] – Números decimais
  • [:space:] – Caracteres de espaçamento
  • [:punct:] – Pontuação

O funcionamento de uma classe de caractere POSIX é o mesmo de um grupo como “a-z”. Logo, uma expressão regular que casa com uma letra maiúscula qualquer poderia ser:

[[:upper:]]

O classe POSIX foi incluída entre colchetes pelo mesmo motivo que uma sequência como “a-z”seria: os colchetes marcam um grupo. Note que a classe POSIX já inclui colchetes, mas isso não elimina a necessidade de usar colchetes para marcar as classes!

Note, porém, que o funcionamento de classes POSIX está sujeito às configurações de seu sistema operacional. Esse é um ponto que o administrador de sistemas deve ajudar você. Note que nem todo sistema Unix suporta as características de internacionalização do português, então o uso de classes POSIX deve ser feito com muito cuidado.

6.6. Palavras finais

Expressões regulares são uma maneira muito prática de validar e manipular texto, mas por seu visual estranho acabam por ser pouco aprendidas e pouco usadas. Mas a “linguagem” expressões regulares é prática e e fácil de aprender, mesmo que aparente ser algo estranho. A manipulação de texto usando expressões regulares economiza tempo e linhas de código ineficientes, e quando bem usadas, facilitam a legibilidade do programa.

Tutorial Linux – Parte 5

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 5. Assuntos a considerar

5.1. Segurança

O desenvolvimento de shell scripts requer cuidados extremados com segurança. Em qualquer ambiente profissional, segurança é um assunto importante e que deve ser levado em conta. É comum ouvir argumentos como “não precisamos nos preocupar com isso, temos um firewallťť ou coisa parecida, mas esses são os argumentos mais perigosos. Um firewall dá sempre uma falsa sensação de segurança. Um firewall não protege, por exemplo, a rede interna de uma empresa de seus próprios funcionários – a principal origem de ataques contra a estrutura de TI de qualquer empresa. Além disso, mesmo que ataques não ocorram, boa parte dos furos de segurança são bugs que podem causar interrupção de serviço. Manter uma disciplina de escrever scripts seguros, além de ser uma boa política de segurança, é uma boa disciplina que certamente evitará horas de manutenção desnecessária no futuro.

Vamos ver alguns dos problemas mais comuns de segurança com relação a shell scripts.

5.2. Permissões excessivamente permissivas

Ao criar arquivos, verifique se eles realmente devem ser lidos por todo o sistema. Use o umask para setar restrições maiores. Não deixe informação visível a não ser que seja estritamente necessário.

Por outro lado, não tente proteger seu script sendo obscuro e pedante. Boa segurança é aquela que, mesmo exposta para o todo o mundo, ainda é robusta. Não baseie sua estratégia de segurança no desconhecimento da maneira como ela funciona. E mantenha o usuário em perspectiva. Manter arquivos sem permissões de leitura para o sistema todo é uma boa política, mas se isso for necessário para o funcionamento do ambiente de produção, não exite em relaxar a permissão (mas analise as alternativas, como criar grupos de pessoas que podem acessar tais logs).

5.3. Race conditions

Uma maneira muito comum de ataque é se aproveitar da criação de arquivos temporários. Com uma pequena manipulação do ambiente, é possível que um usuário não autorizado crie um link simbólico no lugar de algum arquivo temporário usado por um script (ou por qualquer programa). Como as ações praticadas em um link se refletem no arquivo para o qual ele aponta, é possível sobrescrever e mudar permissões de arquivos importantes do sistema.

A maneira mais simples de evitar isso é não usar um diretório temporário compartilhado como o /tmp. Crie um diretório apenas para o usuário, como $HOME/tmp. Sete as variáveis $TMP e $TMPDIR para esse diretório. Se for necessário usar um diretório compartilhado, use o mktemp, que cria nomes de arquivos não predizíveis de maneira segura. Evite construções que usam $$, já que a variável de ambiente $$ (sinônimo do PID do shell) é facilmente predizível na maioria dos sistemas UNIX.

5.4. Dados importantes aparecendo como parâmetros

Cuidado com o conteúdo de dados importantes. Em primeiro lugar, dados como senhas devem estar protegidos no sistema de arquivos por permissões apropriadas (o ideal seria não armazenar senhas em disco). Ao manipular senhas, lembre-se que elas ficam em memória e podem ser bisbilhotadas por outros programas. Nunca passe senhas por parâmetros – eles aparecem na listagem do ps. Use pipes.

5.5. Verifique o $PATH e o $IFS

As variáveis de ambiente $PATH e $IFS são muito importantes. Modificar seus conteúdos pode quebrar o funcionamento do script de muitas maneiras. Sete manualmente e sempre o valor dessas variáveis no topo do script. Uma pessoa que mude um PATH, por exemplo, pode fazer com que um dado importante (como uma senha, ou dados confidenciais) sejam enviados para os programas errados, possivelmente programas que essa pessoa mesmo manipula. Adicionalmente, nunca ponha o diretório atual (.) no PATH.

5.6. Evite scripts setuid

A melhor recomendação com relação a scripts setuid é “não use”. Há inúmeros problemas que podem ser usados para conseguir acesso ao usuário dono do script. Se necessário, use um wrapper compilado ou utilize o sudo. Cada sistema UNIX requer cuidado específico quanto à segurança de shell scripts setuid. Consulte o manual de seu sistema operacional caso precise utilizar esse recurso. Nem todo sistema UNIX suporta shell scripts setuid, então esse é o tipo de solução não portável.

5.7. Favoreça portabilidade

A plataforma pode mudar de um dia para o outro. De maneira geral, escrever scripts já é uma solução portável, mas requer cuidados para não usar recursos específicos de uma determinada plataforma. Mesmo que a mudança de plataforma não pareça algo que possa acontecer, lembre-se que o problema do ano 2000 também parecia não ser algo que pudesse acontecer.

5.8. scripts frágeis

Scripts frágeis podem, em cascata, quebrar o ambiente de produção. Verifique sempre o código de retorno dos comandos para ter certeza de que eles funcionaram como esperado. Ao fazer comparações usando [, sempre inclua as variáveis sendo comparadas entre aspas. Valide dados. Se não é necessário expandir uma variável dentro de uma string, use aspas simples ao invés de duplas. Espere que a entrada de seus programas seja algo extremamente variável (seja bastante permissivo com o que pode entrar) e gere uma saída rigidamente definida.

5.9. Escreva código reutilizável

Sempre que possível, evite fazer código específico para um problema. Gastar um pouquinho de tempo a mais com código reutilizável pode salvar horas de esforço posterior. Além disso, centralizando as principais funções de seu script em torno de código reutilizado significa que há apenas um lugar para consertar ao invés de dezenas de lugares. É possível evoluir e tornar mais robusta uma função reutilizável e ter impactos positivos em todo o sistema.

Por exemplo, se você está escrevendo uma função que lida com logs, possivelmente vai precisar de fazer um processamento de datas. Essas funções de processamento de datas podem ser utilizadas por analisadores de um tipo de log diferente, ou ainda por programas que não são analisadores de log. Criar uma biblioteca de funções de datas e incluí-las nos demais scripts é bem mais inteligente que reescrever as funções diversas vezes.

5.10. KISS

Keep It Simple, Stupid. Não tente escrever aplicações inteiras com shell scripts. Escreva pequenos scripts que realizam operações específicas e que você pode garantir que funcionam bem (essa é uma forma bastante prática de reutilizar código!) e conecte esses utilitários.

Além disso, dentro de scripts, não use estratégias mirabolantes. Use expressões regulares com cuidado, favoreça legibilidade.

5.11. Comente seus scripts

Escreva comentários. Diga para que servem as funções, mas não explique o óbvio. Comentários devem ser escritos para pessoas que conhecem a linguagem, então não é necessário explicar que FOO=bar seta a variável FOO com valor bar. Se o código é muito longo ou complicado, um comentário explicando o que ele faz (não como ele faz) é bem vindo.

5.12. Marque áreas que devem ser consertadas

Algumas vezes, é necessário usar uma solução frágil para resolver rapidamente um problema. Porém, é necessário marcar essa solução para que depois possa ser revisada com mais calma. Ponha um comentário com o texto FIXME e uma explicação do que deve ser consertado. Alguns editores de texto destacam com cores diferentes o texto FIXME, e usar uma string padronizada torna fácil encontrar os pontos em que o script deve ser consertado.

5.13. Indente o código

“Indentar” o código é fundamental para facilitar a compreensão. Separe os blocos de comandos e indente-os de acordo.

5.14. Use variáveis descritivas (mas não muito)

Variáveis de ambiente devem ter um nome minimamente descritivo de sua função. Uma boa variável para conter um nome, por exemplo, é $nome. Não chame essa variável de $n, que seria uma maneira de tornar o programa mais críptico, mas também não chegue no extremo de chamar de $nomedapessoaretiradodatabelatal. Variáveis com nomes muito descritivos são normalmente muletas para um programa excessivamente complicado.

Continua…

Tutorial Linux – Parte 4

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 4. Programando no Shell

O uso do shell como uma linguagem de programação é uma extensão natural de sua utilidade como interpretador de comandos. As pessoas preferem programar em uma linguagem que conhecem e usam no dia-a-dia a ter que aprender uma linguagem totalmente diferente para automatizar suas tarefas.

4.1. O primeiro script shell

Vamos fazer nosso primeiro script. Tudo que queremos fazer agora é imprimir “Hello World” na tela. Nosso primeiro script fica assim:

#!/bin/bash
echo Hello World

Esse scripts deve ser gravado em um arquivo com, pelo menos, permissão de execução e leitura. A primeira linha do script é especial. O marcador #! apenas indica ao kernel que o tipo do arquivo executável é um script. O kernel vai, ao detectar esse marcador, executar o arquivo usando o interpretador de comandos logo à direita (nesse caso, /bin/bash. Verifique se esse é o caminho para o Bourn Against Shell em sua máquina.

Após isso, o que segue é uma série de comandos, exatamente como poderiam ter sido digitados no prompt do shell. Claro que em um shell script, vamos usar recursos que não são normalmente usados no dia-a-dia do uso interativo do shell, como funções, laços, etc, mas é importante lembrar que a linguagem interativa e a linguagem de script são exatamente a mesma.

4.2. Comentários em shell scripts

Os comentários em shell scripts começam com o caractere # e se estendem até o fim da linha, e podem ser colocados em qualquer lugar da linha.

Lembre-se de manter os comentários pequenos e relevantes. O comentário não deve indicar como o shell script é implementado, e sim por que. Evite, portanto, comentários evidentes do tipo:

echo Hello, World! # imprime "Hello, World!"

4.3. Escapando caracteres especiais

As regras de escape de caracteres especiais são exatamente as mesmas que em um shell interativo. Você pode escapar um caractere usando ““uma seqüência de caracteres usando '' e uma seqüência de caracteres mas ainda interpretando variáveis com "". Veja os exemplos no shell interativo:

$ echo # Hello World

$ echo # Hello World
# Hello World
$ echo '# Hello World'
# Hello World
$ echo $HOME
/home/osvaldo
$ echo $HOME
$HOME
$ echo '$HOME'
$HOME
$ echo "$HOME"
/home/osvaldo

Caso haja necessidade de inserir uma string no script, é recomendado que essa string esteja escapada dentro de ". Apenas caso haja absoluta certeza que a expansão de variáveis dentro dessa string seja necessária deve-se usar "".

4.4. Lendo entrada do teclado

Uma maneira simples de ler entrada do teclado é usando o comando read. Rodando read VARIAVEL, o comando read guarda a entrada do teclado em $VARIAVEL.

4.5. Variáveis de parâmetros

Vamos melhorar nosso primeiro exemplo. Ao invés de imprimir Hello, World, vamos imprimir o que quer que o usuário passe como parâmetro. O script ficaria assim:

#!/bin/bash
echo $*

A variável $* é uma variável especial que só faz sentido em scripts. Ela expande para todos os argumentos passados ao script. Veja como ficaria a execução desse script:

$ ./exemplo2 Ola mundo
Ola mundo

Mais usualmente, o autor do script precisa ler um parâmetro em particular passado pelo script. É possível ler cada um dos parâmetros separadamente através das variáveis $1, $2, etc. A variável $0 expande para o comando usado para iniciar o programa. Veja o exemplo:

#!/bin/bash
echo $0 $2

Executando esse script com o nome exemplo3, temos:

$ ./exemplo3 primeiro segundo
./exemplo3 segundo

Finalmente, algumas vezes a quantidade de parâmetros que um script recebe é variável. A quantidade de parâmetros passada ao script é guardada na variável $#.

4.6. O comando de decisão if

if é um comando built-in que serve para fazer uma decisão. O funcionamento dele é assim:

if expressão; then
    comando1
    comando2
    ...
fi

“Expressão” é qualquer coisa que possa retornar um valor verdadeiro ou falso para o shell. Tipicamente, é um comando – seja um comando externo ou built-in.

Vamos criar um pequeno script que lê o arquivo /etc/passwd e procura um usuário no mesmo. O script vai retornar 0 caso o usuário exista, e 1 caso o usuário não exista.

O formato do arquivo /etc/passwd é o seguinte:

login:senha:UID:GID:Nome Completo:Diretório HOME:shell

Nós vamos usar o comando grep com uma pequena expressão regular para procurar o usuário.Em uma expressão regular, ^ indica o começo da linha. Logo, o que queremos encontrar no arquivo é aseqüência ^primeiroparâmetro:, ou ^$1:. Se existir uma linha que combine com essa expressão, o usuário existe no sistema. Ainda, o grep por padrão mostra a linha encontrada na tela. Nós não queremos a linha, queremos apenas saber se ela existe. Para suprimir a saída, o grep tem a opção -q. Fica assim nosso script checa-usuário:

#!/bin/bash
if grep -q ^$1: /etc/passwd; then
    exit 0
fi
exit 1

O if checa o código de retorno do grep. Quando o grep encontra a string no arquivo, ele retorna 0, o que causa a execução do exit 0. Caso negativo, a execução do script segue normalmente, acabando na expressão exit 1.

Opcionalmente nesse caso, pode-se usar a parte else do if. Funciona assim:

if expressão; then
    comando-se-verdadeiro
else
    comando-se-falso
fi

Nosso script revisitado usando else ficaria assim:

#!/bin/bash
if grep -q ^$1: /etc/passwd; then
    exit 0
else
    exit 1
fi

Funcionalmente é idêntico, mas nem sempre tem-se a opção de não usar else.

É possível, ainda, negar o resultado de qualquer expressão, botando ! na frente. Por exemplo:

if ! grep -q ...

4.7. while

O laço while executa determinado código enquanto uma certa condição for satisfeita. Por exemplo, o seguinte script imprime “Esperando” até que alguém crie o arquivo /tmp/continue.

#!/bin/bash

while ! test -f /tmp/continue; do
    echo Esperando
    sleep 10 # Espera dez segundos
done

4.8. for

O for executa um comando (ou uma série de comandos) com uma variável definida cada vez para um valor em uma lista. Por exemplo, o seguinte exemplo imprime linhas com 1, 2 e 3 na tela.

#!/bin/bash

for i in 1 2 3; do
    echo $i
done

4.9. case

Às vezes é necessário checar o valor de uma variável ou a saída de um comando e, de acordo com o conteúdo, executar determinada ação. É possível fazer isso com uma série de if s, mas é mais prático fazer usando o comando case.

Por exemplo, vamos supor que uma variável $STATUS possa ter o conteúdo “OK”, “SUCCESS”, “FAILED”e “NOT RUNNING”(digamos que essa variável venha de um campo de um arquivo de log, por exemplo). Vamos criar uma pequena rotina que imprime na tela se a variável indica sucesso ou erro.

case "$STATUS" in
    OK | SUCCESS)
        echo Programa funcionando com sucesso
        ;; # Indica o fim do bloco de código
    FAILED)
        echo Programa falhou
        ;;
    "NOT RUNNING")
        echo Programa não está rodando
        ;;
esac

Esse é um exemplo bastante completo do uso de case. Note como é possível usar o pipe como o valor de OR para que dois valores executem o mesmo trecho de código. Note também que o valor NOT RUNNING deve vir entre aspas por causa do espaço.

4.10. Expressões matemáticas

Expressões matemáticas são colocadas da seguinte forma:

$ VALOR=$((2+2))
$ echo $VALOR
4
$ echo $(($VALOR+2))
6

4.11. Colocando a saída de um comando em uma variável

É possível colocar a saída de um comando em uam variável (ou de forma genérica setar o valor de uma expressão para a saída de um comando) usando $().

$ SAIDA=$(echo oi)
$ echo $SAIDA
oi

Uma maneira alternativa de fazer a mesma coisa é usando aspas invertidas:

$ SAIDA='echo oi'

Além de colocar a saída de um comando em uma variável, esse tipo de construção pode ser usada em muitas outras partes de um shell script, como em uma comparação, substituindo expressões de outro tipo, etc.

4.12. Fazendo comparações

O comando quase universal de comparações é o [. Na verdade um link para o comando test, às vezes presente como um comando built-in do shell, o [ permite comparar conteúdos de expressões como strings e numericamente, verificar permissões e comparar datas de arquivos. Um exemplo típico é comparar se o conteúdo de uma variável é igual à saída de um comando:

if [ "$MES" = $(date +%m) ]; then
    ...
fi

Note que todo comando [ termina também com ], por motivos principalmente estéticos. Note também que o [ não é muito tolerante a falta de parâmetros. Se a variável MES estivesse vazia e não estivéssemos usando aspas, o comando executado após expansão do shell seria:

if [ = $(date +%m) ]; then

Isso causaria falha no programa, então proteja adequadamente as variáveis com aspas, especialmente dentro do comando [. Alguns outros exemplos de uso do [:

# Checa se o arquivo /tmp/foo não existe
if [ ! -f /tmp/foo ]; then
    ...
fi

# Checa se $MES é menor ou igual ao mês atual
if [ "$MES" -le $(date +%m) ]; then
    ...
fi

# Checa se o arquivo /etc/passwd é mais novo que /etc/nis.map
if [ /etc/passwd -nt /etc/nis.map ]; then
    ...
fi

# Checa se o mês é menor ou igual ao mês atual OU se o arquivo /etc/nis.map
# existe
if [ "$MES" -le $(date +%m) -o -f /etc/nis.map ]; then
    ...
fi

Uma lista completa de opções que podem ser usadas com o [ está na man page do comando test. Abaixo está a lista das mais comuns:

  • -f – Checa se o arquivo existe
  • = – Checa se strings são iguais
  • != – Checa se strings são diferentes
  • -eq – Checa se strings são algebricamente iguais
  • -le – Menor ou igual
  • -ge – Maior ou igual
  • -lt – Menor que
  • -gt – Maior que
  • -ne – Diferente
  • ! – Negação
  • -a – E (AND)
  • -o – Ou (OR)

4.13. As operações mais comuns em shell scripts

4.13.1. Contando linhas, palavras, etc.

O comando wc conta linhas, palavras e caracteres em um arquivo.

  • wc -l Conta linhas
  • wc Conta palavras
  • wc -c Conta caracteres

4.13.2. Separando campos

O comando cut é a forma mais prática de separar campos. Rode da seguitne forma:

cut -d: -f 1,3

O caractere depois do -d é o separados dos campos, e depois do -f vem os campos que deseja pegar.É possível usar “ranges“, como 1-5 para indicar os campos de 1 até 5.

Note que usar o awk para fazer essa separação simples de campos é um exagero, que torna o script mais complexo sem necessidade.

4.13.3. Substituir caracteres

Para substituir caracteres, use o comando tr. A forma de usar é:

tr abc 123

Esse comando substitui as ocorrências de “a”por “1”, “b”por “2”, etc. Também é possível usar ranges:

tr [A-Z] [a-z]

Esse comando transforma todas as maiúsculas de A a Z em minúsculas.

4.13.4. Substituindo strings

O comando para substituir strings é o sed. A funcionalidade básica é:

sed s/isso/aquilo/

Porém, o sed é mais complexo que isso, pois ele aceita o uso de expressões regulares, que serão explicadas mais tarde.

4.13.5. Procurando texto em um arquivo

Para buscar texto em um arquivo ou na entrada padrão, use o comando grep, que vai retornar as linhas em que ocorre o texto:

$ grep root /etc/passwd
root:x:0:0:root:/root:/bin/csh

Mais importante ainda que isso para quem vai fazer scripts é o código que o grep retorna: 0 para string encotnrada, 1 para string não encontrada. Isso, em conjunto com a opção -q, que suprime a saída, é muito útil em um script:

if [ grep -q root /etc/passwd ]; then
    echo Usuário root existe!
fi

4.13.6. Enviando a saída padrão para a saída padrão e para um arquivo

Às vezes é desejável gravar a saída padrão de um programa e ao mesmo tempo enviá-la para um outro programa. Para isso, use o comando tee:

ps -ef | tee processos | grep named

O comando acima guarda a lista completa de processos (a saída do comando ps) no arquivo processos e imprime aquelas linhas que casam com a string “named”na tela.

4.13.7. Economizando tempo setando variáveis

Uma coisa que não foi dita antes é que a forma $VAR de uma variável é apenas uma maneira mais curta de usar a notação completa ${VAR}. A forma completa permite acrescentar alguns manipuladores especiais que economizam código e tempo. Os operadores mais comuns são:

  • ${VAR:=valor} – Seta $VAR como “valor” se a variável estiver vazia ou não existir.
    Funciona como referência normal a $VAR caso não-vazia.
  • ${VAR:-valor} – Se $VAR existe e tem um valor não nulo, age como $VAR, caso contrário $VAR passa a valer “valor“.

4.13.8. Interceptando sinais

Para manter o ambiente de seu script sempre limpo, use a função trap para interceptar sinais. Por exemplo, duas maneiras comuns de matar scripts são enviar os sinais HUP e TERM. Você deve criar um trap para interceptar esses sinais e fazer operações de limpeza (como limpar arquivos temporários).

AVISO: Como avisamos anteriormente, esta parte está incompleta!

Continua…

Tutorial Linux – Parte 3

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 3. O Shell

Como já dissemos anteriormente, o shell é um programa independente que roda na camada do usuário dosistema. Por ser independente do kernel do sistema surgiram diversas versões distintas deshell. O primeiro foi o Bourne Shell criado por Stephen Bourne na década de 70. Essa versãopode ser executada nos UNIX com o comando sh. A partir da década de 70, novas versõesde shell foram sendo criadas, um deles é o C Shell (csh) que foi desenvolvido porBill Joy para ser usada na versão BSD do UNIX em meados de 1980. Existem ainda Tenex/Tops C Shell (tcsh), o Bourne Against Shell (bash) e o Korn Shell (ksh). Neste livro usaremos o bash pois essaé a versão mais comum em distribuições Linux. Para saber qual é a versão de shell que estamos utilizando bastaimprimir o conteúdo da variável de ambiente $SHELL:

$ echo $SHELL
bash
$ _

3.1. O prompt

Quando efetuamos o login no sistema o UNIX irá buscar no arquivo /etc/passwd qual versão do shell é a utilizada pelo usuário. Ao terminar a busca o sistema irá executar um shell para o usuário que entrará no modo ‘interativo’. Apresentando o prompt de comandos:

$ _

O $ geralmente é o prompt exibido para usuários comuns do sistema. No caso do usuário root o prompt será:

# _

Esses prompts indicam que o sistema está pronto e aguardando algum comando. É possível alterar o prompt mudando o conteúdo da variável $PS1:

$ export PS1='prompt: '
prompt: export PS1="$(hostname) > "
maquina1 > _

Notem que alguns caracteres especiais como o ‘>‘ precisam ser precedidos de um caractere ‘‘ para forçar o shell a usar o caractere literal. Essa técnica é conhecida como escape de um caracter.

3.2. Executando comandos

Para executarmos um comando basta digitá-lo no prompt de comandos teclar Enter. Quando fazemos isso, o shell irá verificar se esse comando não é parte de seus comandos internos (veremos mais sobre isso no próximo capítulo), depois disso ele vai procurar por um arquivo com permissão de execução nos diretórios listados na variável de ambiente $PATH. Quando encontrado o shell irá verificar se o arquivo é um binário ou um script. Se o arquivo for um binário será solicitado que o kernel carregue e execute esse arquivo. Caso o arquivo seja um script o shell irá solicitar ao kernel que execute o interpretador apropriado passando como parâmetro o nome do script em questão. A forma que o shell utiliza para identificar qual é o interpretador apropriado para executar o script será vista no próximo capítulo.

Muitos programas requerem que sejam passados parâmetros para eles. Para passarmos esses parâmetros podemos separá-los por um espaço em branco (pode ser um espaço, uma tabulação ou uma quebra de linha). Como já dissemos anteriormente é o shell que fica encarregado de expandir a lista de arquivos e passar para o programa quando passamos nomes de arquivos com globs como parâmetro.

$ ls -l

3.3. Concatenando comandos

Você pode executar vários comandos no prompt, para isso basta separá-los por ‘;’:

$ date; ls -l
Thu Dec 13 15:28:16 GMT-2 2001
total 0
-rw-rw-r--  1  osvaldo  osvaldo  0  Dec 13  15:28  arquivo1
-rw-rw-r--  1  osvaldo  osvaldo  0  Dec 13  15:28  arquivo2
$ _

Como vocês viram no exemplo acima, o shell executou o comando date e em seguida o comando ls -l.

3.4. Evaluation

Nós podemos também executar um segundo comando apenas se o primeiro retornar sucesso usando && (pronuncia-se “and”ou “e”). Por exemplo:

$ grep -q usuario /etc/passwd && finger usuario

O comando acima procura pela string no passwd. Caso ela seja encontrada, o grep retorna sucesso e o comando finger é executado.

Também é possível executar um comando caso outro falhe, usando || (pronuncia-se “or“ou “ou”). O seguinte comando cria um usuário caso ele não exista:

$ grep -q usuario /etc/passwd || adduser usuario

3.5. Redireção

Como foi dito anteriormente o UNIX possui um dispositivo padrão de entrada (stdin), de saída (stdout) e para saída de mensagens de erro (stderr) que, por padrão estão associados ao teclado e ao monitor. Felizmente o UNIX oferece um mecanismo para modificar esses padrões possibilitando que a entrada seja a saída de um outro programa (pipe) ou que a saída de erro seja enviada para um arquivo de log de erros. Esse mecanismo é conhecido como redireção.

É uma prática comum, e recomendada, para todos os programas que rodam em UNIX que eles enviem mensagens para stdout, recebam entradas de stdin e enviem suas mensagens de erro para stderr. Veja o exemplo abaixo:

$ ls -l /etc/passwd
-rw-r--r--  1  root  root  987  Dec 4  08:39  /etc/passwd
$ ls -l /etc/foo
/etc/foo not found

No primeiro caso, a saída foi enviada para stdout. No segundo caso, para stderr. Nós podemos usar os indicadores de redireção para verificar isso. O indicador de redireção > envia a saída padrão (stdout para um arquivo determinado. Veja o exemplo:

$ ls -l /etc/passwd > /dev/null
$ ls -l /etc/foo > /dev/null
/etc/foo not found

Apenas a saída padrão foi redirecionada para /dev/null e, portanto descartada. Lembre-se que um programa pode enviar dados para as duas saídas durante a mesma execução. O ls poderia ter retornado uma mensagem de erro para a saída de erro e uma listagem de arquivos para a saída padrão ao mesmo tempo.

Da mesma forma como podemos descartar a saída enviando-a para /dev/null, também podemos usá-la enviando-a para um arquivo para uso posterior. Veja o exemplo:

$ ls -l /etc > lista

Esse comando envia a listagem de arquivos em /etc para o arquivo lista. Se o arquivo tinha algum conteúdo anterior, ele é eliminado. Para evitar isso, podemos usar o redirecionador duplo ».

$ ls -l /etc >> lista

Nesse caso, o resultado da saída do comando é acrescentado ao fim do arquivo lista. Assim como acontece com o redirecionador simples >, se o arquivo não existia, ele é criado.

Da mesma forma como é possível enviar dados para um arquivo, é possível ler dados provenientes de um arquivo. Para isso, usa-se o redirecionador <. O seguinte comando lê o arquivo lista e passa seus conteúdos para o comando grep:

$ grep passwd < lista

Ainda, a saída de erro pode ser redirecionada com o redirecionador 2>. Veja o exemplo:

$ ls -l /etc/foo > /dev/null
/etc/foo not found
$ ls -l /etc/foo 2> /dev/null

Confira na tabela 3.1 os redirecionadores mais usados.

Redirecionador Significado
> Envia saída padrão para arquivo especificado zerando o arquivo
>> Envia saída padrão para arquivo especificado adicionando os conteúdos ao arquivo
2> Envia saída de erro para arquivo especificado zerando o arquivo
2>> Envia saída de erro para arquivo especificado adicionando o conteúdo ao arquivo
< Usa arquivo especificado como entrada
<< STRING Lê da entrada padrão até encontrar string (ver próximo capítulo)

Tabela 3.1: Redirecionadores.

Se você desejar redirecionar a sua saída padrão para outra saída, tal como a saída de erro, basta redirecioná-la para &2:

$ echo "Erro: Esse programa encontrou um erro ao abrir o arquivo" >&2
Erro: Esse programa encontrou um erro ao abrir o arquivo

A mensagem acima foi enviada para stderr. Da mesma forma é possível redirecionar a saída de erro para a saída normal, para isso basta usar 2>&1:

$ ls -l > lista 2>&1

O comando acima envia a saída do comando ls para o arquivo lista. As mensagens de erro também serão enviadas para o arquivo lista.

3.6. Pipe

Uma das operações mais frequente na informática é a análise de dados. Uma das formas mais indicadas para fazer essa análise no UNIX é combinar os diversos comandos disponíveis no ambiente. A maneira de ‘conectar’ os comandos no UNIX é utilizando o | (pipe).

Quando o shell encontra um | na linha de comando ele sabe que deve enviar a saída padrão de um programa para a entrada padrão de outro aplicativo. Esse recurso é um dos mais poderosos recursos disponíveis em ambientes UNIX. O comando grep por exemplo mostra a linha em que ele encontrou um texto que precisamos procurar:

$ ls -l | grep passwd
-r--r--r--  1  root  root  3903  Dec 5  16:38  passwd

O comando ls -l envia a lista dos arquivos disponíveis no diretório para a saída padrão. Essa saída é enviada para o pipe que por sua vez a envia para a entrada do comando grep que está instruído para procurar pela string ‘passwd’. Quando o grep encontra a string ele a imprime na tela.

3.7. Ambiente

O shell também nos possibilita a criação de variáveis. Essas variáveis, quando criadas, estão disponíveis para o ambiente em que ela foi criada. Por esse motivo elas são conhecidas como variáveis de ambiente. Quando o seu shell script termina ou o shell em funcionamento termina a execução (exit) as variáveis são restauradas ou removidas do ambiente de forma a devolvê-lo ao mesmo estágio em que se encontrava. O shell é sensível ao caso com nomes de variáveis, assim como para comandos, ou seja, VARIAVEL é diferente de Variavel. Existem algumas variáveis que são definidas por padrão pelo próprio shell. Essas variáveis muitas vezes armazenam dados do sistema para serem usados pelos scripts. A tabela 3.2 mostra algumas das mais comuns (uma lista completa pode ser obtida com o comando set).

Variável Conteúdo
$HOME Caminho para o diretório do usuário
$PATH Caminhos de busca de comandos
$PS1 Conteúdo do prompt do shell
$PWD Diretório atual
$TERM Tipo de terminal
$USER Usuário atual
$? Código de retorno do último comando executado
$$ PID do shell em execução

Tabela 3.2: Variáveis padrão.

Para atribuir um valor a uma variável basta utilizar o operador =:

$ var=foo
$ echo $var
foo
$ _

O shell utiliza o método de substituição do valor da variável pelo conteúdo dela sempre que encontra o símbolo $ precedendo um nome de variável. Isso pode ser observado no exemplo abaixo:

$ var='ls -l'
$ $var
total 23872
-rwxr-xr-x  1  root  root  1825  Sep 3  10:27  usuario

Um erro muito comum encontrado em scripts é a utilização de espaços para separar o operador =. O operador = deve ser usado imediatamente depois do nome da variável e seguido, imediatamente, pelo valor a ser atribuído à variável.

$ var = 'ok'
bash: var: command not found
$ _

Quando queremos verificar o conteúdo da variável podemos imprimí-lo com o comando echo:

$ echo $HOME
/home/usuario

As variáveis, além de ser válidas no shell atual, também podem ser passadas para todos os programas executados a partir desse shell. Para que isso aconteça, porém, essas variáveis devem ser exportadas com o comando export:

$ export var

Ainda, é possível exportar uma variável ao mesmo tempo em que ela é criada:

$ export var=oi

Note, porém, que o ambiente só é herdado por processos filhos. Se você abre um shell secundário e cria uma variável, ela vai aparecer para os processos filhos do shell secundário, mas não vai aparecer no shell primário. O ambiente só é copiado ‘para baixo’, nunca ‘para cima’ entre os processos.

Para eliminar variáveis, use o comando unset:

$ set | grep var
var=foo
$ unset var
$ set | grep var
$ _

3.8. Controle de trabalhos

Um processo pode ser iniciado em background adicionando o caractere & ao fim da linha de comando. Por exemplo:

$ kermit &
[1] 6185
$ _

Os números mostrados logo depois da execução do processo em background são respectivamente o número do job no shell e o ID do processo (PID) no sistema. O PID é um número global de identificação do sistema enquanto o número do job só vale para o shell atual. A sua utilização é mais prática ser um número pequeno, incremental e independente dos demais processos do sistema.

O exemplo 3.8 vai iniciar o kermit e voltar imediatamente ao prompt do shell para que o usuário possa continuar executando comandos. Esse comando é útil para disparar programas demorados e que não mostrem saída no terminal (por padrão, o programa pode imprimir texto no terminal livremente, o que pode “embaralharťť a tela do usuário). Caso durante a execução do programa ele tente ler alguma entrada do teclado (como o kermit faz), o processo será interrompido.

O shell pode eventualmente mostrar mensagens sobre o status de seus jobs. A mensagem de que o processo foi interrompido por tentar ler dados do teclado é:

[1] + Stopped (tty input) ftp &

Por padrão, esse tipo de mensagem aparece apenas quando o prompt vai ser redesenhado na tela (ficar com o shell parado esperando a mensagem não funciona. Apertar Enter uma vez pode forçar o aparecimento da mensagem). Novamente, o primeiro número é o número do job no shell, seguido pela explicação do que aconteceu com o processo e por que motivo e, finalmente, a linha de comando usada para lançar o processo.

O usuário pode abrir vários processos em background. O gerenciamento dos jobs é feito, no shell, com o comando jobs. Veja o exemplo:

$ jobs
[3] + Stopped (tty output) vi &
[2] - Stopped (tty output) kermit&
[1] Stopped (tty input) ftp &

Aqui o usuário tem três processos, parados, em background. Para trazer para a frente um dos processos, usa-se o comando fg.

$ fg %3
<traz o vi para a frente>

Note como o número do job deve ser precedido por %.

Uma das operações mais comuns, porém, é o usuário ter que interromper seu programa atual para verificar alguma outra informação em outra parte do sistema. O shell, por padrão, aceita que se digite no teclado a seqüência CTRL+Z. Essa seqüência faz com que o processo atual seja jogado em background exatamente como teria sido se o programa tivesse sido iniciado com &. O usuário pode voltar ao programa usando o comando fg seguido pelo número do processo. Adicionalmente, pode executar apenas fg para voltar automaticamente ao último processo manipulado.

Um inconveniente às vezes indesejado acontece quando o processo deveria continuar rodando enquanto o usuário volta ao shell. Quando o processo é enviado interativamente para background, ele é parado. Um processo parado em background pode ser continuado usando o comando bg, que funciona como o comando fg. Assim como o fg, bg aceita um número de job precedido por % e, se não tiver parâmetros, usa o último job manipulado.

O comando kill pode ser usado para mandar sinais para os jobs. O seguinte comando envia o sinal TERM para o processo vi iniciado em background.

$ jobs
[1] + Stopped (tty output) vi&
$ kill %1
[1] + Terminated vi&

O sinal TERM é o padrão enviado pelo kill. Poderia-se usar, por exemplo, kill -KILL %1 para enviar o sinal KILL, ou qualquer outro sinal. kill -l lista os sinais disponíveis no sistema. Note que, apesar do nome, o kill não serve somente para matar os processos.

Finalmente, caso o usuário tente sair do sistema com jobs que não foram interrompidos, a mensagem You have stopped jobs vai aparecer. O usuário terá, então, mais uma chance para fechar seus programas rodando. Se o usuário tentar sair do sistema mais uma vez imediatamente após essa mensagem aparecer, os jobs rodando serão interrompidos.

3.9. Aliases

Aliases são uma forma de encurtar comandos longos e muito usados, e são muito úteis e largamente usados por usuários experientes. O bash vem com vários aliases definidos por padrão. O usuário pode consultar a lista de aliases com o comando alias.

Veja, por exemplo, o alias stop. Ele está definido como

stop=kill -STOP

Caso o usuário rode stop, internamente o comando será substituído por kill -STOP. Parâmetros podem ser usados normalmente.

Novos aliases podem ser usados executando-se o comando alias nomedoalias='comando a ser executado'.

Continua…

Tutorial Linux – Parte 2

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 2. O editor vi

O editor vi foi criado por Bill Joy aproximadamente em 1975, em Berkeley. Ao contrário da maioria dos editores de seu tempo, o vi provia uma interface que permitia que o texto fosse visualizado enquanto era editado.

A maneira anterior de editar textos era usado um editor orientado a linha. Sistemas UNIX ainda trazem um editor de linha chamado ed por padrão. Esses editores eram usados em terminais burros ou até mesmo usando uma impressora como saída de todo o processo de edição, sem terminal.

Há vários motivos para não se usar o vi. A edição modal é um conceito ultrapassado, os comandos são crípticos e nada intuitivos. As implementações mais antigas, como as que vêm em sistemas UNIX comerciais trazem poucas facilidades de recuperação em caso de erro e um sistema de expressões regulares ultrapassado. Os comandos do editor são muitas vezes inconsistentes, as mensagens de erro são curtas e pouco explicativas, não há ajuda online, a barreira de entrada para iniciantes é muito alta.

Isso dito, o vi ainda é o editor padrão em sistemas UNIX, está disponível em qualquer lugar, tem implementações para todos os sistemas operacionais imagináveis, e, principalmente, funciona. O vi, uma vez aprendido, é fácil e prático de usar, suporta edição avançada, é prático e leve.

2.1. Os modos

O vi tem dois modos. O primeiro modo é o modo de comando, em que é possível usar as teclas para passar comandos ao editor. No modo de edição, as teclas inserem texto.

2.2. Criando seu primeiro texto

Vamos criar nosso primeiro texto com o vi. Execute o vi rodando:

$ vi [nome-de-arquivo]

A tela inicial do vi contém apenas linhas vazias (marcadas por ~). Se você passou um nome de arquivo como parâmetro, ele será aberto e o conteúdo mostrado na tela. Se o arquivo passado não existe, assim que gravado ele será criado.

2.3. O básico

Quando inicia, o vi está no chamado modo de comando. Nesse modo, as teclas são utilizadas para a execução de comandos e movimentação do cursor (algumas implementações mais recentes do vi permitem que a movimenteção do cursos em modo de edição).

Você pode entrar no modo de inserção com a tecla i. Uma vez no modo de inserção é só digitar o texto:

Esse é um pequeno texto
escrito com o vi_
~
~

O vi, por padrão, não quebra linhas automaticamente (mesmo que no terminal elas apareçam quebradas). É necessário apertar Enter para que as quebras sejam inseridas.

Para sair do modo de inserção e entrar no modo de comando, aperte a tecla Esc. Apesar de não haver mudança visível na tela (exceto possivelmente pelo recuo do cursor em um caracter) agora é possível entrar comandos e mover o cursor. Mexa o cursor pelo texto usando as seguintes teclas:

Tecla Movimento
k Move o cursor uma linha para cima
j Move o cursor uma linha para baixo
h Move o cursor uma coluna para esquerda
l Move o cursor uma coluna para direita

Tabela 2.1: Teclas de movimentação de cursor.

Em alguns terminais e teclados, é possível usar as teclas de direção para mover o cursor. É recomendável evitar esse hábito. As teclas de caracteres são universais, funcionam mesmo no mais primitivo dos terminais. Além disso, elas são reconhecidas por vários outros comandos, como o more, que dependendo da implementação podem não reconhecer as teclas de direção. Além disso, o uso de teclas de caracteres mantém as mãos na parte certa do teclado, dando mais agilidade ao uso do vi. Mais adiante neste capítulo daremos mais informações sobre os comandos para movimentação de cursor.

Tecla Comando
i Insere o texto na posição do cursor (insert).
I Insere o texto no início da linha.
a Insere o texto à direita do cursor (append).
A Insere o texto no fim da linha.
o Insere uma linha abaixo do cursor (open line).
O Insere uma linha na posição do cursor.
s Insere o texto no lugar do caractere sob o cursor.
S Insere o texto no lugar do caractere sob o cursor.
R Entra em modo replace que sobrescreve os caracteres.

Tabela 2.2: Teclas inserção.

Mova agora o cursor para o fim da segunda linha. Se você apertar i para entrar em modo de inserção, os caracteres que você inserir entrarão antes do último caractere da linha. Use a tecla a para começar a escrever do lado direito do cursor.

Após terminar sua edição, volte para o modo de comando com Esc. O vi possui os comandos : que são utilizados para operações que requerem informações do usuário (como o nome do arquivo para gravar). Para entrar com um comando : basta teclar :. Neste momento você poderá entrar com o comando na última linha do terminal. O comando para gravar arquivos em disco é o :w. Digite-o e tecle Enter.

Se você abriu o vi sem especificar o nome do arquivo será necessário informa-lo ao comando :w:

:w nome_do_arquivo

Finalmente, para sair do editor, utilize o comando :q (você pode ainda salvar e sair ao mesmo tempo usando 😡 no lugar de w, ou ainda apertando ZZ no modo de comando). Se você desejar sair sem salvar as alterações em disco basta utilizar o comando :q! onde ! é usado para ‘forçar’ a execução dos comandos.

2.4. Movimentação do cursor

Já foi dito que em alguns terminais pode-se fazer uso de teclas como Home, Page Up, e as setas de direção. Entretanto, na maioria dos terminais UNIX disponíveis essas teclas não funcionarão. Para esses casos o vi possui comandos de movimentação de cursor em seu modo de comando.

2.4.1. Movendo-se por palavras

Movimentação caracter-por-caractere não é muito prática. Uma maneira mais rápida de movimentar-se dentro de uma linha é usando as teclas e e b.

Tecla Comando
e Move para o fim da palavra.
E Move para o fim da palavra e não para em pontuação.
b Move para o início da palavra.
B Move para o início da palavra e não para em pontuação.

Tabela 2.3: Teclas de movimentação.

A diferença das teclas maiúsculas é perceptível em texto como “levante-se”. Se o cursor estiver em cima da letra l, apertar e leva o cursor para a seguinte posição:

levante-se

Apertar E leva o cursor para a posição a seguir:

levante-se

Coisa análoga acontece com os comandos e e E.

2.4.2. Movendo-se entre várias linhas e palavras

No vi, a maioria dos comandos pode ser multiplicada precedendo o comando pela quantidade de vezes que ele deve ser executado. É possível, portanto, mover 4 linhas para baixo usando o comando 4j. Isso vale para linhas, palavras e para uma infinidade de outros comandos. É possível, por exemplo, mover-se duas palavras à direita usando o comando 2e.

2.4.3. Movendo-se entre as linhas

No modo de comando, digite :<númerodalinha><Enter> para ir direto a uma determinada linha. Por exemplo, :13<Enter> vai para a linha 13. Finalmente, para mostrar em que linha você está, digite Ctrl+G.

Tecla Comando
G Move para o fim do arquivo.
:$ Move para o fim do arquivo.
$ Move para o fim da linha.
^ Move para o começo da linha (texto).
0 Move para a coluna 0 (começo da linha)

Tabela 2.4: Comandos de movimentação.

Existe também uma forma de se executar o vi que faz com que ele abra um arquivo e já posicione o cursor numa linha determinada:

$ vi +num_linha arquivo

2.4.4. Movendo-se pela tela

Os comandos para se mover pela tela são:

Tecla Comando
H Move o cursor para o topo da tela
M Move o cursor para o centro da tela
L Move o cursor para o fim da tela

Tabela 2.5: Comandos para movimentação de cursor na tela.

Os equivalentes a Page Up e Page Down são:

Tecla Comando
Ctrl+D Mover meia tela abaixo
Ctrl+U Mover meia tela acima
Ctrl+F Mover uma tela abaixo
Ctrl+B Mover uma tela acima

Tabela 2.6: Comandos para movimentação.

2.5. Copiando, cortando e colando textos

O vi apresenta também recursos de copia, corte e colagem de textos. Algumas implementações mais modernas do vi (como o vim) apresentam o modo visual que pode ser usado para marcar textos de forma mais simples. Para entrar em modo visual basta usar o comando v no modo de comando e marcar o texto movimentando o cursor. Teclar Ctrl+V no modo visual irá habilitar o modo de colunas. Esse modo é bastante útil, principalmente, para programadores.

Explicaremos adiante como proceder para copiar, cortar e colar textos em implementações de vi que não possuem o modo visual.

2.5.1. Uma nota sobre os comandos que manipulam texto

Comandos de manipulação de texto funcionam digitando o comando em questão (como y para copiar) e seguido por uma das teclas especiais de movimentação de texto. Por exemplo, y$ copia o texto até o fim da linha.

2.5.2. Copiando e cortando

O comando para copiar texto é y (mnemônico: yank). Após o comando, segue-se um parâmetro para escolher a área a ser copiada. O comando d funciona de maneira quase idêntica:

Tecla Comando
yw ou dw Copia ou recorta até o fim da palavra
yy ou dd Copia ou recorta a linha
y$ ou y$ Copia ou recorta até o fim da linha
yG ou dG Copia ou recorta até o fim do arquivo

Tabela 2.7: Comandos de cópia e recorte de texto.

2.5.3. Colando o texto

Os comandos para colar texto são:

Tecla Comando
P Cola texto na linha atual
p Cola texto na linha abaixo

Tabela 2.8: Comandos para colagem de textos.

Caso o texto copiado não seja uma linha completa, os dois comandos colam no lugar do cursor.

2.6. Procurando texto

Os comando de busca de textos do vi são: / e ?. O recurso de procura e procura/troca de textos do vi é muito poderoso pois utiliza-se de expressões regulares para efetuar as suas buscas. Um resumo sobre as expressões regulares será visto no capítulo 6. Por hora vamos nos preocupar apenas com as buscas simples de texto.

Os comandos de procura de textos funcionam de forma semelhante ao comando :. Após acionados o cursor se move para a parte inferior do terminal para que seja digitado o texto a ser procurado. A sintaxe dos comandos de busca é:

Tecla Comando
/<texto|expressão-regular> Procura o texto na direção do fim de arquivo
?<texto|expressão-regular> Procura o texto na direção do começo de arquivo

Tabela 2.9: Comandos de procura de textos.

Note aqui mais uma característica do vi: normalmente, uma tecla realiza um comando, e seu oposto é realizado pela mesma tecla com Shift apertado.

Tecla Comando
n Repete a busca
N Repete a busca em sentido oposto

Tabela 2.10: Comandos para repetir buscas.

2.7. Substituindo texto

O comando de busca e substituição de texto é semelhante ao comando de busca simples, e também funciona na linha de comando do vi.

<área>s/<expressão-regular>/<texto a colocar>/<modificador>

Aqui, área é a parte do arquivo em que a busca deve ser feita. Vamos ficar com o caso comum, que é usar o modificador % para indicar “todo o arquivo”. Se o modificador for omitido, a busca é feita só na linha atual.

O modificador tem também uma série de valores possíveis, mas os mais úteis são g e c. Se não for usado o g (global), a substituição é limitada à primeira ocorrência de cada linha. O modificador c pede confirmação de cada substituição. É possível combinar modificadores.

2.8. Um comentário final

O vi é um editor de textos bastante críptico, e não há nada nele que sequer vagamente lembre facilidade de uso. Isso deixado de lado, para o profissional, o vi é uma ferramenta ágil, universal, leve e prontamente disponível para todos os sistemas operacionais. Apesar de ter uma curva de aprendizado incrivelmente íngrime, o benefício de se familiarizar com o vi vale o esforço. Persistência é a palavra chave para uso do vi. E vale a pena.

Continua…

Tutorial Linux – Parte 1

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 1.UNIX

O UNIX tem uma história interessante que estaremos contando agora. É uma história de mais de 25 anos que foi iniciada nos laboratórios Bell da AT&T, nos EUA, como um passatempo de um dos jovens pesquisadores que trabalhavam lá então. Hoje o UNIX roda nos mais importantes servidores do mundo e faz parte do alicerce da estrutura sobre a qual a Internet funciona.

Nos idos de 1970 o M.I.T., a General Electric e o Bell Labs iniciaram o desenvolvimento do sistema operacional MULTICS (MULTiplexed Information and Computing Service) que trazia diversas inovações, como um sistema de arquivos hierárquico e controle de permissões por arquivo. Entretanto esse trabalho foi se mostrando um grande ‘elefante branco’ e isso fez com que o Bell Labs se retirasse do projeto obrigando um dos pesquisadores envolvidos no projeto, Ken Thompson, a procurar algo mais ‘útil’ para fazer.

Ele resolveu então iniciar o desenvolvimento de uma versão menos ambiciosa do MULTICS em linguagem assembly para um PDP-7 que estava esquecido num canto do laboratório. Quando Brian Kernighan viu o projeto em andamento resolveu batizá-lo de UNICS (UNIplexed Information and Computing Service) como uma brincadeira com o MULTICS. O nome foi posteriormente adaptado para UNIX.

O projeto começou a ficar tão bom que ganhou a participação de outro cientista, Dennis Ritchie. Nessa época eles portaram o UNIX para versões mais modernas dos PDPs. Como ele era todo desenvolvido em linguagem assembly era necessário reescrever grandes quantidades de código para cada versão nova do trabalho. Foi então que Brian Kernighan e Dennis Ritchie resolveram criar uma nova linguagem de programação baseada em B e BCPL que foi denominada C para que pudessem desenvolver o UNIX nela.

Nessa época a AT&T enfrentava um ação de monopólio na justiça que a impediu de vender software. Isso fez com que ela distribuisse livremente os códigos para as universidades americanas formando assim um ambiente de desenvolvimento de software semelhante ao que vemos hoje com o Linux. Nas universidades americanas, o UNIX ganhou muitos dos recursos que o tornaram popular, como uma pilha de rede TCP/IP e memória virtual paginada.

Essa divisão do desenvolvimento entre a AT&T e as universidades americanas gerou uma das grandes cisões do mundo UNIX. Cada sistema UNIX tem pequenas diferenças, mas eles estão agrupados em duas famílias maiores. São elas a família do System V , formada por sistemas derivados do código da AT&T, e a família BSD, formada pelos sistemas derivados do UNIX da Universidade da Califórnia em Berkeley (BSD significa Berkeley Software Distribution). Como exemplo de sistema que seguiu o estilo System V temos o HP-UX, e como exemplo de um sistema BSD temos o SunOS. Essa grande divisão foi quase fechada com o tempo, na medida em que os sistemas acabaram adquirindo os recursos uns dos outros, mas até hoje essa diferença se manifesta em coisas tão simples quanto os parâmetros que um comando aceita.

De qualquer forma, o norte do desenvolvimento do UNIX é a filosofia KISS (Keep It Simple, Stupid!). Se você for desenvolver uma ferramenta para editar textos, ela deve editar textos; deixe a função de planilha pra algum outro aplicativo. No UNIX, cada programa realiza uma tarefa, e realiza essa tarefa bem. Há maneiras bem simples de realizar a integração de diversos aplicativos pequenos e eficientes para realizar as operações que o usuário precisa. Há mais de 25 anos, com a filosofia KISS, sistemas UNIX têm servido bem a função de prover uma plataforma sólida e eficiente para os mais diversos problemas computacionais.

1.1. Estrutura

O UNIX é um sistema operacional multiusuário, multitarefa desde o seu projeto. Ele é organizado em camadas de abstração de hardware como ilustrado na figura abaixo:

Camadas dos Sistemas Unix
Camadas de um sistema Unix
O tema desse livro é o desenvolvimento em shell script, entretando é interessante analisar essa divisão para sabermos exatamente onde estaremos trabalhando.

1.1.1. Kernel

Um dos princípios básicos de um sistema operacional moderno é o de criar uma camada de abstração do hardware, possibilitando que ao desenvolvedor de aplicações não se preocupar com o funcionamento específico de cada um dos modelos de hardware disponíveis no mercado. A aplicação irá solicitar um recurso do sistema operacional e ele é quem irá instruir o hardware corretamente através dos drivers de dispositivos.

O kernel é o núcleo dos sistemas operacionais UNIX. Ele que é reponsável por gerenciar a memória, gerenciar os processos que estão rodando, acessar os discos, passar informações para os drivers de dispositivos, entre outras coisas. A quantidade de tarefas que o kernel de um sistema executa pode variar de sistema operacional para sistema operacional.

1.1.2. Nível de usuário

Nesse nível é onde os aplicativos do sistema rodam. Nessa camada não é permitido fazer acesso direto ao hardware e nenhuma operação sem a permissão do kernel. É nessa camada que é executado o interpretador de comandos do sistema operacional UNIX, o shell.

1.1.3. Shell

O shell é o interpretador de comandos que recebe instruções do usuário e as executa. É a primeira coisa que o usuário vê quando entra no sistema. Além disso, o shell é uma linguagem de programação completa, permitindo que o usuário automatize tarefas da mesma forma que as executaria na linha de comando.

1.2. Conceitos

Vamos agora conhecer as características que são típicas de um sistema UNIX.

1.2.1. Processos

Todas as aplicações que rodam sob um sistema UNIX terão um ou mais processos. Sempre que executamos um programa na linha de comando, o shell irá enviar uma solicitação para que o kernel do sistema execute esse programa e dê a ele uma identificação de processo (PID – Process IDentification). Esse programa automaticamente irá se transformar num processo ‘filho’ do shell que o executou.

Como o UNIX é um sistema multitarefa, ele permite que vários programas sejam executados ao mesmo tempo na máquina, utilizando uma técnica de scheduling para ‘dividir’ o tempo que a CPU vai utilizar para executar cada um dos programas carregados em memória. Os UNIX possuem mecanismos que permitem que você dê mais prioridade a determinados processos fazendo com que o scheduling dedique uma fatia maior de tempo para essa aplicação. O comando que controla esse recurso é o nice. Para listar os processos rodando no sistema, seus PID s e outras informações adicionais, rode ps -ef.

1.2.2. Usuários e Grupos

O UNIX é um sistema multiusuário. Para gerenciar essa característica do sistema foi criado um mecanismo de contas de login. Cada usuário normalmente possui uma conta para usar o sistema. Quando uma conta de usuário é criada, o sistema atribui um número de identificação a esse usuário (o UID). Além disso, os sistemas UNIX possuem uma conta denominada root que é usada para administração do ambiente. O UID do root é reservado e especial, sempre igual a zero.

Essas contas também são organizadas em grupos. Cada grupo recebe um número que o identifica (o GID). Podem existir grupos vazios, que não possuem nenhum usuário, e grupos com diversos usuários. Cada usuário deve pertencer a um grupo principal e pode participar de outros grupos secundários.

Os usuários dos sistemas UNIX normalmente estão cadastrados no arquivo /etc/passwd. A estrutura desse arquivo é a mostrada abaixo:

$ cat /etc/passwd
root:*:0:0:Administrator:/:/usr/bin/ksh
fulano:*:101:101:Fulano de Tal, RH, 555-1234:/home/fulano:/usr/bin/ksh
$ _

Os campos são separados por ’:’ e organizados da seguinte maneira:

  1. Login – nome da conta do usuário. Deve possuir no máximo 8 caracteres e não deve conter símbolos.
  2. Senha – Senha criptografada do usuário. Em sistemas mais modernos a senha é armazenada criptografada em um arquivo legível apenas pelo administrador do sistema.
  3. UID – Identificação do usuário. Note que o UID do usuário root é 0.
  4. GID – Identificação do grupo principal do usuário.
  5. Descrição – Dados do usuário.
  6. Home – Diretório que será o $HOME do usuário.
  7. Shell – Qual será o shell que o usuário irá usar. Não é necessário que seja um shell, se for especificado outro programa aqui, este será executado logo que o usuário efetuar o login e retornará à tela de solicitação de login imediatamente após o término da aplicação.

Os grupos do sistema estão relacionados no arquivo /etc/group. Esse arquivo tem o seguinte aspecto:

$ cat /etc/group
users::100:fulano, cicrano, beltrano
$ _

O arquivo acima diz que os usuários ’cicrano‘, ’fulano‘ e ’beltrano‘ pertencem ao grupo ’users‘ cujo GID é 100. Note que esses usuário tem o grupo ’users’ como grupo secundário, e não primário. O grupo primário de cada um dos usuários é especificado no arquivo /etc/passwd.

1.2.3. Diretórios, Arquivos e dispositivos

O sistema de arquivos do UNIX possui diretórios, arquivos e dispositivos. Cada um deles possui atributos especiais que informam quais as permissões de acesso ao arquivo, quais os usuários que são donos desses arquivos, tamanho, data de criação e nome do arquivo. Você pode visualizar essas informações com o comando ls -l que terá uma saída como abaixo:

$ ls -l
total 0
-rw-rw-rw-  1  nobody  nobody       0  Dec 5  12:27  arquivo
brw-r--r--  1  root    root    0,   0  Dec 5  12:28  device
drwxrwxrwx  2  nobody  nobody      35  Dec 5  12:27  diretorio
-rwxrwxr-x  1  nobody  nobody       0  Dec 5  12:29  script.sh
$ _

Como você pode observar o comando ls -l mostra o conteúdo do diretório e os atributos dos arquivos organizados em colunas. Na primeira coluna podemos ver o mapa de bits com as permissões dos arquivos. A primeira letra desse mapa de bits indica qual o tipo do arquivo, os outros caracteres são agrupados de três em três indicando as permissões conforme a tabela abaixo:

Tipo Dono Grupo Todos
- - - - - - - - - -
d r w x - - - - - -
- r w - r - - r - -
l r w x r w x r w x

Tabela 1.1: Mapa de bits de permissão.

O dono do arquivo e o grupo ao qual ele pertence estão nas colunas 3 e 4 respectivamente. Cada uma das letras acima informa um determinado tipo de permissão conforme mostra a tabela abaixo:

Permissão Octal Significado
0 - - - Nenhuma operação permitida.
1 - - x Permitido apenas execução.
2 - w - Permitido apenas gravação.
3 - w x Permitido gravação e execução.
4 r - - Permitido apenas leitura.
5 r - x Permitido leitura e execução.
6 r w - Permitido leitura e gravação.
7 r w x Permitido leitura, gravação e execução.

Tabela 1.2: Permissões.

Existem outras permissões interessantes cujas implementações variam de UNIX para UNIX, a tabela abaixo mostra algumas delas. Elas serão explicadas com mais detalhes com os comandos chmod e chown.

Atributo Descrição
s SUID/SGID bit.
t Sticky bit.

Tabela 1.3: Outras permissões.

Além das permissões existe o caractere que informa qual o tipo do arquivo. A tabela abaixo mostra cada uma delas:

Atributo Tipo
- Arquivo normal.
d Diretório.
l Link simbólico.
c Dispositivo de caracter.
b Dispositivo de bloco.
p Filas (named pipes).

Tabela 1.4: Tipos de arquivos.

Além de arquivos e diretórios, que são as estruturas mais básicas de um sistema de arquivos existem outros tipos especiais de arquivos aos quais vamos dar uma atenção especial:

Links

Os links simbólicos, ou soft links, são utilizados para apontar para arquivos localizados em outros subdiretórios. Se o link é removido acidentalmente, o arquivo para o qual ele apontava permanece intacto. Se o arquivo original é removido, o link simbólico irá continuar apontando para o mesmo lugar e será inválidado (muito conhecido como link quebrado).

Além dos soft links existem também os hard links que são arquivos normais que possuem o mesmo ponto de entrada dos arquivos originais. Os arquivos com hard links são identificados pelo número da segunda coluna do comando ls -l (contador de referência) que será diferente de 1.

$ ls -l
total 1
-rw-rw-r--  3  osvaldo  osvaldo   0  Dec 5  15:39  arquivo_original
-rw-rw-r--  3  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_1
-rw-rw-r--  3  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_2
lrwxrwxrwx  1  osvaldo  osvaldo  16  Dec 5  15:39  softlink_1 -> arquivo_original
$ rm hardlink_2
$ ls -l
total 1
-rw-rw-r--  2  osvaldo  osvaldo   0  Dec 5  15:39  arquivo_original
-rw-rw-r--  2  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_1
lrwxrwxrwx  1  osvaldo  osvaldo  16  Dec 5  15:39  softlink_1 -> arquivo_original

Notem que o contador de referência mostra quantos hard links apontam para o mesmo arquivo. Agora vamos remover o arquivo_original.

$ rm arquivo_original
$ ls -l
total 1
-rw-rw-r--  1  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_1
lrwxrwxrwx  1  osvaldo  osvaldo  16  Dec 5  15:39  softlink_1 -> arquivo_original
$ _

Vejam que o hard link continua existindo e com o mesmo conteúdo do arquivo_original, entretanto o soft link continua apontando para o arquivo que foi removido, caracterizando um link quebrado.

Dispositivos

Os arquivos do tipo dispositivo são utilizados para fazer a comunicação com os drivers dos dispositivos físicos da máquina. Quando gravamos ou lemos informações desses arquivos estamos, na verdade, enviando e recebendo informações do driver do dispositivo associado a esse arquivo.

Normalmente, todos os dispositivos ficam no diretório /dev e só são usados diretamente pelo usuário root. Alguns dispositivos, porém, são usados rotineiramente por todos os usuários. O /dev/null, por exemplo, é um dispositivo de caracteres que simplesmente descarta qualquer coisa que seja enviada pra ele. Esse dispositivo é usado para, por exemplo, eliminar saída indesejada de algum comando.

Filas

As filas (conhecidas como named pipes) são arquivos que podem ser abertos para operações de escrita por um processo e para leitura por um ou vários processos. Tudo o que for escrito nesses arquivos pelo processo que o abriu para escrita estará disponível para leitura pelos outros processos.

As filas, como o nome em inglês diz, são funcionalmente equivalentes aos pipes, mas têm uma manifestação no sistema de arquivos que pode ser usada em outros programas (por isso, pipes nomeados).

1.2.4. Sinais

Sinais são uma forma simples de comunicação entre processos, com algumas convenções pré-estabelecidas.

Um sistema UNIX normalmente executa muitos processos ao mesmo tempo. Porém, essa execução não acontece sem interrupções; há eventos no sistema que influenciam o funcionamento deles, e deve haver uma forma de notificar esses processos. Por exemplo, quando o sistema vai ser desligado para manutenção, deve existir uma forma de avisar os processos que eles devem escrever qualquer informação que estejam manipulando para o disco, arrumar seu ambiente e parar de rodar para que a máquina possa ser desligada. É para esse tipo de tarefa que os sinais são usados.

Os sinais mais comuns são o HUP, o TERM, o INT e o KILL. O HUP é enviado para processos para indicar que o usuário que o estava executando saiu do sistema. Normalmente, o procedimento padrão nesse caso é gravar dados no disco e sair, mas a ação depende do que o programa achar apropriado. Se for um processo que deve continuar rodando, esse sinal será ignorado, ou alguma outra ação apropriada será performada. O TERM é um pedido de interrupção do processo, e a forma padrão de agir é o programa se desligar graciosamente. O INT é como o TERM, mas é enviado quando o usuário aperta CTRL+C para interromper a execução do processo. O KILL é uma ordem para matar o processo. Ao contrário do TERM, que é um pedido, o KILL elimina imediatamente o processo, não dando tempo sequer parar que ele grave informações essenciais em disco ou para qualquer outra tarefa.

Quase todos os sinais podem ser interceptados pelo processo, exceto os sinais KILL e o sinal STOP, que são essenciais para garantir a estabilidade do sistema em caso de mau uso por algum usuário. Alguns sinais são normalmente enviados pelo kernel, e não pelo usuário, como o FPE em caso de exceção de ponto flutuante (caso o processo execute uma divisão por zero ou outra operação matemática ilegal), o SEGV (em caso de falha de segmentação – o programa tentou ler uma área de memória que não pertence a ele). Outros sinais são enviados por processos de monitoramento. Por exemplo, o processo que monitora a quantidade de energia do no-break da máquina pode enviar o sinal PWR para todos os processos ao detectar que uma falta de energia está prestes a ocorrer. Há, ainda, dois sinais cujo funcionamento não é especificado nem por convenção: USR1 e USR2, que realizam funções não determinadas e que variam de programa para programa.

Uma lista completa dos sinais disponíveis pode ser encontrado na man page signal(5) (acessível com o comando man 5 signal). Lembre-se que, por motivos óbvios de segurança, os usuários só podem enviar sinais para seus próprios processos. Apenas o usuário root pode enviar sinais para processos de todos os usuários.

1.2.5. Pipes

Pipes são uma forma mais elaborada de comunicação entre processos. Cada processo, ao ser rodado, ganha automaticamente três file descriptors. Eles são stdin, stdout e stderr. Pipes fazem a ligação entre descritores de arquivo de diferentes programas.

Por exemplo, é possível especificar que a saída de um programa vai ser a entrada de outro. Essa operação é muito comum. O seguinte comando vai ler o arquivo dados e enviar seu conteúdo para o o comando grep:

$ cat dados | grep texto

Com o auxílio do shell, foi criado um pipe que une a saída do comando cat (stdout) com a entrada do comando grep (stdin). O descritor stderr é usado para mensagens de erro. Normalmente, a saída de erro vai para o terminal, assim como a saída padrão. Quando chegarmos no capítulo sobre shell, porém, vamos aprender como usar redireção para enviar os dados dessas duas saídas para lugares diferentes.

1.2.6. Códigos de erros

Todo processo, ao ser terminado, retorna um código de erro numérico entre 0 e 255. O código 0 indica, por convenção, execução com sucesso, e qualquer coisa diferente disso indica algum erro ou alguma situação não normal. O código de retorno dos comandos é descrito na seção Return Codes de suas respectivas man pages (que podem se acessadas com o comando man comando), e varia de acordo com a função dos programas. O código de retorno 1, por exemplo, normalmente indica uso incorreto do programa. O código 2 pode indicar que algum arquivo está faltando, etc. Também é razoavelmente comum o uso do código 255 para indicar situações catastróficas de erro.

Alguns sistemas mais antigos têm programas que retornam a quantidade de erros ocorridos. Isso é uma convenção antiga que não deve mais ser usada – o correto é retornar um valor fixo de erro e enviar dados sobre quais erros ocorreram para stderr. Essa convenção foi abolida porque, caso o programa tenha mais de 255 erros, o código poderá voltar para os valores iniciais. Um programa com 256 erros retornaria 0, o padrão de execução com sucesso.

1.2.7. Parâmetros

É comum, quando executamos determinados programas, que tenhamos que passar parâmetros para eles. Por convenção, os parâmetros no UNIX são precedidos do caractere ‘-‘. Programas GNU (que seguem as regras da Free Software Foundation) suportam também uma forma de passagem de parâmetros mais extensos, que são precedidos por ’–‘ como na mensagem apresentada abaixo:

-v, --verbose - Exibe informações detalhadas.
-x, --exchange - Troca os parâmetros.
-h, --help - Exibe esta tela de ajuda.

Existe também uma forma de forçar o UNIX a não mais interpretar os parâmetros desse tipo, para isso basta inserir o ’–‘ sem nenhuma opção a mais. Essa opção é útil quando temos que informar um nome de arquivo começado com ’-‘ como parâmetro de um comando:

$ ls -- -arquivo-
-arquivo-
$ _

Outra coisa importante que devemos explicar aqui é a utilização de globs para a passagem de nomes de arquivos como parâmetros. Na tabela abaixo você poderá ver os globs mais comuns:

Glob Descrição
? Qualquer caracter.
* Quaisquer caracteres.
[] Qualquer caractere da lista.
[!] Qualquer caractere exceto o da lista.
{} Qualquer ocorrência da lista (separada por vírgula).

Tabela 1.5: Globs.

Esses globs podem ser utilizados da seguinte maneira:

$ ls
abbb abcde bbbb cbbb hipermercado mercado supermercado
$ ls [bc]bbb
bbbb cbbb
$ ls {hiper,super,}mercado
hipermercado mercado supermercado
$ ls a???
abbb
$ ls a*
abbb abcde
$ ls [!a]*
bbbb cbbb hipermercado mercado supermercado
$ _

Uma coisa que precisa ser explicada é que o shell é que fica com a responsabilidade de ’traduzir‘ os globs e passá-los como parâmetro para aplicação. Deixando a aplicação livre do trabalho de interpretar os globs e substituí-los pelos nomes dos arquivos.

1.3. Comentários finais

Nesse capítulo aprendemos um pouco sobre a história dos sistemas UNIX, seus fundamentos e sua filosofia, com uma breve introdução ao uso da linha de comando. Esse conhecimento é fundamental para usar efetivamente o sistema. Coisas que podem parecer estranhas, como decorar os números octais equivalentes às permissões, facilitam o trabalho do usuário de sistema UNIX, e esse tipo de conhecimento pode ser assimilado facilmente com a prática e a disciplina de uso do sistema.

Continua…