Por favor, parem de escrever shell scripts

Please stop writing shell scripts

Por favor, parem de escrever shell scripts


 Este artigo é uma tradução do original de de autoria de Itamar Turner-Trauring intitulado Please stop writing shell scripts escrito no dia 22 de Março de 2022.

 Quando você está automatizando alguma tarefa, por exemplo empacotando a sua aplicação pelo Docker, você com frequência se encontrará escrevendo shell scripts. Você pode ter um script Bash para guiar o seu processo de empacotamento , e outro script como um ponto de entrada para o container. A medida que seu empacotamento cresce em complexidade, seu shell script também.

 Tudo funciona bem.

 E então, uma dia, seu shell script faz algo completamente errado.

 É aí que você percebe seu engano: bash, e linguagens shell scripting em geral, são na maioria quebrados por padrão. Ao menos que você seja muito cuidadoso desde o primeiro dia, qualquer shell script acima de um certo nível de complexidade é quase garantido de ser bugado... e adaptar os recursos de correção é bastante difícil.


O problema com shell scripts


 Vamos focar no bash como um exemplo específico.


Problema #1: Erros não param a execução


 Considere o shell script a seguir:
#!/bin/bash
touch newfile
cp newfil newfile2  # Deliberate typo
echo "Success"
 O que você acha que irá acontecer quando você executá-lo?
$ bash bad1.sh 
cp: cannot stat 'newfil': No such file or directory
Success
 O script continuou executando mesmo que um comando falhou! compare isso com Python, onde uma exceção evita que códigos posteriores sejam executados.

 Você podde solucionar isso ao adicionar set -e ao topo do shell script:
#!/bin/bash
set -e
touch newfile
cp newfil newfile2  # Deliberate typo, don't omit!
echo "Success"
 E agora:
$ bash bad1.sh 
cp: cannot stat 'newfil': No such file or directory


Problema #2: Variáveis desconhecidas não causam erros


 Agora vamos considerar o seguinte script, que tenta adicionar um diretório à variável de ambiente PATH. PATH é como o local de executáveis é encontrado.
#!/bin/bash
set -e
export PATH="venv/bin:$PTH"  # Typo is deliberate
ls
 Quando executamos o script:
$ bash bad2.sh 
bad2.sh: line 4: ls: command not found
 Ele não consegue localizar o comando ls porque tinhamos um erro de digitação, escrevendo $PTH ao invés de $PATH—e o bash não reclamou a respeito de uma variaável de ambiente desconhecida. Em Python você iria obter uma NameError exception; em uma linguagem compilada o código nem mesmo compilaria. No bash o script continua executando; o que poderia dar errado?

 A solução é utilizar set -u:
#!/bin/bash
set -eu
export PATH="venv/bin:$PTH"  # Typo is deliberate
ls
 E agora bash pega o erro de digitação:
$ bash bad2.sh
bad2.sh: line 3: PTH: unbound variable
Problem #3: Pipes don’t catch errors
 Achamos que solucionamos o problema da falha do comando com set -e, mas não solucionamos todos os casos:
#!/bin/bash
set -eu
nonexistentprogram | echo
echo "Success!"
 E quando executamos:
$ bash bad3.sh 
bad3.sh: line 3: nonexistentprogram: command not found
Success!
 A solução é utilizar set -o:
#!/bin/bash
set -euo pipefail
nonexistentprogram | echo
echo "Success!"
 Agora:
$ bash bad3.sh
bad3.sh: line 3: nonexistentprogram: command not found
 Nesse ponto implementamos (a maioria) do modo não oficial estrito do. Mas ainda não é o suficiente.


Problema #4: Subshells são esquisitos


 Nota: Uma versão anterior desse artigo continha informação incorreta sobre subshells. Agradeço a Loris Lucido por me mostrar meu erro.

 Utilizando a syntax $(), você   pode lançar um subshell:
#!/bin/bash
set -euo pipefail
export VAR=$(echo hello | nonexistentprogram)
echo "Success!"

 Quando você executar-lo:
$ bash bad4.sh 
bad4.sh: line 3: nonexistentprogram: command not found
Success!

 O que está acontecendo? Erros no subshell não são tratados como um erro se eles forem parte de um argumento de comando. Isso significa que erro de subshell é descartado.

Uma exceção é definir uma variável diretamente, então precisamos escrever nosso código assim:
#!/bin/bash
set -euo pipefail
VAR=$(echo hello | nonexistentprogram)
export VAR
echo "Success!"

 E agora nosso programa opera corretamente:
$ bash good4.sh 
good4.sh: line 3: nonexistentprogram: command not found

 Provavelmente essa seja uma demonstração suficiente  de mal comportamento do bash, mas certamente não é uma demonstração completa.


Alguns péssimos motivos para se utilizar shell scripts


 Quais são as rezões que você possa querer utilizar shell scripts assim mesmo?


Péssimo motivo #1: Está sempre lá!


 Praticamente todo ambiente de computação Unix-y terá um shell básico. Então se você estiver escrevendo algum empacotamento ou script de inicialização, é tentador usar uma ferramenta que você sabe que estará lá.

 A coisa é, se você estiver empacotando uma aplicação Python, você pode praticamente garantir que o desenvolvimento, CI, e ambiente de runtime todos terão Python instalado. Então por que não utilizar uma linguagem de programação que trata de verdade de erros por padrão?

 Mais amplamente, praticamente todas as linguagens de programação com uma base de usuários de tamanho decente terão algum tipo de biblioteca ou idiomas orientados a scripts. Rust, por exemplo, tem xshell e outras bibliotecas também. Portanto, na maioria dos casos, você pode usar sua linguagem de programação preferida em vez de um shell script.


Péssimo motivo #2: Apenas escreva o código correto!


 Em teoria, se você souber o que está fazendo, e manter-se focado e não esquecer de nenhum detalhe, você pode escrever shell scripts corretamente, mesmo mais complexos. Você pode até mesmo escrever testes de unidade.

 Na prática:

 Você provavelmente não está trabalhando sozinho; é improvável que todos em sua equipe tenham a experiência relevante. Todos se cansam, se distraem e acabam cometendo erros.

 Quase todo shell script complexo  que eu tenho visto estava  sem a invocação set -euo pipefail, e adicioná-lo depois do fato é bastante difícil (geralmente impossível). Não tenho certeza se já vi teste automatizado para u m shell script. Tenho certeza que eles existem, mas eles são bastante raros.


Péssimo motivo #3: Shellcheck capturará todos estes bugs!


 Se você estiver escrevendo programas shell, shellcheck é um jeito muito util de capturar bugs. Infelizmente, isso não é o suficiente sozinho.

 Considere o seguinte programa:
#!/bin/bash
echo "$(nonexistentprogram | grep foo)"
export VAR="$(nonexistentprogram | grep bar)"
cp x /nosuchdirectory/
echo "$VAR $UNKNOWN_VAR"
echo "success!"
 Se executarmos esse programa, ele exibirá a mensagem “success!”, mesmo que ele tenha 4 problemas separados (ao menos):
$ bash bad6.sh 
bad6.sh: line 2: nonexistentprogram: command not found

bad6.sh: line 3: nonexistentprogram: command not found
cp: cannot stat 'x': No such file or directory
 
success!

 Como o shellcheck faz? Ele capturará alguns dos problemas… mas não todos:
  1. Se você executar shellcheck, ele apontará o problema com o export.
  2. Se você executar shellcheck -o all, ele executará todos os checks e também apontará o problema com echo "$(nonexistentprogram ...)". Isso é, assumindo que você esteja utilizando v0.8, que foi lançado em Novembro de 2021. Versões mais antigas não possuíam esse check, então qualquer distribuição Linux anterior a isso lhe fornecerá um  shellcheck que não captura esse problema.
  3. Ele não sugere set -euo pipefail.
 Se você estiver confiando no shellcheck, recomendo fortemente se atualizar e certificar-se de executar com -o all.

 Parece de escrever shell scripts; Shell scripts são legais em algumas situações:

 Para scripts one-off que você estiver manualmente supervisionando, você pode escapar com práticas mais relaxadas.
 Às vezes, você realmente não tem garantias de que outra linguagem de programação esteja disponível e precisa utilizar o shell para fazer as coisas acontecerem.
 Para casos suficientemente simples, apenas executar alguns comandos sequencialmente, com no cubshells, lógica condicional, ou loops, set -euo pipefail é suficiente (e certifiqie-se de utilizar shellcheck -o all).
 Assim que você se encontrar fazer qualquer coisa além disso, é muito melhor utilizar uma linguagem de programação menos propensa a erros. E como a maioria dos softwares tendem a crescer com o tempo, sua melhor aposta é começar com algo um pouco menos quebrado.

Comente com o Facebook:

Nenhum comentário:

Postar um comentário

Viu algum erro e quer compartilhar seu conhecimento? então comente aí.

Observação: somente um membro deste blog pode postar um comentário.

Marcadores

A pior história sobre Linux que já ouvi (5) A.I (2) ambiente gráfico (19) AMD (14) analise (10) Andriod (16) android (7) Apple (1) arm (5) artigo (5) aws (1) bc (23) benchmark (6) BetrFS (1) blackhat (1) BSDs (30) btrfs (32) bugs (2) Caixa de Ferramentas do UNIX (19) canto do Diego Lins (2) certificações Linux (7) Código Fonte (54) comandos (31) comp (1) compressores (6) container (7) CPU (19) cracker (1) criptografia (5) crowdfunding (9) cursos (24) daemons (13) Debian (31) desempenho (1) desenvolvimento (90) desktop (19) DevOps (3) DevSecOps (4) dic (1) Dica de leitura (90) dica DLins (2) dicas do Flávio (27) Dicas TechWarn (1) diet libc (3) diocast (1) dioliunx (3) distribuições Linux (14) Docker (12) DragonflyBSD (22) driver (1) dropbear (3) ead Diolinux (2) edição de vídeo (5) embarcados (1) EMMI Linux (4) emuladores (9) endless (5) English interview (3) Enless OS (2) entrevista (17) espaço aberto (82) evento (6) facebook (1) Fedora (11) filesystem (82) financiamento coletivo (2) fork (4) fox n forests (4) FreeBSD (20) Funtoo Linux (13) games (94) gerenciadores de pacotes (4) glaucus (4) GOG (3) google (9) gpu (3) hacker (2) hardware (104) hash (1) helenos (3) I.A (1) init system (11) Intel (15) inteligencia artificial (2) IoT (1) ispconfig (1) jogos (38) kde (1) kernel (138) lançamento (64) leis (1) LFCS (1) libs (2) licenças (8) Linus (16) linus torvalds (2) Linux (194) linux foundation (3) linux para leigos (1) live (5) LPI (8) LTS (1) Mac (1) machine learning (1) matemática (9) mesa redonda (27) microcontroladores (1) microsoft (6) microst (1) muito além do GNU (167) musl (3) não viva de boatos (9) navegadores (5) NetBSD (7) newlib (1) nim (1) nintendo (1) novatec (17) novidades (1) nuvem (1) o meu ambiente de trabalho (3) off-topic (12) open source (84) OpenBSD (7) OpenShift (1) oracle (1) os vários sabores de Linux (43) padrim (2) palestras e eventos (5) partições (6) pentest (8) performance (1) pipewire (1) plan9 (1) playstation (1) processadores (30) professor Augusto Manzano (11) Programação (64) promoção (1) propagandas com Linux (8) ps4 (1) real-time. (1) Red Hat (23) redes (4) resenha nerd (4) Resumo da Semana do Dlins (2) resumo do Tux (19) retrospectiva Linux (1) risc-V (14) RISCV (13) rtos (1) runlevel (2) rust (12) segurança digital (24) servidor web (2) servidores (3) shell (9) shell script (8) sistema operacional (25) skarnet (1) smartphones (3) Software livre e de código aberto (151) sorteio (3) Steam (10) Steam no Linux (8) supercomputadores (4) suse (6) systemd (7) terminal (89) terminal de comandos (18) toca do tux (1) toybox (27) tutorial (6) Tux (3) unboxing (7) UNIX (17) UNIX Toolbox (14) vartroy (1) vga (1) virtualização (2) vulnerabilidade (6) wayland (5) web (1) whatsapp (1) whitehat (1) Windows Subsystem for Linux (2) wine (14) WoT (1) yash (1) ZFS (15) zsh (3)