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:
-
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. -
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.
-
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 odash
, 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
eexec
, 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 pelofork
e peloexec
;1
: se o processo passou pelofork
, mas não peloexec
.
Não nos interessa a flag
4
, do processo dops
, 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.