Dicas para o Bash: subshells

Uma das coisas que mais me atraiu, quando comecei a usar Linux, foi a shell. À sua disposição, encontra-se um interpretador de comandos com uma linguagem de programação completa e de altíssimo nível. Não demorou para que eu estivesse escrevendo programas completos nesse interpretador.

Porém, certas coisas parecem não dar certo. Isso porque quem está acostumado a programar em linguagens compiladas pressupõe um comportamento que não é obedecido pela shell. Nem deveria – a shell tem suas peculiaridades e elas têm sua razão de ser. Aí você procura a informação necessária no manual. Digita ‘man bash’ e caboom! Um texto gigantesco aparece para você. A ideia deste post e de outros como este que virão é dar algumas dicas, coisas que podem passar despercebidas por programadores e entusiastas e que seriam difíceis de serem absorvidas em uma leitura do manual.

Eu gosto do Bash. Portanto, vou falar sobre ele. Eu uso a versão 4.2.37 e, portanto, o que é colocado aqui foi testado nele.

Subshells

No Linux (e em sistemas Unix em geral), um processo é originado a pedido de um processo pai. As únicas exceções são a tarefa ociosa e o processo init, que são iniciados diretamente pelo kernel. O processo pai pede para que seja clonado e o kernel constrói um ambiente para que o novo processo execute. Esse ambiente é herdado do processo pai. O significado disso é que o processo filho detém as mesmas variáveis do pai, compartilha alguns recursos com ele, etc. Porém, as variáveis que o filho criar não poderão ser acessadas pelo pai, a menos que o filho permita explicitamente isso (exportando a variável). Uma subshell é um processo filho de uma shell.

Quando executamos um comando externo em uma shell, ele cria uma subshell para este comando. O comando é executado como um processo separado. Para evitar isso, o comando pode ser chamado com a diretiva exec, se necessário. Note que há diferença ao executar um comando externo e uma função interna da shell. O comando cd, é uma função interna (builtin) e não é executado em uma subshell. Para saber se um comando é uma uma função interna ou um programa externo, use ‘type <comando>’. Por exemplo, ‘type ls’ deve retornar o caminho do binário ls (geralmente /bin/ls) e ‘type cd’ deve retornar algo como “cd is a shell builtin”.

Outra ocasião em que uma subshell é criada é quando fazemos redirecionamento de entrada e saída, com um conduíte (o pipe ‘|’). O que vem depois do pipe é executado em uma subshell.

Mas, nós mesmos podemos criar uma subshell. Existem duas maneiras de agruparmos comandos. Uma é usar

{ comando1
  comando2
  ...
  comandoN; }

Esta maneira de agrupamento não cria nova subshell. Note os espaços ao redor das chaves. As chaves, usadas desta maneira, são palavras reservadas da linguagem de programação. Assim, devem ser “isoladas” por espaços em branco. Também, o ponto-e-vírgula é obrigatório, ao final do último comando.

O agrupamento que cria a subshell é o dado por parênteses. Assim,

( comando1
  comando2
  ...
  comandoN )

será executado em uma subshell. Note os espaços em branco ao redor dos parênteses. Façamos uma experiência. Abra um terminal e digite

i=10
echo $i

O resultado deve ser ’10’. Agora, façamos o seguinte:

i=10
( i=70
echo $i )
echo $i

O que aconteceu? A variável i interna à subshell é diferente da variável i do ambiente. Modificações nela, feitas pelo filho, não podem ser vistas pelo pai, a não ser que sejam exportadas. Assim, para o pai, i=10. O filho muda a variável de 10 para 70, mas o pai não recebe as modificações. Tente o mesmo código substituindo o agrupamento de parênteses pelo de chaves.

Tá, e daí?

Suponha que, dentro de um script, queiramos fazer modificações no ambiente, como mudar o diretório de trabalho ou mudar o separador interno de campos, mas depois teremos que restaurar tudo como estava. É só em um trecho do script que essas coisas devem ser diferentes. Uma solução é criar uma variável temporária para cada coisa que você alterar e depois restaurar tudo como estava, torcendo para não se esquecer de nada. Outra é fazer todas as modificações em uma subshell. Ao sair da subshell, as modificações desaparecem, sem precisar guardar nada, sem precisar lembrar do que deve ser restaurado, nada. Simples assim.

Outra utilidade está na execução assíncrona de processos. Em uma shell, ao executarmos um comando, o comportamento padrão é esperar que o comando termine para retornar o controle ao usuário. Esta é a execução síncrona. Se quisermos continuar a executar outros comandos, sem esperar que um determinado comando termine (a execução assíncrona), basta colocarmos o caractere & ao final do comando. É comum as pessoas se referirem a isto como “execução em segundo plano”. Suponha que você precise executar cálculos difíceis em uma máquina com quatro núcleos e que o programa possa ser dividido em quatro blocos de execução. Então, o código

( bloco1 ) &
( bloco2 ) &
( bloco3 ) &
( bloco4 ) &

atribuirá cada bloco a um processador.

Para saber em que nível de aninhamento o código se encontra, há duas variáveis ambiente que guardam essa informação. A primeira delas é a SHLVL (shell level) que indica quantas instâncias aninhadas do Bash estão em execução. Experimente:

echo $SHLVL
bash
echo $SHLVL

O valor deve ter sido incrementado em uma unidade. Esta variável não indica os subníveis, apenas instâncias. Veja:

echo $SHLVL
( echo $SHLVL )

que devem ter fornecido os mesmos resultados. Para saber o subnível, a variável a ser chamada é a BASH_SUBSHELL. Experimente:

echo $BASH_SUBSHELL
( echo $BASH_SUBSHELL )

que deve ter sofrido incremento na subshell. Você pode usar essas variáveis para depurar aquele script em que o laço for, ou while, deveria fazer alguma coisa com uma variável, mas ela não se altera. Estaria ela em uma subshell ao ser alterada? Imprimir o conteúdo dessas variáveis pode ajudar.

Ficou com dúvidas? Alguma curiosidade? Conhece um uso diferente de subshells? Deixe seu comentário!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s