Falando sobre o UNIX: Linguagem de Shell e de Script portável do Squirrel

Grave scripts de shell orientado a objeto para várias plataformas

Se você não quiser ficar ligado só a um shell específico executado em uma determinada plataforma, tente o Squirrel Shell. O Squirrel Shell fornece uma linguagem de script avançada orientada a objeto que funciona igualmente bem nos sistemas UNIX, ®, Linux®, Mac OS X™, e Windows®. Grave um script uma vez e execute-o em qualquer lugar.

Martin Streicher , Software Developer, 自由职业者

Photo of Martin StreicherMartin Streicher é chefe executivo em tecnologia da McClatchy Interactive, editor chefe da Linux Magazine, um desenvolvedor da Web e um colaborador regular do developerWorks. Ele possui mestrado em ciência da computação pela Purdue University e programa sistemas semelhantes ao UNIX desde 1986.


nível de autor Contribuidor do
        developerWorks

17/Mar/2009

Em 1799, um engenheiro do exército francês fez uma grande descoberta. E não era um foie gras, um queijo camembert, a técnica de pasteurização ou o próprio Sartre—na realidade, foi a Pedra de Roseta, a chave para decifrar muitas inscrições egípcias antigas (consulte a Figura 1).

Figura 1. A Pedra de Roseta, que pesava 500 kg e continha política de impostos escrita em três idiomas diferentes. A inscrição registrava a gratidão dos sacerdotes pela isenção de uma série de impostos que o povo havia recebido.
A Pedra de Roseta

A pedra, criada em 196 a.C. contém textos escritos em três idiomas de um único fato—cada um em hieróglifos, em Demótico (uma inscrição egípcia) e em grego clássico. Embora seja uma tradução comparativa, ou uma sequência de frases de uma tradução para outra, a Pedra de Roseta revelou o significado de muitas inscrições outrora indecifráveis.

Em outras palavras, pense na Pedra de Roseta como uma ferramenta Babelfish de meia tonelada. Mesmo em 196 a.C., havia mais de uma maneira de dizer alguma coisa.

Os desenvolvedores de software lidam com um problema semelhante em alguns 2000 mil anos depois. Há muitas maneiras de dizer a mesma coisa em muitas linguagens de programação. Mesmo na linha de comandos, há muitas analogias a serem escolhidas, incluindo uma variedade de shells e várias combinações de comando.

Em geral, a variedade é boa, mas também pode causar desânimo. Qual solução devo escolher? A tecnologia acompanha as necessidades? A dedicação de tempo e de esforço podem compensar? Ou aquelas inscrições antigas escritas organizadamente (ou os símbolos da linguagem Perl?) ficaram obsoletos? E pior, tudo deverá ser traduzido (reescrito) para outros ambientes?

Se você não quiser ficar ligado só ao Fish shell, ao Bash shell, ao Z shell, ao arquivo cmd.exe do Windows ou à alguma outra linguagem de script shell, tente o Squirrel Shell. O Squirrel Shell fornece uma linguagem de script avançada orientada a objeto que funciona igualmente bem nos sistemas UNIX, Linux, Mac OS X e Windows. Você pode gravar um script uma vez e executá-lo em qualquer lugar.

Ou melhor, não é necessário colocar uma pedra de meia tonelada no ouvido para usá-lo.

Obtendo o Squirrel

O Squirrel Shell está disponível para uso gratuito sob os termos do GNU Public License versão 3 (GPLv3). O release mais recente é 1.2.2, de 11 de outubro de 2008. O idealizador e mantenedor do Squirrel Shell é o Constantin "Dinosaur" Makshin.

A página de download do Squirrel Shell (consulte Recursos para obter um link) fornece o código de origem e os binários para Windows de 32 e 64 bits. Se você usar um tipo de UNIX ou Linux, verifique o repositório da distribuição para os binários adequados ou crie o Squirrel Shell desde o início.

A criação desde o início é um processo mais direto. Faça download e extraia o arquivo tar de origem, mude para o diretório de origem e use as palavras mágicas na construção típica, como mostra a Lista 1.

Lista 1. Criar o Squirrel Shell da origem
$ ./configure --with-pcre=system && make && sudo make install
Checking CPU architecture...   x86
Checking for install...   /usr/bin/install
...
Configuration has been completed successfully.
   Build for x86 CPU architecture
   Installation prefix: /usr/local
   Allow debugging: no
   Build static libraries
   Use system PCRE 6.7 library
   Install MIME information: auto
   Create symbolic link: no
   Compile C code with 'gcc'
   Compile C++ code with 'g++'
   Create static libraries with 'ar rc'
   Create executables and shared libraries with 'g++'
   Install files with 'install'

Para obter a lista de opções específicas do pacote para configurar, digite ./configure --help na linha de comandos.

Por conveniência, o Squirrel Shell fornece o código de origem da biblioteca Perl Compatible Regular Expression (PCRE), que é amplamente usada no programa. Se o seu sistema não possuir o PCRE, o código do pacote configurável faz a construção ser mais rápida e fácil. Porém, se o seu sistema já tiver o PCRE, poderá escolher usá-lo ao especificar a opção --with-pcre=system. Caso contrário, especifique a opção --with-pcre=auto para vincular-se à cópia mais recente da biblioteca do sistema ou do Squirrel Shell.

O resultado da construção será um novo binário, apropriadamente denominado squirrelsh. Supondo que esse binário foi instalado em um diretório na variável PATH, como /usr/local/bin, digite squirrelsh para ativar o shell. No prompt de comandos, digite o comando printl(getenv("HOME")); para imprimir o caminho do seu diretório inicial.

$ squirrelsh
> printl( getenv( "HOME" ) );
/home/strike
> exit();

O Squirrel Shell baseia-se na linguagem de programação do Squirrel (consulte Recursos para obter links para mais informações). A linguagem é como a C++ e oferece recursos mais voltados às linguagens de script orientadas à objeto, como Python e Ruby. O Squirrel Shell incorpora todos os recursos e tipos de dados localizados no Squirrel e inclui um host de novas funções gravadas especialmente para tarefas comuns de shell script, como a cópia de um arquivo e a leitura de uma variável de ambiente.

Embora a sintaxe do Squirrel Shell seja muito detalhada para o cotidiano, o uso da linha de comandos —echo $HOME é o equivalente Bash de printl( "~")— do Squirrel Shell, que se destaca nos scripts. Você grava uma vez e pode executar em qualquer lugar, sem, por exemplo, precisar gravar uma vez no UNIX e de novo para o Windows. Como o próprio "Dinossaur" diria sobre sua obra, "O Squirrel Shell é antes de tudo um interpretador de script".


Criando Script com o Squirrel

Vamos examinar um exemplo de script do Squirrel Shell. Lista 2 mostra o arquivo listing2.nut, um script para listar recursivamente o conteúdo do seu diretório inicial.

Lista 2. Arquivo listing2.nut
#!/usr/bin/env squirrelsh

function reveal( filedir ) {
  if ( !exist( filedir ) ) {
    return;
  }

  if ( filename( filedir ) == ".." || filename( filedir ) == "." ) {
    return;
  }
  		
  if ( filetype( filedir ) == FILE ) {
  	printl( filename( filedir, true ) );
  	return;
  }

  printl("directory: " + filename( filedir, true) );
  local names = readdir( filedir );

  foreach( index, name in names ) {
    reveal( name );
  }
}

local previous = getcwd();

chdir( "~" );

reveal( getcwd() );

chdir( previous );

exit( 0 );

Por convenção, a primeira linha de cada shell script informa ao sistema operacional qual programa deve ser ativado para interpretar o script. Normalmente, você vê #! /usr/bin/bash ou #! /bin/zsh nessa linha para ativar um shell específico ou interpretar a partir de um local específico.

A#!/usr/bin/env squirrelsh é um pouco diferente. Ele ativa um programa especial, env, que por sua vez ativa a primeira instância de squirrelsh localizada na sua variável PATH. Por conseguinte, você pode alterar a variável PATH em função de uma versão local de algum programa—ou seja, você modifica a cópia do squirrelsh em $HOME/bin/squirrelsh—e não altera o conteúdo do shell script.

Nota: Esta dica dá certo com todos os interpretadores. Por exemplo, #!/usr/bin/env ruby chamaria sua versão preferencial do Ruby, conforme especificado pela sua configuração da variável PATH. Em geral, se você planejar distribuir qualquer shell script que foi gravado, usar o formato do aplicativo #!/usr/bin/env na primeira linha é mais prático porque ele executa a versão do aplicativo que o usuário configurou na variável PATH.

O restante da Lista 2 deve ser igual, pelo menos na abordagem. A função reveal() é recursiva:

  • Se você transmitir ao reveal() um caminho inválido ou o ponto perene (., o diretório atual) ou "dois pontos" (.., o diretório pai), a recursão terminará.
  • Caso contrário, se o argumento filedir for um arquivo, o código imprimirá o nome e será retornado, gerando de novo uma outra recursão. A função filename() pode criar um argumento ou dois. Com um argumento ou se o segundo argumento for false, a extensão do nome do arquivo será omitida. Se você fornecer true como o segundo argumento, o nome do arquivo inteiro será retornado.
  • Se o argumento for um diretório, o código imprimirá o nome e depois varrerá o conteúdo. (O processamento não é necessariamente aprofundado porque o conteúdo do diretório não é ordenado em uma sequência especial. O próximo exemplo fornece uma saída melhor).

Um ponto interessante: Como a chamada para reveal() é a última instrução da mesma função, a máquina virtual (VM) do Squirrel —o mecanismo que executa o código de script—pode trocar a recursão pela iteração através de uma técnica chamada recursão final. Essencialmente, essa recursão elimina o uso da pilha de chamada para recursão, tornando, assim, a recursão mais aprofundada e evitando o estouro de pilha.

A sintaxe do Squirrel é muito esparsa assim, gravar o código na linguagem é rápido, especialmente se você tiver um código gravado na linguagem C, C++ ou qualquer outra de nível superior.

O melhor de tudo é que o código shell é portátil. Você pode transferí-lo para uma máquina Windows, instalar o Squirrel Shell para Windows e executar o código.


Resolvendo Problemas com Tabelas

Um dos recursos mais legais do Squirrel é o conjunto completo de estruturas de dados relacionadas aos shells típicos. Problemas complexos sempre podem ser resolvidos se os dados puderem ser simplesmente bem organizados. O Squirrel possui objetos reais, arrays heterogêneas e arrays associativas (ou tabelas, falando do Squirrel).

Uma tabela do Squirrel é composta de slots, ou dos pares (chave-valor). Qualquer valor, exceto Null, pode servir como uma chave e qualquer valor pode ser designado a um slot. Crie um novo slot com o operador "arrow" (<-).

Vamos melhorar o código na Lista 2 para mostrar o conteúdo de um diretório antes de passar para os subdiretórios. A abordagem? Use uma tabela local para colocar os arquivos e subdiretórios em slots separados e depois processe as duas categorias de acordo. A Lista 3 mostra a nova versão do código.

Lista 3. Uma versão aprimorada da Lista 2 para imprimir o primeiro conteúdo do diretório e depois recursar em subdiretórios
#!/usr/bin/env squirrelsh

function reveal( filedir ) {
  local tally = {};
  tally[FILE] <- [];
  tally[DIR] <- [];

  if ( !exist( filedir ) ) {
    return;
  }

  if ( filename( filedir ) == ".." || filename( filedir ) == "." ) {
    return;
  }
  		
  local names = readdir( filedir );

  foreach( index, name in names ) {
    tally[ filetype( name ) ].append( name ) ;
  }

  foreach( index, file in tally[FILE] ) {
    printl( file );
  }

  foreach( index, dir in tally[DIR] ) {
    printl( filename( dir ) + "/" );
  }
 	
  foreach( index, dir in tally[DIR] ) {
    reveal( dir );
  }

}

local entries = readdir( (__argc >= 2) ? __argv[1] : "." );

exit( 0 );

Uma tabela é uma estrutura de dados ideal para ser usada aqui. A tabela em reveal() possui dois slots: um para arquivos e outro para diretórios. O valor de retorno da função filetype( name )—, seja a constante FILE ou a constante DIR—correlaciona cada item no sistema de arquivos no seu slot apropriado.

Além disso, cada slot é uma array, criada pelas duas instruções tally[FILE] <- [] e tally[DIR] <- [];. ([] é uma array vazia.) Como tally é uma variável local dentro de uma função, ela é criada novamente com cada chamada, removida do escopo e destruída automaticamente quando cada chamada for retornada.

A função da array append( arg ) inclui arg no final da array enchendo, assim, uma lista no processo. Após o loop foreach( index, name in names ), cada item foi colocado em uma lista de slot ou outra. O restante do código localizado na função imprime os arquivos, depois os diretórios e as recursões.

De certo, um shell script seria um tanto inútil sem os argumentos da linha de comandos. As variáveis especiais do Squirrel Shell __argc e __argv contém a contagem de argumentos da linha de comandos e a lista de argumentos como uma array de cadeias de caracteres, respectivamente. Novamente, por convenção, __argv[0] é sempre o nome do shell script, assim, se __argc for no mínimo 2, então os argumentos adicionais foram fornecidos. Para resumir, esse script processa apenas o primeiro argumento extra, argv[1].

Para referência, a Lista 4 mostra um script Ruby (gravado por Makshin) que é funcionalmente idêntico à Lista 3. Embora o Ruby seja tão resumido, ele ainda não é tão sucinto quanto ao código do Squirrel Shell.

Lista 4. Uma reimplementação da Lista 3 no Ruby
!/usr/bin/ruby

# List directory contents.

path = ARGV[0] == nil ? "." : ARGV[0].dup

# Remove trailing slashes
while path =~ /\/$/
  path.chop!
end

entries = Dir.open(path)
for entry in entries
  unless entry == "." || entry == ".."
    filePath    = "#{path}/#{entry}"
    fileStat = File.stat(filePath)
    if fileStat.directory?
      puts "dir : #{filePath}"
    elsif fileStat.file?
      puts "file: #{filePath}"
    end
  end
end

entries.close()

Para obter informações sobre a linguagem Squirrel, consulte a Referência de Linguagem de Programação Squirrel (consulte Recursos para obter um link).

Inteligentemente, virtualizar todas as funções no Squirrel Shell remove as peculiaridades do sistema operacional subjacente, assim, seu código pode ser o mais genérico possível. Por exemplo, a função filename() (usada nas primeiras duas listas acima) remove o caminho inicial do nome do caminho de arquivo— reduzindo /home/example/some/directory/file.txt para file.txt, por exemplo—independente da sua plataforma. Da mesma forma, readdir() e filetype() continua protegendo você de problemas e armadilhas de sistemas operacionais e de arquivos subjacentes. Normalmente, um shell comum não oferece esses recursos. (diferentemente das linguagens de script mais avançadas).

Outras funções úteis independentes de plataforma inclui convpath() para converter um nome de caminho para o formato de nome de caminho nativo e run() para chamar outro executável. A função convpath() é bidirecional e muito útil ao gravar scripts de plataforma cruzada.


A Maioria das Expressões Regulares

Os shell scripts são usados normalmente para automatizar administração de sistemas e o trabalho de manutenção. Uma das maiores capacidades desta automação é a expressão regular, um conjunto real de hieróglifos para localizar, corresponder e decompor cadeias de caracteres. Conforme mencionado no início, o Squirrel Shell requer a biblioteca PCRE, que também é localizada no Perl, PHP, Ruby e em muitos outros interpretadores e programas. O PCRE é uma arma poderosa dos divisores e fragmentadores de dados.

Embora completa, a implementação de expressões regulares do Squirrel Shell é um pouco diferente e pode lembrar a implementação do PHP. Para usar uma expressão regular no Squirrel Shell, defina a expressão regular, compile-a, faça uma comparação e depois interaja entre os resultados, se houver.

Lista 5 mostra um programa de amostra que demonstra as expressões regulares no Squirrel Shell (o código foi gravado por Makshin e usado sob permissão).

Lista 5. Uma demonstração das expressões regulares no Squirrel Shell
#!/usr/bin/env squirrelsh

// Match a regular expression against text

print("Text: ");
local text = scan();

print("Pattern: ");
local pattern = scan();

local re = regcompile(pattern);
if (!re)
{
	printl("Failed to compile regular expression - " + regerror());
	exit(1);
}

local matches = regmatch(re, text);
if (!matches)
{
	printl("Failed to match regular expression - " + regerror());
	regfree(re);
	exit(1);
}

regfree(re);
printl("Matches found:");
foreach (match in matches)
	printl("\t\"" + substr(text, match[0], match[1]) + "\"");

Aqui, scan() lê algum texto e um padrão da entrada padrão, embora sem nenhum caractere barra inicial ou final (/) que normalmente delimita o início e o final de uma expressão regular.

Dado um padrão, a função reqgcompile() compila o padrão, que acelera as correspondências repetidas. Você pode ativar ou desativar a distinção entre maiúsculas e minúsculas com um sinalizador para a função reqgcompile() (equivalente ao modificador /i do PCRE) e pode corresponder em relação a uma linha ou muitas com outra opção (um equivalente para a opção /m PCRE). Se você não compilar a expressão regular, todas as correspondências falharão.

A função regmatch(re, text) compara a expressão regular para o texto, produzindo tanto Null para não correspondências ou um array de pares de números inteiros (um array de dois elementos). O primeiro número inteiro em qualquer par é o início da correspondência e o segundo número inteiro é o final da correspondência. Isso explica o uso de substr(text, match[0], match[1]) na última linha do código.

Depois de executar a comparação, você pode iterar com os resultados. Se em algum momento você não precisar mais da expressão regular compilada, desfaça dela com a função regfree(). Também há uma função regfreeall() que descarta todos os recursos mantidos por todas as expressões compiladas.


Peculiaridades do Squirrel Shell

Em um mundo perfeito, a mesma lógica de programação seria aplicada ao UNIX, Linux e Windows e os programadores ficariam mais contentes se não mais produtivos. Mas como infelizmente os sistemas operacionais são diferentes, há momentos em que você tem que recorrer ao código customizado para um sistema específico.

Nesses casos em que nem o Squirrel Shell nem você podem simplificar a plataforma, o Squirrel Shell fornece uma função prática para sondar o sistema operacional para que o código possa seguir uma ramificação apropriada.

A lista 6 mostra como usar a função platform() para tomar decisões. Essa função sempre retorna um valor, embora este possa ser um valor desconhecido.

Lista 6. A função platform() retorna o tipo do sistema operacional
print( "Made by ... ");

local platform = platform();

switch ( platform ) {
  case "linux":
    printl( "Linus." );
    break;

  case "macintosh":
    printl( "Steve." );
    break;

  case "win32":
  case "win64":
    printl( "Bill." );
    break;

  default:
  	printl( "Unknown" );
}

Você também pode descobrir o tipo da plataforma atual através da variável de ambiente PLATFORM do Squirrel Shell:

> printl( PLATFORM );
linux

A variável de ambiente CPU_ARCH retorna o processador para o qual o shell foi compilado:

> printl( CPU_ARCH );
x86

Mais peculiaridades!

O restante das funções do Squirrel Shell gerencia os arquivos, manipula o ambiente e faz cálculos. Na realidade, há mais de 20 funções integradas para a trigonometria. A versão 2.0 está sendo planejada no momento e incluirá mais classes, suporte ao Unicode, um modo interativo melhorado e uma arquitetura de plug-in modular.

O Squirrel Shell não chega a ser um shell interativo, mas tudo bem. Essa categoria já tem muitas alternativas. O Squirrel Shell é muito melhor como um executor de script. As suas estruturas de dados têm mais capacidades do que um shell tradicional, sua sintaxe é conhecida e seu mecanismo virtual subjacente suporta qualquer coisa a partir de tipos enumerados de encadeamentos. O mecanismo Squirrel também é bem pequeno, com pelo menos 6000 linhas de código. Inclusive você pode integrar todo o Squirrel em outro aplicativo.

Experimente o Squirrel Shell quando for necessário gravar um código para duas plataformas. Essas peculiaridades ajudarão você a manter a sua.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

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=395344
ArticleTitle=Falando sobre o UNIX: Linguagem de Shell e de Script portável do Squirrel
publish-date=03172009