Você está na página 1de 12

O Shell e as linguagens de Script

As linguagens de script podem parecer um pouco estranhas para os


programadores de linguagens mais tradicionais, como programadores C ou C+
+. A exemplo, eu cito, falta de tipos distintos para as variáveis, ou mesmo o
fato de estas linguagens usarem algum mecanismo obscuro para
gerenciamento dinâmico de memória que consome mais recursos da máquina
do que os estritamente necessários. Por outro lado se pensarmos que em
linguagens como C e C++ cerca de 40% do tempo de desenvolvimento é gasto
com técnicas de armazenamento e gerenciamento de memória (Hans Boehm.
Advantages and Disadvantages of Conservative Garbage Collection. ) essas
linguagens podem ser utilizadas em projetos cujo desempenho possa ser
“rápido o bastante” e não “o mais rápido possível” ou mesmo em sistemas de
prova de conceito.

O termo script, que provém de mecanismos antigos onde usavam-se arquivos


de texto (estes sim scripts) para gerar entradas de programas iterativos, não
mais faz sentido. Perceba, as linguagens de script modernas como Perl5, TCL
e Python cresceram em capacidade e poder que o termo “linguagem de script”
não passa de mera palavra técnica que não mais expressa a atual capacidade
destas linguagens sendo usado ainda por que não temos um termo melhor para
elas. Hoje elas possuem estruturas de dados complexas, como referências,
hashs ou dicionários e aceitam até mesmo as modernas técnicas de
programação orientada a objetos além de frameworks complexos.

E para sermos programadores ou administradores de sistemas proficientes nos


ambientes Linux o conhecimento de ao menos uma ou duas linguagens de
Scripts e conhecimentos mínimos de C ou C++ fazem-se necessários. Dentre
as linguagens de script, minha preferida é o Perl5, porém hoje vamos falar do
Shell.

O Shell foi uma das, se não a primeira interface de desenvolvimento de scripts


no mundo Unix. Mas com certeza foi a primeira interface capaz de produzir
código interpretado portável (Addison Wesley - The Art of Unix
Programming). Apesar de ser muito utilizada como mera interface de
comando, o Shell é uma linguagem de programação simples e natural. Por não
possuir estruturas de dados complexas o Shell utiliza de forma muito
inteligente outros programas em modo texto (como sort, sed, awk e perl -e),
suprindo assim esta fraqueza.

A primeira versão do netnews era um shellscript com 150 linhas apenas. De


acordo com Steven M. Bellovin, idealizador do programa, a versão em
shellscript era muito lento para produção, mas muito eficiente para provas de
conceito e testes.

O primeiro Shell disponível para Unix foi o Bourne Shell (sh), hoje
substituído em muitos sistemas pelo Bourne Again Shell (bash). Depois do
Bourne Shell muitos outros shells foram desenvolvidos, como o Korn Shell
(ksh) e o C Shell (csh) entre outros.

Como funciona o Shell

Scripts em Shell são excelentes em sistemas onde não podemos assumir a pré
existência de outras linguagens, como Python ou Perl ou mesmo em scripts
que rodam durante o start-up do sistemas operacional para levantar os serviços
necessários e fazer as configurações dos usuários. Também são uma ótima
pedida em situações simples para administradores, instaladores e como
ferramenta didática para programadores iniciantes. Sistemas Shell mais
complexos podem arriscar sua portabilidade, pois existem diferenças de
implementação entre os shells existentes (Bash, Korn, C) além da
possibilidade da não existência de um determinado comando no sistema que
executará sua aplicação.

É muito importante saber operar o Shell no mundo Unix e Linux. No mundo


Linux o shell mais popular é o Bash. No mundo Unix o Ksh ainda tem muita
importância. Historicamente os principais Shells são: Bourn Shell ou sh; C
Shell ou csh; Korn Shell ou ksh; Bourn Again Shell ou bash;

Inicialmente os sistemas Unix usavam o sh como linguagem de programação


e interpretação de comandos. O sh introduziu a possibilidade de declaração de
variáveis e estruturas de decisão em um interpretador de comandos.
Posteriormente foi desenvolvido o csh, cuja a estrutura de programação era
semelhante ao C, muito utilizado no desenvolvimento Unix até os dias de
hoje.

Exemplos de código em sh e csh para comparação:


#!/bin/sh
  if [ $days -gt 365 ]
  then
     echo This is over a year.
  fi
  #!/bin/csh
  if ( $days > 365 ) then
     echo This is over a year.
  endif
  #!/bin/sh
  i=2
  j=1
  while [ $j -le 10 ]; do
     echo '2 **' $j = $i
     i=`expr $i '*' 2`
     j=`expr $j + 1`
  done
  #!/bin/csh
  set i = 2
  set j = 1
  while ( $j <= 10 )
     echo '2 **' $j = $i
     @ i *= 2
     @ j++
  end

O ksh surgiu de uma implementação onde David Korn tentou unir as


qualidades do csh e do sh em um único shell além de adicionar aritmética de
ponto flutuante e estrutura de arrays associativos, tornando-se muito popular
em sistemas Unix como Solaris, True64 e AIX.

O bash surgiu como uma implementação mais atual do Bourn Shell, com


aritmética em diversas bases, arrays de tamanho ilimitado, funções e diversas
outras facilidades ao programador/administrador de sistemas.

Você pode aprender mais sobre o bash em http://www.gnu.org/software/bash/


e sobre o ksh em http://www.kornshell.com/.

Todo shell moderno deve atender ao padrão POSIX 1003.2 "Shell and


Utilities Language Committee". As principais características dos modernos
Shells são:

 Controle de Jobs;
 Funções;
 Alias;
 Redirecionamento;
 Histórico de comandos;
 Editor de comandos;
 Auto-Completar;

O Bash, como hell padrão do projeto GNU atende a todos estes requisitos do
padrão Posix e as qualidades de um shell moderno listadas acima.

Usando o shell em sistemas Linux

Apesar do Bash ser o shell padrão do Linux, diversos outros podem existir na
instalação. O shell padrão de cada usuário é definido no arquivo /etc/passwd:
$ cat /etc/passwd
  root:x:0:0:root:/root:/bin/bash
  bin:x:1:1:bin:/bin:/sbin/nologin
  daemon:x:2:2:daemon:/sbin:/sbin/nologin
  thiago:x:500:500::/home/thiago:/bin/bash
  $

E também pode ser visto na variável de ambiente SHELL:


$ echo "Minha shell eh: $SHELL"
  Minha shell eh: /bin/bash
  $

Se você, por alguma razão, preferir outro shell ao bash pode alterar o shell
padrão do seu usuário com o comando chsh:
$chsh thiago
  Alterando o interpretador de comandos para o usu��ário thiago.
  Senha:
  Novo interpretador de comandos [/bin/bash]: /bin/ksh
  Interpretador de comandos alterado.

Depois saia do shell com o comando logout e log no sistema novamente. Veja
o seu novo shell:
$ echo $SHELL
  /bin/ksh
  $

E no arquivo /etc/passwd
$ grep thiago /etc/passwd
  thiago:x:500:500::/home/thiago:/bin/ksh
  $

Arquivos de Configuração do BASH:

Os arquivos de configuração do bash definem os alias, atalhos, prompt e


variáveis de ambientes da sua sessão dentre outras coisas. Estes arquivos são
divididos em globais, que influenciam as sessões de todos os usuários e os
arquivos locais que alteram apenas o seu usuário. Os arquivos globais
são /etc/profile e /etc/bashrc. Os arquivos locais são $HOME/.profile;
$HOME/.bash_login; $HOME/.bashrc; $HOME/.bash_profile;
$HOME/.bash_logout;

Exceção feita ao arquivo local $HOME/.bash_logout, os outros arquivos de


configuração local são equivalentes e não irão necessariamente coexistir,
bastando apenas um dos arquivos de configuração local. O arquivo que estará
na sua pasta home dependerá da distribuição. As configurações feitas nos
arquivos locais sobrescrevem as configurações locais. Quando você utilizar
um shell que não seja de login, apenas os arquivos locais serão lidos para
configurar este shell e o $HOME/.bahs_logout não é executado ao sair do
shell.

Nota: $HOME e ~ são formas de referenciar o diretório home do usuário


atual, no caso /home/thiago

Ordem de leitura dos arquivos:

1. Ao carregar um shell de login o Linux carrega inicialmente o arquivo


/etc/bashrc ou /etc/profile.
2. Depois o sistema procura executar o script $HOME/.bash_profile.
3. Se ele não existir o sistema irá procurar pelo $HOME/.bash_login.
4. Se ele não existir o sistema irá procurar pelo $HOME/.profile.
5. O sistema executa $HOME/.bashrc se houver.
6. O shell está pronto para interagir com o usuário e apresenta o prompt.
7. Quando o usuário sair do shell o script $HOME/.bash_logout é
executado.

Executando comandos no Bash

No bash a estrutura básica de um comando é:


comando [opções] [argumentos]

Exemplo:
$ ls -la $HOME
  total 40
  drwx------ 3 thiago thiago 4096 Mai  4 11:28 .
  drwxr-xr-x 4 root root 4096 Mai  3 16:15 ..
  -rw------- 1 thiago thiago 2155 Mai  4 13:02 .bash_history
  -rw-r--r-- 1 thiago thiago   33 Mai  3 16:15 .bash_logout
  -rw-r--r-- 1 thiago thiago  176 Mai  3 16:15 .bash_profile
  -rw-r--r-- 1 thiago thiago  124 Mai  3 16:15 .bashrc
  drwxr-xr-x 4 thiago thiago 4096 Mai  3 16:15 .mozilla
  -rw------- 1 thiago thiago  134 Mai  4 13:02 .sh_history
  -rw-r--r-- 1 thiago thiago  658 Mai  3 16:15 .zshrc
    
  $ cd /etc
  $ pwd
  /etc
  $
Quando você precisar executar uma sequência muito grande de comandos
podemos utilizar o caractere \ para fazer quebra de linha e facilitar a leitura da
linha ou script:
# ls -lR /etc   > | wc -l
  2570
  #

Outra facilidade importante do Bash é sua capacidade de autocompletar


comandos com a tecla <tab>. Quando você iniciar a digitação de um comando
e não tiver certeza do nome completo do comando basta digitar tab duas vezes
e ver as possibilidades:
$ ca <tab><tab>
  cadaver             cancel              captoinfo
  cal                 cancel.cups         case
  caller              capifax             cat
  callgrind_annotate  capifaxrcvd         catchsegv
  callgrind_control   capiinfo
  $ca

Se houver apenas uma possibilidade de comando, ela irá aparecer na tela após
o tab:
$ calle <tab>
  $ caller

Caracteres Especiais do Shell

Para atender sua necessidade de ser tanto um interpretador de comandos


quanto uma linguagem de programação, o Bash possuí diversos caracteres
especiais:

Caractere Finalidade/Significado Exemplo


~ Home do usuário atual cd ~ ls -la ~
Escape do caractere seguinte: touch my\*file myfile
/ Separação de Diretórios cd /etc/apache2 ls /va
$ Variável echo $PATH
? Existe um caractere qualquer ls my?file
' Cota absoluta echo 'não substitui: $
` Executa comando/Substituição NOW=`date` ; echo $
“ Cota dupla echo “Este é o PATH
* Qualquer caractere, nenhuma ou várias vezes ls m*file
& Envia processo para o background xeyes&
Operador AND curto-circuíto: se cmd1 executar com sucesso
&& echo 1 && echo 2 fak
executa o segundo, senão encerra
| Pipe: Redireciona a saída de um comando para outro comando ls ~ | xargs wc
Operador OR curto-circuíto: Se primeiro comando com sucesso
|| echo 1 || echo 2 fake |
não executa o segundo comando
cmd1; cmd2; cmd3...
; Executa os comandos em sequência
echo “O home é: “; ec
[] Range de caracteres $ touch file1 file2 file
> Redireciona saída Cmd > out_file.txt
< Redireciona entrada Cmd < in_file.txt

Valor retornado por comandos

Como as funções em linguagens de programação, os comandos do


GNU/Linux retornam um valor para o Shell. O valor retornado para o Shell
indica o status da execução. O valor de retorno zero indica que o comando foi
executado com sucesso. Qualquer outro valor indica um erro na execução do
comando. O valor retornado fica armazenado na variável de ambiente $?.

exemplo:
$ echo "Usuario:home_dir:login_shell";grep thiago /etc/passwd
  |cut -d: -f1,6-7
  && echo "estatus do comando: $?"
  Usuario:home_dir:login_shell
  thiago:/home/thiago:/bin/bash
  estatus do comando: 0
  $

Perceba: uma vez que a execução do comando anterior teve sucesso o valor de
$? foi setado para zero. Caso eu utilize um comando incorreto, inexistente ou
que eu não tenho permissão $? receberá um valor diferente de 0.

exemplo:
$ comando_inexistente ; echo "Erro $?"
  -bash: comando_inexistente: command not found
  Erro 127
    
  $ rm / || echo "Erro. $?"
  rm: cannot remove directory `/': �� um diretório
  Erro. 1
    
  $ rm -rf /root || echo "Erro: $?"
  rm: cannot chdir from `.' to `/root': Permissão negada
  Erro: 1

Executando diversos comando na mesma linha

Existem várias maneiras de executar mais de um comando na mesma linha.


Existem as maneiras condicionais e não condicionais.

 ponto-e-vírgula (;): quando colocamos diversos comandos em uma


linha separados por ponto e vírgula eles são executados em sequência:
$ echo "cria diversos arquivos: ";touch file1 file2 file3; ls file*
  cria diversos arquivos:
  file1  file2  file3  file4
  $

 or curto circuíto ( || ): se o o primeiro comando não é executado com


sucesso, então o shell tenta executar comando seguinte:
$ rm -f /etc/cron.d || echo "ATENCAO: Nao pude remover arquivo. Erro $?"
  rm: impos��vel remover `/etc/cron.d': Permiss��o negada
  ATENCAO: Nao pude remover arquivo. Erro 1
    
  $ rm -f /etc/cron.d || mail x8ge -s "Nao pude remover arquivo. Erro
$?"
    
  $ ls $HOME || echo "Nao foi possivel ler os arquivos: $?"
  file1  file2  file3  file4
  $

 and curto circuíto ( && ): se o primeiro comando é executado com


sucesso, então o shell tenta executar o segundo comando.
$ ls $HOME && echo "arquivos lidos em $HOME"
  file1  file2  file3  file4
  arquivos lidos em /home/x8ge
    
  $ ls ~root && echo "arquivos lidos em ~root"
  ls: /root: Permiss��o negada
  $

 Substituição de Comandos: Você pode atribuir um comando a uma


variável e utilizar esta variável em linhas de comando e scripts:
$ ESTRUTURA=`mkdir -p $HOME/myapp/bin $HOME/myapp/conf
$HOME/myapp/help`
  $ echo "Criando estrutura de arquivos"; $ESTRUTURA ||
     echo "WARNING: NAO FOI POSS'IVEL CRIAR ESTRUTURA DE DIRETORIOS
     PARA MYAPP $?"; ls -R myapp/
  Criando estrutura de arquivos
  myapp/:
  bin  conf  help
    
  myapp/bin:
    
  myapp/conf:
    
  myapp/help:
  $

Neste exemplo criamos uma variável, ESTRUTURA, que na verdade contém


o comando `mkdir -p $HOME/myapp/bin $HOME/myapp/help
$HOME/myapp/conf`. A substituição de comandos também pode ser
utilizada para gerar argumentos para outro comando:
$ wc -c $(ls *.pl)
       117 pingador.pl
       160 usa_ponto.pl
       277 total
  $

Neste exemplo você pode ver o comando pode ser executado dentro de $( ),
em $( ls *.pl ). Da mesma maneira que fosse uma variável, mas o valor não é
atribuído a nenhuma variável real.

O Histórico de comandos do Shell

O histórico do Gnu/Linux é um arquivo que contém os últimos comandos


digitados pelo usuário. Entre suas finalidades podemos citar: Analisar as
últimas ações do usuário; Executar comandos repetitivos; Executar comando
que tenham pequena variação na lista de argumentos. Por exemplo, eu preciso
verificar os arquivos de uma estrutura de diretório que eu não estou
familiarizado. Então irei executar o comando:
$ ls /var
agentx   cache  games  local  log   opt  spool  www
backups  crash  lib    lock   mail  run  tmp

Agora que eu sei quais os subdiretórios diretamente abaixo, basta clicar a seta
para cima que o shell vai colocar o comando anterior ls /var na linha de
comando e eu só preciso digitar a pasta que quero listar, abaixo de /var. Assim
eu só vou digitar /www pois ls /var foi posto no meu prompt pelo histórico:
$ ls /var/www
cgi-bin  htdocs      kumera-0.3.tar.gz  library  news       tools
data     index.html  lib                media    teste.asp

Outro atalho interessante é fornecido pela característica de substituição rápida:


^string_anterior^nova_string. Com está técnica eu substituo uma string do
comando anterior por uma nova string e executo novamente o código. Por
exemplo eu uso o comando ls para verificar o tamanho de um arquivo
qualquer, como /var/www/index.html:
# ls -l /var/www/index.html
-rw-r--r-- 1 root root 2070 2011-03-09 02:17 /var/www/index.html

E decido compactá-lo. basta substituir ls -l por gzip:


# ^ls -l^gzip
gzip /var/www/index.html

Isto se torna extremamente útil quando estamos lidando com comandos muito
extensos que possuem diversos pipes, argumentos muito longos, etc... e
iremos executar comandos diferentes com argumentos repetidos ou
argumentos diferentes com o mesmo comando.

Os comandos digitados pelo usuário ficam armazenados no arquivo


$HOME/.bahs_history
$ cat .bash_history | nl | tail
   466    modprobe -lt net
   467    locate modules.dep
   468    cat /lib/modules/2.6.32-31-generic/modules.dep
   469    lsmod
   470    lsmod | sort
   471    grep vboxnetadp /lib/modules/2.6.32-31-generic/modules.dep
   472    grep vboxdrv /lib/modules/2.6.32-31-generic/modules.dep
   473    modprob -l
   474    modprobe -l
   475    wc -l $(modprobe -l)
   476    modprobe -l | wc -l

Configuração do Histórico

As principais configurações do histórico podem ser vistas e alteradas nas


variáveis de ambiente HISTSIZE, HISTFILE e HISTCMD. HISTSIZE
contém o tamanho do buffer de comandos do history, ou seja, quantos
comandos serão armazenados pelo HISTFILE. HISTFILE contém o path do
arquivo que armazena o histórico. E HISTCMD contém o número do próximo
comando do histórico:
$ echo $HISTSIZE
500
$ echo $HISTFILE
/home/thiago/.bash_history
$ echo $HISTCMD
511

Apesar de HISTSIZE estar configurada para 500 e HISTCMD marcar 511 o


meu arquivo de histórico contém apenas 500 comandos, mas seu número
inicial não está mais em 1. Você pode ver a lista de todos os comandos do
histórico com o comando history:
$ history
   14  ls -R /etc/apache2
   15  ls -Rd /etc | wc -l
   16  ls -d /etc
   17  ls -R /etc | wc -l
   18  sudo ls -R /etc | wc -l
   19  sudo ls -R /etc | wc -l
   20  sudo ls -Ra /etc | wc -l
   21  sudo ls -Ra /etc
   22  sudo ls -Rla /etc
   23  echo `date`
... saída omitida.

O buffer de comandos do histórico pode ser limpo com o comando history -c


$ history -c
$ history
   15  history

Nota: As variáveis do histórico estão definidas nos arquivos de configuração


do seu shell.

Atalhos interessantes do History.

O history nos fornece atalhos muito interessantes, veja:


!! => executa o último comando digitado
!n => executa o comando de número n, exemplo:
$ history
   15  history
   16  grep HIST* .profile
   17  ls .profile
   18  cat .profile
   19  cat .bashrc #vamos usar o atalho para executar este comando.
   20  history
 
$ !19
cat .bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-
doc)
# for examples
...saída omitida
 
!string => executa o comando mais recente que inicia com “string”
 
!?string => executa o comando mais recente que contém “string”

Para obter mais informações sobre o history do Linux:


comandos:
man history
man fc ou info fc
man bash

Você também pode gostar