Shells são como editores: cada pessoa tem um favorito e defende veementemente sua escolha (e diz por que você deveria mudar). É verdade que shells podem oferecer diferentes recursos, mas todas elas implementam ideias principais que foram desenvolvidas há décadas.
Minha primeira experiência com a shell moderna foi nos anos 1980, quando eu estava desenvolvimento software no SunOS. Depois que aprendi o recurso de aplicar a saída de um programa como entrada de outro (até mesmo fazer isso diversas vezes, em cadeia), eu tinha uma maneira simples e eficiente de criar filtros de transformações. A ideia principal proporcionava uma maneira de desenvolver ferramentas simples que eram flexíveis o suficiente para ser aplicadas com outras ferramentas em combinações úteis. Dessa maneira, shells proporcionam não apenas uma maneira de interagir com o kernel e com dispositivos, mas também serviços integrados (como canais e filtros) que são hoje padrões de design comum no desenvolvimento de software.
Vamos começar com uma breve história das shells modernas, e em seguida explorar algumas shells úteis e exóticas para Linux hoje.
Shells—ou interpretadores de linha de comando—têm
uma longa história, mas essa discussão começa com a primeira shell UNIX®. Ken Thompson (da Bell Labs) desenvolveu a primeira shell para Unix chamada shell V6 em 1971. Assim como seu antecessor no Multics, essa shell (/bin/sh) era um programa independente do usuário executado fora do kernel. Conceitos como globbing (reconhecimento de padrões para expansão de parâmetro, como *.txt) foram implementados em um utilitário separado chamado glob, assim como o comando if para avaliar expressões condicionais. Essa separação manteve a shell pequena, com menos de 900 linhas de código C (consulte Recursos para obter um link para a fonte original
A shell introduziu uma sintaxe compacta para redirecionamento (< >
e >>) e canalização (|
ou ^) que sobrevive nas shells modernas. Também existe suporte para chamar comandos sequenciais (com ;)
e comandos assíncronos (com &).
O que falta na shell de Thompson era a capacidade de scripts. Seu único propósito era ser uma shell interativa (interpretadora de comandos) para chamar comandos e visualizar resultados.
Além da shell de Thompson, começamos nosso exame das shells modernas em 1977, quando a Bourne shell foi introduzida. A Bourne shell, criada por Stephen Bourne do AT&T Bell Labs para UNIX V7, continua sendo uma shell útil hoje (em alguns casos a shell raiz padrão). O autor desenvolveu a Bourne shell após trabalhar em um compilador de ALGOL68, portanto, pode-se perceber que sua gramática é mais semelhante à Algorithmic Language (ALGOL) que outras shells. O próprio código fonte, embora desenvolvido em C, usava macros para dar um sabor de ALGOL68.
A Bourne shell tinha dois objetivos primários: servir como um interpretador de comando para executar comandos interativamente para o sistema operacional e para criação de scripts (escrever scripts reutilizáveis que podiam ser chamados por meio da shell). Além de substituir a shell Thompson, a Bourne shell oferecia várias vantagens em relação a suas antecessoras. Bourne introduziu fluxos de controle, loops e variáveis nos scripts, criando uma linguagem mais funcional para interagir com o sistema operacional (interativamente ou não). A shell também permitia usar scripts como filtros, fornecendo suporte integrado para lidar com sinais, mas não tinha a capacidade de definir funções. Por fim, ela incorporava alguns recursos usados hoje, incluindo substituição de comando (usando aspas invertidas) e documentos HERE para integrar literais de cadeia de caracteres preservados a um script.
A Bourne shell foi não apenas um importante passo para frente, mas também a âncora para diversas shells derivadas, muitas das quais são usadas hoje em sistemas Linux típicos.
A Figura 1 ilustra a linhagem de shells importantes. A Bourne shell levou ao desenvolvimento da shell Korn (ksh),
shell Almquist (ash) e da popular Bourne Again Shell (ou Bash). A shell C (csh) estava em desenvolvimento no momento em que a Bourne shell foi lançada. A Figura 1 mostra a principal linhagem mas não todas as influências; houve contribuição significativa entre shells que não é mostrada.
Figura 1. Shells Linux desde 1977
Iremos explorar algumas dessas shells mais tarde, e ver exemplos da linguagem e dos recursos que contribuíram para seu avanço.
A arquitetura fundamental de uma shell hipotética é simples (como mostra a shell de Bourne). Como é possível ver na Figura 2, a arquitetura básica é semelhante a um canal, no qual a entrada é analisada, símbolos são expandidos (usando diversos métodos como expansão e substituição de chave, til, variável e parâmetro, e geração de nome de arquivo), e por fim comandos são executados (usando comandos integrados da shell ou comandos externos).
Figura 2. Arquitetura simples de uma shell hipotética
Na seção Recursos, há links para saber sobre a arquitetura da Bash shell de software livre.
Vamos agora explorar algumas dessas shells parar revisar sua contribuição e examinar um script de exemplo em cada uma. Essa revisão inclui a shell C, shell Korn e Bash.
A shell C foi desenvolvida para sistemas UNIX Berkeley Software Distribution (BSD) por Bill Joy enquanto ele era estudante de graduação da Universidade da Califórnia, Berkeley, em 1978. Cinco anos depois, a shell introduziu funcionalidade do sistema Tenex (popular em sistemas DEC PDP). A Tenex introduziu o nome do arquivo e o comando de conclusão além de recursos de edição de linha de comando. A shell Tenex C (tcsh) continua sendo compatível com versões anteriores da csh, mas melhorou seus recursos interativos gerais. A tcsh foi desenvolvida por Ken Greer na Universidade Carnegie Mellon.
Um dos principais objetivos de design da shell C era criar uma linguagem de script que fosse semelhante à linguagem C. Esse era um recurso útil, dado que C era a linguagem primária em uso (além do sistema operacional ser desenvolvido predominantemente em C).
Um recurso útil introduzido por Bill Joy na shell C foi o histórico de comandos. Esse recurso mantinha um histórico dos comandos executados anteriormente e permitia que o usuário revisasse e selecionasse facilmente comandos anteriores para serem executados. Por exemplo, digitar o comando atom
mostraria os comandos executados anteriormente. As teclas de seta para cima e para baixo podiam ser usadas para selecionar um comando, ou o comando anterior poderia ser executado usando !!. Também é possível referenciar argumentos do comando anterior; por exemplo, !* refere-se a todos os argumentos do comando anterior, enquanto !$ refere-se ao último argumento do comando anterior.
Veja um exemplo curto do script tcsh (Listagem 1). Esse script toma um único argumento (o nome de um diretório) e emite todos os arquivos executáveis nesse diretório, junto com o número de arquivos localizados. Eu reutilizo esse script em cada exemplo para ilustrar as diferenças.
O script tcsh é dividido em três seções básicas. Primeiro, observe que eu uso o símbolo shebang, ou hashbang, para declara esse arquivo como sendo interpretável pelo executável de shell definido (neste caso, o binário tcsh). Isso permite executar o arquivo como um executável regular em vez de precedê-lo com o binário do interpretador. Um contador é mantido dos arquivos executáveis encontrados, portanto eu inicializo esse contador com zero.
Listagem 1. Arquivar todos os scripts de arquivos executáveis em tcsh
#!/bin/tcsh
# find all executables
set count=0
# Test arguments
if ($#argv != 1) then
echo "Usage is $0 <dir>"
exit 1
endif
# Ensure argument is a directory
if (! -d $1) then
echo "$1 is not a directory."
exit 1
endif
# Iterate the directory, emit executable files
foreach filename ($1/*)
if (-x $filename) then
echo $filename
@ count = $count + 1
endif
end
echo
echo "$count executable files found."
exit 0
|
A primeira seção testa os argumentos passados pelo usuário. A variável #argv representa o número de argumentos passados (excluindo o próprio nome do comando). É possível acessar esses argumentos especificando seu índice: por exemplo, #1 refere-se ao primeiro argumento (que é uma versão curta de argv[1]). O script está esperando um argumento. Se não encontrá-lo, emite uma mensagem de erro, usando $0 para indicar o nome do comando que foi digitado no console (argv[0]).
A segunda seção garante que o argumento passado era um diretório. O operador -d retorna Verdadeiro se o argumento for um diretório. Mas observe que eu especifico um símbolo ! primeiro, o que significa negar. Dessa forma, a expressão diz que, se o argumento não for um diretório, deve-se emitir uma mensagem de erro.
A seção final itera os arquivos no diretório para testar se são executáveis. Eu uso o iterador foreach, que faz um loop por cada entrada nos parênteses (nesse caso, o diretório) e testa cada parte do loop. Essa etapa usa o operador -x para testar se o arquivo é executável. Se for, o arquivo é emitido e o contador é aumentado. Eu finalizo o script emitindo a contagem de variáveis.
A shell Korn (ksh), projetada por David Korn, foi introduzida na mesma época que a shell Tenex C. Um dos recursos mais interessantes da shell Korn era usar uma linguagem de script além de ser compatível com a Bourne shell original.
A shell Korn era software proprietário até 2000, quando foi lançada como software livre (sob a Common Public License). Além de fornecer forte compatibilidade com a Bourne shell, a shell Korn inclui recursos de outras shells (como histórico de csh). A shell também fornece vários recursos mais avançados encontrados em linguagens de script modernas, como Ruby e Python — por exemplo, array associativo e aritmética de ponto flutuante. A shell Korn está disponível em vários sistemas operacionais, incluindo IBM® AIX® e HP-UX, e se esforça para suportar o padrão de linguagem de shell Portable Operating System Interface for UNIX (POSIX).
A shell Korn é derivada da Bourne shell e se parece mais com ela e com Bash que a shell C. Vamos examinar um exemplo da shell Korn para localizar executáveis (Listagem 2).
Listagem 2. Localizar todos os scripts de arquivos executáveis no ksh
#!/usr/bin/ksh
# find all executables
count=0
# Test arguments
if [ $# -ne 1 ] ; then
echo "Usage is $0 <dir>"
exit 1
fi
# Ensure argument is a directory
if [ ! -d $1 ] ; then
echo "$1 is not a directory."
exit 1
fi
# Iterate the directory, emit executable files
for filename in $(ls $1/*)
do
if [ -x $filename ] ; then
echo $filename
count=$((count+1))
fi
done
echo
echo "$count executable files found."
exit 0
|
A primeira coisa que você perceberá na Listagem 2 é sua semelhança à Listagem 1.
Estruturalmente, o script é quase idêntico, mas diferenças importantes são evidentes na maneira que condicionais, expressões e iteração são realizadas. Em vez de adotar operadores de teste semelhantes a C, ksh adota os típicos operadores de estilo Bourne (-eq,
-ne, -lt, e assim por diante).
A shell Korn também contém algumas diferenças relacionadas à iteração. Na shell Korn, a estrutura for in é usadas, com substituição de comando para representar a lista de arquivos criada pela saída padrão do comando ls '$1/* representando o conteúdo do subdiretório nomeado.
Além dos recursos definidos acima, Korn suporta o recurso de alias (para substituir uma palavra com uma cadeia de caracteres definida pelo usuário). Korn tem muitos recursos inativos por padrão (como conclusão de nome de arquivo) mas podem ser usados pelo usuário.
A Bourne-Again Shell, ou Bash, é um projeto GNU de software livre que se destina a substituir a Bourne shell. Bash foi desenvolvida por Brian Fox e se tornou uma das shells de maior disponibilidade (aparecendo no Linux, Darwin, Windows®, Cygwin, Novell, Haiku e mais). Como o nome implica, Bash é um superconjunto da Bourne shell, e a maioria dos scripts Bourne pode ser executada sem mudança.
Além de suportar compatibilidade com versões antigas para criação de script, Bash incorporou recursos das shells Korn e C. Você encontrará histórico de comando, edição de linha de comando, uma pilha de diretórios (pushd e popd), muitas variáveis de ambiente úteis, conclusão de comando e mais.
Bash continua evoluindo com novos recursos, suporte para expressões regulares (semelhante ao Perl) e arrays associativos. Embora alguns desses recursos não estejam presentes em outras linguagens de script, é possível criar scripts que são compatíveis com outras linguagens. Para isso, o script de amostra mostrado na Listagem 3 é idêntico ao script da shell Korn (na Listagem 2) exceto pela diferença de shebang (/bin/bash).
Listagem 3. Localizar todos os scripts de arquivos executáveis em Bash
#!/bin/bash
# find all executables
count=0
# Test arguments
if [ $# -ne 1 ] ; then
echo "Usage is $0 <dir>"
exit 1
fi
# Ensure argument is a directory
if [ ! -d $1 ] ; then
echo "$1 is not a directory."
exit 1
fi
# Iterate the directory, emit executable files
for filename in $(ls $1/*)
do
if [ -x $filename ] ; then
echo $filename
count=$((count+1))
fi
done
echo
echo "$count executable files found."
exit 0
|
Uma diferença importante entre essas shells são as licenças sob as quais são liberadas. Bash, como seria de se esperar já que foi desenvolvida pelo projeto GNU, foi lançada sob a GPL, mas csh, tcsh, zsh, ash e scsh foram todas lançadas sob a licença BSD ou semelhante a BSD. A shell Korn está disponível sob a licença Common Public License.
Os mais aventureiros podem usar shells alternativas com base em suas necessidades ou gosto. A shell Scheme (scsh) oferece um ambiente de script usando Scheme (um derivado da linguagem Lisp). Pyshell é uma tentativa de criar um script semelhante que use a linguagem Python. Por fim, para sistemas integrados, há o BusyBox, que incorpora uma shell e todos os comandos em um único binário, para simplificar sua distribuição e gerenciamento.
A Listagem 4 dá um exemplo do script de encontrar todos os executáveis na shell Scheme shell (scsh). Esse script pode parecer estranho, mas implementa funcionalidade semelhante aos scripts fornecidos até agora. Esse script inclui três funções e código executável diretamente (no final) para testar o contador do argumento. O núcleo real do script é a função showfiles, que itera uma lista (desenvolvida após with-cwd), chamando write-ln após cada elemento na lista. Essa lista é gerada iterando o diretório nomeado e filtrando arquivos que sejam executáveis.
Listagem 4. Arquivar todos os scripts de arquivos executáveis em scsh
#!/usr/bin/scsh -s
!#
(define argc
(length command-line-arguments))
(define (write-ln x)
(display x) (newline))
(define (showfiles dir)
(for-each write-ln
(with-cwd dir
(filter file-executable? (directory-files "." #t)))))
(if (not (= argc 1))
(write-ln "Usage is fae.scsh dir")
(showfiles (argv 1)))
|
A maior parte das ideias e da interface das primeiras shells permanece a mesma quase 35 anos depois — um grande tributo aos autores das primeiras shells. Em um segmento de mercado que continuamente se reinventa, a shell foi melhorada mas não modificada substancialmente. Embora tenha havido tentativas de criar shells especializadas, as derivações da Bourne shell continuam a ser as shells primárias em uso.
Aprender
-
A V6 Thompson Shell Port (osh), desenvolvido e mantido por J.A. Neitzel, é um grande recurso para a fonte osh, bem como utilitários de shell externos dos quais depende (como
ifegoto). Também existe um archive de utilitários escritos na shell Thompson além do próprio código fonte original. -
Goosh é a shell não oficial do Google, que implementa uma interface de shell sobre a interface de procura de uso comum do Google. Goosh é um exemplo interessante de como shells podem ser aplicadas a interfaces não tradicionais.
-
A Bourne shell é a âncora da qual derivam nossas shells atuais. Os arquivos de fonte têm um sabor de ALGOL68 que foi obtido com o uso de macros de
C. -
A Bourne-Again Shell é a shell mais usada no Linux, combinando recursos da Bourne shell, shell Korn e shell
C. Para uma boa leitura, saiba mais sobre a estrutura e aspectos internos de Bash no terceiro capítulo de "The Architecture of Open Source Applications". -
Confira artigos adicionais do developerWorks sobre scripts de shell, como o de Daniel Robbins, "Bash by example" Part 1 (março de 2000), Parte 2
(abril de 2000) e Parte 3
(maio de 2000). Também é possível saber mais sobre Scripts em shell Korn (junho de 2008) e variáveis da shell Tcsh (agosto de 2008).
-
No site kornshell, obtenha as mais recentes notícias sobre a shell Korn, incluindo documentação e outros recursos.
-
A Wikipédia inclui uma ótima comparação de shells, incluindo características gerais, recursos interativos, recursos de programação, sintaxe, tipos de dados e mecanismos de IPC.
-
O artigo de Tim, "BusyBox simplifies embedded Linux systems" (developerWorks, agosto de 2006), explora o aplicativo BusyBox e como incluir novos comandos nessa arquitetura de shell estática.
- Na rotina zona Linux do developerWorks, encontre vários artigos de instruções e tutoriais, bem como downloads, fóruns de discussão e muitos outros recursos para desenvolvedores e administradores Linux.
- Fique por dentro dos eventos técnicos e webcasts do developerWorks com foco em uma variedade de produtos IBM e tópicos do segmento de mercado de TI.
- Participe de um briefing gratuito
do developerWorks para se atualizar rapidamente sobre produtos e ferramentas da IBM, bem como tendências do segmento de mercado de TI.
- Acompanhe as demos on demand do developerWorks , que abrangem desde demos de instalação e configuração de produtos para iniciantes até funcionalidades avançadas para desenvolvedores experientes.
- Siga o Tim no Twitter. Também é possível seguir developerWorks no Twitter, ou inscreva-se em um feed de Linux tweets no developerWorks.
Obter produtos e tecnologias
-
Avalie produtos IBM da maneira que for melhor para você: faça download da versão de teste de um produto, avalie um produto on-line, use-o em um ambiente de nuvem ou passe algumas horas na SOA Sandbox aprendendo a implementar Arquitetura Orientada a Serviços de modo eficiente.
Discutir
- Participe da comunidade do My developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

M. Tim Jones é arquiteto de firmware integrado e autor de Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (atualmente em sua segunda edição), AI Application Programming (em sua segunda edição) e BSD Sockets Programming from a Multilanguage Perspective. Seu conhecimento em engenharia varia do desenvolvimento de kernels para naves espaciais geossíncronas até a arquitetura de sistemas embarcados e o desenvolvimento de protocolos de rede. Tim trabalha na Intel e reside em Longmont, Colorado.