Anotações do curso básico de programação do shell (aula 1)

Estas são as anotações da aula 1 do curso básico de programação do shell GNU/Linux, que estão sendo compartilhadas livre e gratuitamente graças aos inscritos no curso e apoiadores. Você pode me ajudar a continuar produzindo e compartilhando esse tipo de material de várias formas:

  • Divulgando, inscrevendo-se ou doando inscrições para os meus cursos;
  • Com seu apoio regular pelo Apoia.se;
  • Com seu apoio eventual pelo PicPay ou pela chave PIX abaixo:

PIX: e26a05db-d200-4261-aa65-c6ac3dc2dd0f

Conheça também a campanha de pré-lançamento do meu próximo livro!


Aula 1: anotações

1. Variável ‘SHLVL’

A única utilidade da variável SHLVL é informar o quão fundos nós estamos em sessões aninhadas do shell. A propósito, não existe um SHLVL que expanda o valor 0, como perguntado na aula: o menor valor é 1, mas a pergunta rendeu uma boa investigação!

Dependendo da forma como o shell é iniciado, a sessão que nós temos no terminal pode ser maior do que 1. Por exemplo, aqui no sistema em que eu trabalho, assim que eu abro o terminal no ambiente gráfico, a variável SHLVL expande o valor 2.

blau@debxp:~$ echo $SHLVL
2

O mesmo acontece se eu abrir um segundo terminal:

blau@debxp:~$ echo $SHLVL
2

Isso acontece devido à forma como o Bash é disponibilizado para mim a partir do início da sessão do X: eu faço login no tty e inicio o X a partir do Bash de nível 1 com o comando startx:

blau@debxp:~$ pstree $(pidof Xorg) -sa
systemd
  └─login -p --
      └─bash   <---- aqui está o Bash SHLVL=1
          └─startx
              └─xinit
                  └─Xorg

Sendo assim, podemos depreender que a contagem é iniciada no momento em que fazemos o login em uma sessão do shell:

blau@debxp:~$ echo $SHLVL
2
blau@debxp.org:~$ su -
Senha:
root@debxp:~# echo $SHLVL
1

2. Processos

Processos são uma estrutura de dados que o kernel utiliza para gerenciar os programas em execução e a designação de recursos. Essa estrutura é implementada a partir de um sistema de arquivos virtual chamado procfs, montado no diretório /proc. Ali, cada diretório tem como nome o número de um processo (PID).

As principais variáveis do shell para expandir PID’s são:

  • $: expande a sessão corrente do shell.
  • BASHPID: também expande a sessão corrente do shell, mas não é "copiada" para subshells.
  • PPID: expande o PID do processo pai.

Também vimos que o utilitário ps, sem argumentos, exibe informações sobre os processos associados ao usuário corrente no terminal onde o comando é executado:

blau@debxp:~$ ps
    PID TTY          TIME CMD
 517640 pts/0    00:00:00 bash
 518242 pts/0    00:00:00 ps

3. Sessões do shell

Uma sessão é o tipo de agrupamento de processos que está relacionado com a execução de um shell desde o momento em que ele é iniciado a partir de um login (veja novamente o que dissemos sobre o SHLVL).

Quando invocamos a execução de um script ou do próprio binário do Bash, é iniciada uma nova sessão do shell, filha da sessão em que houve a invocação.

Sessões filhas são sessões totalmente novas do shell, mas existem formas de iniciar sessões filhas que são um clone da sessão mãe: são os subshells. Para entender como essas coisas funcionam, é preciso saber como o shell inicia os processos:

  1. Quando mandamos o shell executar um comando, ele chama uma função do kernel (uma chamada de sistema) para que um clone de seu próprio processo seja criado para o nosso comando: é a chamada fork. Este clone tem seu próprio PID, mas é uma cópia do processo do shell que o iniciou.

  2. Se o comando que estamos executando contiver um pipe ou um agrupamento de comandos com parêntesis, o processo forkado (o clone) será o processo definitivo daquilo que estivermos executando: ou seja, este clone é o nosso subshell.

  3. Por outro lado, se o nosso comando envolver um binário executável ou um script, o shell fará outra chamada de sistema, a chamada exec, que trocará o processo clone pelo processo definitivo do nosso comando.

Nota: obviamente, se o comando contiver apenas comandos internos do shell, sem agrupamentos com parêntesis ou pipes, nenhum subprocesso será iniciado, porque o shell dará conta da execução na sua própria sessão.

É por isso que:

  • Um script sem hashbang tem seu próprio PID e não é uma cópia da sessão mãe: ser executável, neste caso, significa poder iniciar uma nova sessão do shell.

  • Uma sessão iniciada pelo executável do Bash não é uma cópia da sessão mãe.

  • Enquanto os subshells, por sua vez, são cópias da sessão mãe.

4. O shell ‘sh’

O chamado shell sh é qualquer shell utilizado para fazer o papel do shell de mesmo nome nos sistemas Unix. Sendo um sistema parecido com o Unix, ou Unix like, o GNU/Linux também precisa definir que shell será esse, o que geralmente é feito a partir de um link simbólico no diretório /usr/bin. No Debian, o shell que cumpre essa função é o dash:

blau@debxp:~$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 jan 25 22:52 /usr/bin/sh -> dash

Nos sistemas *BSD e no macOS, o "shell sh" é um binário executável chamado sh.

5. Shell POSIX

Um engano comum é confundir a função de shell sh com o conceito de um shell POSIX.

Um shell POSIX é aquele que implementa as normas POSIX a respeito do que o shell de um sistema Unix like deve oferecer em termos de recursos, comandos e comportamento. Neste sentido, são considerados POSIX os shells: Bash, Ash, Dash, Korn Shell, entre outros. Um dos requisitos para ser o shell sh, é que o shell em questão seja POSIX.

Além disso, as normas POSIX dizem respeito ao conjunto de recursos e comportamentos do shell com vistas à portabilidade entre sistemas Unix like, e não a ideias de "boas práticas", ou de "certo ou errado", na escrita do seu programa em shell!

Então, fique atento:

  • Ser portável é um requisito de projeto: você escreve um programa portável se quiser ou precisar.

  • Se precisar de um shell menor (em bytes) e mais leve, isso também é um requisito do projeto que, fatalmente, levará você a utilizar um shell que oferece estritamente (às vezes, até menos) os recursos esperados de um shell POSIX: como o ash e o dash, o que obrigará você a escrever um código utilizando apenas esse pequeno conjunto de recursos.

  • Em qualquer outra situação, não há nada de errado ou contrário às boas práticas explorar ao máximo os recursos adicionais que shells como o Bash, por exemplo, têm a oferecer.

  • Não caia na lábia dos sabichões, que não manjam nada do shell, mas adoram "despejar" regras para os outros como se fossem sumidades!

6. Hashbang

A hashbang, ou shebang, é um comando iniciado pelos caracteres #! posicionados exatamente no começo de arquivos de texto que recebem o atributo de execução (arquivos executáveis) e tem a finalidade de provocar a chamada de sistema exec após o processo do shell ter sido clonado (chamada fork), ao mesmo tempo que informam ao kernel qual programa será utilizado para interpretar o restante do texto no arquivo.

Nota: embora nós falemos de chamadas fork e exec, no geral, existem várias funções do kernel para esses propósitos: algumas delas permitem o transporte do ambiente da sessão mãe para as sessões filhas (como nos subshells), outras não.

Quando o script não tem uma hashbang, o processo do shell onde ele foi executado será clonado (fork) mas não passará pela chamada exec (e nem por isso será um subshell).

Observe o script abaixo (teste-exec):

echo Meu PID é $$
echo O PID da sessão mãe é: $PPID

echo ----------

ps -o 'f,pid'

Ele tem permissão de execução, mas não tem uma hashbang. Observe o que acontece na primeira coluna da saída do comando ps quando o script é executado:

blau@debxp:~$ ./teste-exec 
Meu PID é 523916
O PID da sessão mãe é: 523771
----------
F     PID
0  523771
1  523916
4  523917

A primeira coluna (F) é uma flag que indica:

  • 0: se o processo passou pelo fork e pelo exec;
  • 1: se o processo passou pelo fork, mas não pelo exec.

Não nos interessa a flag 4, do processo do ps, por enquanto.

Mas, o que acontece se incluirmos uma hashbang no script?

#!/bin/bash

echo Meu PID é $$
echo O PID da sessão mãe é: $PPID

echo ----------

ps -o 'f,pid'

Veja o resultado:

Meu PID é 524001
O PID da sessão mãe é: 523771
----------
F     PID
0  523771  <--- 'fork' e 'exec'
0  524001  <--- 'fork' e 'exec'
4  524002

Como escrever a ‘hashbang’

Para scripts, por algum motivo, limitados aos recursos do shell sh, nós utilizamos:

#!/bin/sh

Para scripts em Bash, ambas as hashbangs abaixo são válidas:

#!/bin/bash

Ou…

#!/usr/bin/env bash

Na próxima aula, nós veremos a diferença entre essas formas de escrever a hashbang do Bash.

Músico, programador, designer e apaixonado pelos ideais do Software Livre.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post comment