Automação com Bash, parte 1
Ideia geral desta série de posts
Nesta série de posts, vou apresentar alguns processos que eu automatizei durante algumas disciplinas na universidade, a maioria deles em Bash (Bourne Again Shell). Pode ser que a automação aconteça usando outras ferrametas como o Python e outros em ambientes como uma IDE ou uma pipeline de CI.
A ideia é que você leia esses posts, aprenda uma ferramenta ou outra e se inspire para automatizar os seus próprios processos na sua vida de programação.
Eu mesmo fui inspirado por uma aula da disciplina
Missing Semester do MIT.
A aula de data wrangling
foi onde eu formei a base do meu conhecimento (ainda fraca por falta de prática)
do programa sed
e de regex
(regular expressions ou expressões regulares),
que vão ser bastante usados nesta série. Se você estuda Ciências da Computação ou
Engenharia de Software, vale à pena você dar uma olhada nessa disciplina.
Tópicos deste post
Neste post vamos aprender alguns conceitos e comandos:
watch
- operador pipe
ps
grep
O que você precisa
- Computador com ambiente Unix (Máquina virtual, dual boot com alguma distro Linux)
- Se você está em Windows, você pode usar o WSL2.
- Terminal
- gcc (não é obrigatório)
Um pouco de prática: relógio ao vivo de terminal
Vamos começar com uma situação muito simples: mostrar as horas no terminal a cada
intervalo de tempo, como um relógio ao vivo. Em Linux, um comando que mostra as
horas é o date
. Se você usá-lo no terminal, você deve ver algo assim:
sex nov 26 18:40:39 -03 2021
Como queremos apenas as horas, podemos passar a opção +"%R:%s"
. O resultado de
executar date +"%R:%s"
é:
18:40:55
Mas esse comando mostra as horas apenas uma vez. Teríamos que executar o comando
date
pelo menos uma vez por segundo para termos um relógio decente. Queremos algo
mais automático. Para isso vamos usar o comando watch
. watch
executa um comando
periodicamente mostrando o resultado dele em tela cheia. Esse comando é muito
útil quando queremos observar alguma coisa que muda com o tempo sem ter que ficar
digitando um comando no terminal toda ver que queremos ver um resultado.
Você pode aprender mais sobre comandos no manpages
do Linux. Se você rodar
man watch
você verá algo assim no terminal:
WATCH(1) User Commands WATCH(1)
NAME
watch - execute a program periodically, showing output fullscreen
SYNOPSIS
watch [options] command
DESCRIPTION
watch runs command repeatedly, displaying its output and errors (the
first screenfull). This allows you to watch the program output
change over time. By default, command is run every 2 seconds and
watch will run until interrupted.
No terminal, você pode rolar a página para baixo com as setas do teclado ou com
a barra de rolagem do mouse. Toda vez que você tiver dúvida sobre o funcionamento
de algum comando ou suas flags e opções, pesquise primeiro no Stackoverflow
consulte as páginas do manual para esse comando.
Executando watch date +%R
no terminal deve resultar nas horas sendo impressas
a cada 2 segundos, com um cabeçalho com parâmetros adicionais sobre o comando. Não
é bem o que a gente quer, mas estamos quase lá. Você pode interromper a execução
de watch
com Ctrl + C.
Olhando no manual de watch
,
percebemos que existe uma flag -n
para especificar o intervalo de tempo em
segundos. O intervalo padrão, como pode-se perceber é de 2 segundos. Se queremos
um relógio ao vivo, podemos diminuir o intervalo para 1 segundo ou um intervalo
menor que isso.
watch -n 0.5 date +%R
Note que a flag -n
é para o comando watch
e não para o comando date
.
Exercício para o leitor: consultando as páginas do manual, descubra como fazer
o comando watch
não mostrar o cabeçalho com informações adicionais, ou seja,
que mostre apenas o resultado do comando date
. Dica: procure por título ou
title.
Um pouco mais complicado: árvore de processos
Vamos usar esses conhecimentos em um cenário um pouco mais complicado: observar a árvore de processos de um programa.
Para isso, vamos executar um programa feito em C que simplesmente cria vários processos filhos e morre depois de alguns segundos:
1// procs.c
2#include <stdio.h>
3#include <sys/types.h> // pid_t
4#include <unistd.h> // fork, sleep
5
6int main() {
7 int i;
8 pid_t pid;
9
10 for (i = 0; i < 3; i++) {
11 sleep(1);
12 pid = fork();
13
14 if (pid > 0) {
15 sleep(2);
16 fork();
17 }
18 }
19
20 return 0;
21}
Compile com gcc procs.c
e execute com ./a.out
.
Você pode escolher outro programa para observar. Navegadores web (Chrome, Brave Browser, Edge) são ótimos exemplos.
Vamos usar novos comandos para isso:
ps
: mostra um snapshot dos processos atuais.grep
: imprime as linhas que contém determinada string.
Consulte os manuais desses comando para saber mais (eu só traduzi essas definições
de lá). Tome um tempo pra explorar o comando ps
com diferentes flags e opções.
Leia a seção EXAMPLES
do manual do ps
. Depois disso, tente filtrar o resultado
do comando ps
com o grep
para obter os processos de determinado programa.
Se você conseguiu fazer tudo isso, fica fácil mostrar a árvore de processos de um programa a sua escolha. Caso contrário, continue lendo.
Para usar esses comandos, vamos usar o operador pipe em shell. O pipe redireciona a saída padrão de um comando (ou programa) para a entrada padrão de outro comando (ou programa). A partir de agora, use duas janelas de terminal: uma para executar os comandos de shell e a outra para executar o programa em C.
Na primeira janela, execute o programa em C com
./a.out
Logo em seguida, na segunda janela de terminal, execute o seguinte:
1ps axjf | grep "a.out"
(Ao invés de observar o programa a.out
, você pode observar algum navegador.
Basta substituir a.out
por google-chrome
ou brave-browser
).
Se você executou os comandos anteriores suficientemente rápido, você deve ver
uma árvore de processos em certo instante da execução do programa a.out
. Se
você não conseguiu ser rápido o suficiente, você pode aumentar o tempo nas
chamadas de função sleep
ou, melhor ainda, usar o comando watch
.
1watch -n 0.1 "ps axjf | grep 'a.out'"
Note os diferentes usos de aspas simples e aspas duplas nos comandos daqui pra frente.
As opções pra ps
vão listar todos os processos do computador e mostrar a relação
entre alguns deles através de uma árvore. Usando o resultado do ps
, grep
imprime todas as linhas com a string a.out
presente.
Exercício para o leitor: você pode notar que mesmo depois que o programa
a.out
termina sua execução, o
comando watch
continua mostrando alguns resultados de processos como watch
e
o próprio grep
. Pesquise na internet ou nas páginas do manual como podemos
mandar o grep
não imprimir linhas com certas strings, de maneira que apareça
apenas a linhas dos processos filhos do a.out
.
Para atingir o objetivo do parágrafo anterior, podemos usar a flag -v
do grep
:
1watch -n 0.1 "ps axjf | grep 'a.out' | grep -v 'watch'"
Se você executar assim, ainda sobra um processo que não pertence à arvore de
processos de a.out
. Podemos removê-la com outro filtro para grep
1watch -n 0.1 "ps axjf | grep 'a.out' | grep -v 'watch' | grep -v 'grep'"
Mas podemos remover essa última parte de outra maneira:
1watch -n 0.1 "ps axjf | grep 'a.out' | grep -v -E 'watch|grep'"
Sem entrar em muitos detalhes, a flag -E
habilita regex para o grep
. No
contexto de regex, o operador |
funciona como um operador lógico OU. Então,
pode-se ler grep -v -E 'watch|grep'
como “não imprima as linhas que contêm
watch OU grep”.
E finalmente, o que resta na tela é a árvore de processos do programa a.out
.