Shell (bash)

Iniciar uma shell

Quando se faz login numa máquina UNIX, o processo de login executa automaticamente uma shell. A informação sobre a shell a ser executada está guardada na entrada relativa ao utilizador, existente no ficheiro /etc/passwd.

O último campo de cada linha do /etc/passwd específica qual é o programa executado quando um determinado utilizador faz login. Esse programa é habitualmente uma shell, mas tal não é obrigatório, podendo ser um programa qualquer (ex. leitor de email, programa de monitorização, etc.).

Ao ser iniciada em modo de login, a bash executa o script de arranque do sistema (/etc/profile), e o primeiro script de arranque que existente na home do utilizador (~/.bash_profile ou se não existir o ~/.bash_login ou se não existir o ~/.profile). De igual modo ao ser feito o logout da shell é executado o script de logout (~/.bash_logout).

Outros tipos de shell

Existem vários tipos de shell:

É possível a partir duma shell, arrancar uma nova shell(sub-shell), executando o comando a partir da linha de comando. Ao sair da nova shell, torna-se à shell anterior (como pode ser visto no exemplo seguinte). Para sair de uma shell podem ser utilizados os comandos logout, exit ou ctrl-D (End of File), dependendo do tipo de shell que estamos a utilizar.

bash$ sh
$ csh
% tcsh
> exit
%
$
bash$

Para evitar a sobreposição de shells, existe o comando exec, que nos permite substituir o processo corrente (shell) por um novo processo. Ao fazer logout da nova shell, é efectuada uma mudança para o nível que chamou a shell original (normalmente o processo de login).

bash$ exec tcsh
> exit
user logout
username:

Fazendo o parsing à linha de comando

A primeira coisa que a shell faz quando o utilizador insere um comando, é o parsing da linha de comando. Isso significa separar em componentes e também substituir alguns caracteres especiais que existam nessa linha de comando.

A tabela seguinte apresenta os caracteres especiais reconhecidos pela shell, e que são explicados mais à frente.

Caracteres Significado
caracteres de espaçamento Os caracteres de espaçamento (tabs, espaços) são utilizados para separar argumentos. O espaçamento múltiplo é ignorado
nova linha Utilizado para indicar o fim de uma linha de comando
'   "   \ Caracteres de citação (quotes), que alteram o modo como a shell interpreta os caracteres especiais
& Utilizado no final de um comando, diz à shell para correr o comando em background
<   >   >>   <<   `   | Caracteres de redireccionamento de I/O
*   ?   [   ]   [^ Caracteres de substituição de nomes de ficheiros
$ Carácter de identificação de nome de variável
; Utilizado para separar múltiplos comandos numa única linha

Argumentos

O primeiro passo da shell é separar a linha de comandos em argumentos. O comando apresentado no exemplo, é separado em argumentos e todos os espaços extra são ignorados.

bash$ echo hello        world !!!!
hello world !!!!

A shell separou os argumentos da seguinte forma:

     comando: echo
argumentos 1: hello
           2: world 
           3: !!!!

E como o comando echo apresenta os vários argumentos separados por um espaço, os espaços extra são ignorados

Um comando por linha

Normalmente a shell só aceita um comando por linha. No entanto este comportamento pode ser alterado, inserindo um ; a separar os diversos comandos, como é demonstrado no seguinte exemplo.

bash$ ls ; cd /etc ; ls

Comandos em background

Enquanto a shell está a executar um comando, não é apresentada a nova prompt, tendo o utilizador esperar que o comando acabe para poder ser inserido um novo comando. Este comportamento pode ser alterado, acrescentado um & no final do comando. Neste caso a prompt é apresentada de imediato, ficando o comando a ser executado em "fundo". No entanto todo o output do comando executado continua a ser enviado para a shell, podendo ser complicado perceber o que estamos a escrever.

bash$ sleep 10
bash$ sleep 10 &

Substituição de nomes de ficheiros

A maior parte dos comandos UNIX, manipulam ficheiros. Para facilitar a manipulação de um número grande de ficheiros por um único comando, a shell reconhece um determinado número de caracteres (wildcards), que substitui por nomes de ficheiros.

O primeiro carácter é o *, que representa 0 ou mais caracteres num nome dum ficheiro.

bash$ ls -la *.html

Este comando vai fazer uma listagem longa de todos os ficheiros cujo nome acaba em .html, o * pode ser utilizado em qualquer parte do nome, e mais que uma vez. Por exemplo a*.html representa todos os ficheiro que começa com a e acabam em .html, e *abc* representa todos os ficheiros que tenham abc no nome.

Caracteres Significado
* 0 ou mais caracteres
? 1 carácter
 [   ] 1 carácter dos que entre parêntesis rectos
[^   ] 1 carácter que não seja um dos que estão entre parêntesis rectos

Exemplos:

bash$ ls -l *
bash$ ls -l a*bc
bash$ ls -l a?bc
bash$ ls -l [ic]???
bash$ ls -l [^a-z]?

Remoção do significado especial

A tabela seguinte apresenta uma breve descrição das várias alternativas para retirar significado especial aos caracteres especiais.

Caracteres Significado
' que indica à shell para ignorar todos os caracteres especiais até à próxima '
" que indica à shell para ignorar todos os caracteres especiais excepto $, ` e \ até à próxima "
 \ retira o significado especial ao carácter imediatamente seguinte inclusive o final da linha, permitindo escrever um comando em várias linhas

Exemplos:

bash$ echo 'hello           world !!!!'
hello          world !!!!
bash$ echo \*
*
bash$ echo files = \; ls)
files = ; ls
bash$ echo linha 1 \
> linha 2 \
> linha 3
linha 1 linha 2 linha 3

Redireccionamento de Input/Output

Quando a shell executa um comando, cria automaticamente 3 descritores de ficheiros para o novo processo. A tabela seguinte apresenta esses descritores (0,1 e 2) o seu significado e destino normal.

Nome Descritor do Ficheiro Destino por defeito
standard input (stdin) 0 Teclado
standard output (stdout) 1 Ecrã
standard error (stderr) 2

Ecrã

Por omissão todos os dados fornecidos a um comando, são fornecidos através do stdin, todos os resultados são apresentados no stdout e os erros são apresentados no stderr.

Existem vários tipos de redireccionamento para os vários standards, que são apresentados na tabela seguinte

Caracteres Resultado
comando < ficheiro

lê o input (stdin) do comando do ficheiro

comando > ficheiro envia o output (stdout) para o ficheiro, apagando qualquer coisa que exista no ficheiro
comando >> ficheiro envia o output (stdout) para o ficheiro, acrescentando aos dados que já possam existir no ficheiro
comando << etiqueta lê o input (stdin) até encontrar uma linha que contenha a etiqueta
`comando` executa o comando e substitui `comando` com o resultado do comando
comando1 | comando2 | ... passa o output do comando 1 para o input do comando 2, e assim sucessivamente
comando 2> ficheiro envia os erros (stderr) para o ficheiro. O 2 pode ser alterado por qualquer um dos identificadores de ficheiros
comando >& descritor_ficheiro envia o output (stdout) para o descritor_ficheiro (o próprio número)

No entanto existem vários comandos que não utilizam o stdin e outros que não produzem dados para o stdout. Por exemplo fazer ls | cd dá um erro de "broken pipe", pois o comando cd não recebe dados do stdin. De igual modo não faz sentido executar o comando cd /etc | ls pois o comando cd não produz dados para o stdout, devendo-se utilizar o comando cd /etc ; ls.

Exemplos:

bash$ ls > ficheiros.lst
bash$ ls /dir_nao_existente 2> /dev/null
bash$ ls /etc >> ficheiros.lst
bash$ echo numero de linhas em ficheiros.lst = `wc -l ficheiros.lst`
numero de linhas em ficheiros.lst = 30
bash$ cat << final > texto

Quando se deseja fazer o redireccionamento do stdout e do stdin para o mesmo ficheiro, deve-se utilizar o redireccionamento com os descritores de ficheiros, tomando em atenção à ordem com que tal se faz. O redireccionamento do stderr é útil, quando corremos um comando em background e não queremos que ele apresente dados para o ecrã, misturando-os com os comandos que nós estamos a escrever ou executar.

bash$ ls ficheiro.lst xx
ls: xx: No such file or directory
ficheiro.lst
bash$ ls ficheiro.lst xx >& 2 2> erros
ficheiro.lst
bash$ ls ficheiro.lst xx 2> erros >& 2
bash$ ls ficheiro.lst xx > /dev/null 2>&1

Tudo é um ficheiro

Uma das características dos sistemas UNIX, é que quase tudo pode ser tratado como um ficheiro. Isto combinado com o redireccionamento de I/O permite fazer algumas coisas interessantes.

Por exemplo o comando tty, apresenta o nome do device de output do terminal que estamos a utilizar (ex. /dev/ttyp1). Assim é possível apresentar os dados no ecrã de várias maneiras:

bash$ tty
/dev/ttyp1
bash$ ls ficheiro.lst
ficheiro.lst
bash$ ls ficheiro.lst > /dev/ttyp2
ficheiro.lst
bash$ ls ficheiro.lst > `tty`
ficheiro.lst

Podemos ainda utilizar o device /dev/audio e enviar para lá directamente um ficheiro de áudio.

bash$ cp musica.au > /dev/audio

Há no entanto alguns devices que não devem ser escritos directamente, sob o risco de serem corrompidos. Entres estes devices estão os discos.

Variáveis da shell

A shell fornece um mecanismo de variáveis para armazenamentos de informação para uso futuro. As variáveis da shell são utilizadas para dois propósitos:

O comando set permite fazer a alteração do comportamento da shell, e se utilizado sem argumentos apresenta uma listagem de todas as variáveis definidas na shell.

O comando unset nome_var permite eliminar uma variável, não podendo no entanto algumas das variáveis de ambiente ser eliminadas.

O comando export nome_var permite exportar as variáveis de para as novas shells executadas. Sem o comando export, uma variável criada numa shell, não existirá num nova shell. No entanto se o valor da variável for alterado na nova shell, esse valor não é alterado na shell onde a variável foi definida.

O comando readonly nome_var permite tornar uma variável só de leitura, não podendo o seu valor ser alterado. Se for executado sem parâmetros, apresenta a listagem de variáveis só de leitura existentes.

Podem ser efectuadas várias operações sobres as variáveis, nomeadamente:

bash$ comando=ls
bash$ $comando
bash$ comando=
bash$ num_linha=`wc -l ficheiro.txt`

Para aceder ao valor e características das variáveis podem ser utilizada uma das várias sintaxes apresentadas na seguinte página.

Aritmética

A shell não compreende directamente aritmética, não podendo ser atribuída directamente a uma variável a soma de dois valores. Para realizar aritmética podem-se utilizar os comando expr e bc ou utilizar as capacidade aritméticas da bash através do comando $[ ]. No entanto só o comando bc permite a aritmética com números reais. Quando utilizamos o comando expr todos os operandos e operadores aritméticos têm de ser separados por espaços e aos caracteres especiais tem de ser retirado o significado especial.

bash$ res=`expr $valor + 2 \* 3`
bash$ res=`echo "$valor+2*3"|bc`
bash$ res=$[ 5 + 5 ]

De todas estas maneiras de efectuar aritmética, a mais eficiente é a 3ª, pois não necessita de criar um processo novo, seguida da 1ª, que só cria 1 processo para o comando expr. O comando bc, como necessita de receber os dados do stdin, necessita criar 2 processos novos, 1 para o echo e outro para o próprio bc, demorando bastante mais tempo a executar. Isto pode ser bastante significativo em scripts que efectuem muitos cálculos aritméticos, podendo fazer bastante diferença a utilização de um ou outro método.

Para calcular o tempo de execução de um comando pode ser utilizado o comando time, seguido do comando a executar ex. time echo "$valor+2*3"|bc

Nesta página existem vários modos de utilizar as potencialidades aritméticas da bash, nomeadamente o comando built-in let.

Ordem de avaliação do comando

Sempre que é executado um comando, a shell segue uma determinada ordem, no tratamento dos caracteres especiais existentes nesse comando:

Esta ordem é importante, pois se tivermos dentro de uma variável carácter de redireccionamento (|, >, <, etc.), este não vai ser interpretado pela shell como tal, pois a variável é substituída depois de ser efectuado o redireccionamento. Mas se no valor da variável estiver um carácter de substituição de nome de ficheiros (*, ?, etc.), a shell já o vai interpretar como tal.

É no entanto possível utilizar o comando eval para dizer a shell para avaliar 2 vezes o mesmo comando, isso é útil quando queremos fazer o redireccionamento utilizando variáveis, ou ter nomes de variáveis guardados em outras variáveis.

bash$ pipe=\|
bash$ echo $pipe
|
bash$ star=\*
bash$ echo $star
fich.txt output.dat passwd texto
bash$ ls $pipe more
ls: |: No such file or directory
ls: more: No such file or directory
bash$ eval ls $pipe more
fich.txt
output.dat
passwd
texto

Comandos Built-in da shell

Muitos dos comandos aqui utilizados estão incluídos no código da própria shell, ex. cd, set, unset, eval, export, readonly, time, etc.. Para obter uma listagem da sintaxe saber os comandos existentes e se não quiserem utilizar o comando man bash, que apresenta outra informação da shell. Se na linha de comando executarem o comando help, é apresentada a lista dos comandos incluídos na shell. Podem também executar o comando help *set para apresentar a ajuda de todos os comando built-in terminados em set.

Nesta página são apresentados os comandos built-in da bash

Ultima alteração: terça-feira, 01 de Abril de 2003 às 16:07