Evolução de shells no Linux

De Bourne a Bash, e além

Apontar e clicar é útil para a maioria das tarefas de computação do dia a dia, mas para realmente aproveitar a força do Linux em relação a outros ambientes, é preciso eventualmente chegar na shell e entrar na linha de comandos. Várias shells de comando estão disponíveis, de Bash e Korn a shell em C e várias shells exóticas e estranhas. Saiba qual shell é a melhor para você.

M. Tim Jones, Platform Architect, Intel

author photo - M. Tim JonesM. 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.



22/Dez/2011

Entre em contato com o Tim

Tim é um dos nossos autores mais populares e prolíficos. Navegue por todos os artigos do Tim no developerWorks. Confira o perfil dele e comunique-se com ele, outros autores e colegas desenvolvedores na comunidade do developerWorks.

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.

Uma história das shells

Shells como pequenas linguagens

Shells são linguagens especializadas e de domínio específico (pequenas linguagens) que implementam um modelo de uso específico — nesse caso, fornecer uma interface com um sistema operacional. Além de shells de sistema operacional baseadas em texto, é possível encontrar shells para interface gráfica com o usuário e shells para linguagens (como Python shell ou a irb do Ruby). A ideia de shell também foi aplicada à navegação na web por meio de um frontend da web chamado goosh. Essa shell pelo Google permite procura na linha de comando com o Google, usando comandos como search, more e go.

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.


Shells UNIX desde 1977

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
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.


Arquitetura básica de shell

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
Arquitetura simples de uma shell hipotética

Na seção Recursos, há links para saber sobre a arquitetura da Bash shell de software livre.


Explorando as shells Linux

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 Tenex

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.

Shell Korn

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

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.


Shells exóticas

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)))

Conclusão

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.

Recursos

Aprender

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.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=781938
ArticleTitle=Evolução de shells no Linux
publish-date=12222011