Por que escrever um script bash inteiro em funções?

Por que escrever um script bash inteiro em funções?

No trabalho, escrevo scripts bash com frequência. Meu supervisor sugeriu que todo o script fosse dividido em funções, semelhante ao exemplo a seguir:

#!/bin/bash

# Configure variables
declare_variables() {
    noun=geese
    count=three
}

# Announce something
i_am_foo() {
    echo "I am foo"
    sleep 0.5
    echo "hear me roar!"
}

# Tell a joke
walk_into_bar() {
    echo "So these ${count} ${noun} walk into a bar..."
}

# Emulate a pendulum clock for a bit
do_baz() {
    for i in {1..6}; do
        expr $i % 2 >/dev/null && echo "tick" || echo "tock"
        sleep 1
    done
}

# Establish run order
main() {
    declare_variables
    i_am_foo
    walk_into_bar
    do_baz
}

main

Existe alguma razão para fazer isso além da "legibilidade", que eu acho que poderia ser igualmente bem estabelecida com mais alguns comentários e algum espaçamento entre linhas?

Isso faz com que o script seja executado com mais eficiência (na verdade, eu esperaria o contrário, se houver) ou facilita a modificação do código além do potencial de legibilidade mencionado acima? Ou é realmente apenas uma preferência estilística?

Observe que, embora o script não demonstre isso bem, a "ordem de execução" das funções em nossos scripts reais tende a ser muito linear - walk_into_bardepende do que i_am_foofoi feito e do_bazatua de acordo com o que foi configurado por walk_into_bar- sendo assim poder trocar arbitrariamente a ordem de execução não é algo que geralmente faríamos. Por exemplo, você não iria querer colocar declare_variablesafter de repente walk_into_bar, isso quebraria as coisas.

Um exemplo de como eu escreveria o script acima seria:

#!/bin/bash

# Configure variables
noun=geese
count=three

# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"

# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."

# Emulate a pendulum clock for a bit
for i in {1..6}; do
    expr $i % 2 >/dev/null && echo "tick" || echo "tock"
    sleep 1
done

Responder1

Legibilidadeé uma coisa. Mas há mais paramodularizaçãodo que apenas isso. (Semi-modularizaçãotalvez seja mais correto para funções.)

Nas funções você pode manter algumas variáveis ​​locais, o que aumentaconfiabilidade, diminuindo a chance de as coisas ficarem confusas.

Outra vantagem das funções éreutilização. Depois que uma função é codificada, ela pode ser aplicada várias vezes no script. Você também pode portá-lo para outro script.

Seu código agora pode ser linear, mas no futuro você poderá entrar no reino damultithreading, oumultiprocessamentono mundo Bash. Depois de aprender a fazer coisas em funções, você estará bem equipado para entrar no paralelo.

Mais um ponto a acrescentar. Como observa Etsitpab Nioliv no comentário abaixo, é fácil redirecionar as funções como uma entidade coerente. Mas há mais um aspecto dos redirecionamentos com funções. Ou seja, os redirecionamentos podem ser definidos junto com a definição da função. Por exemplo.:

f () { echo something; } > log

Agora nenhum redirecionamento explícito é necessário pelas chamadas de função.

$ f

Isto pode poupar muitas repetições, o que novamente aumenta a confiabilidade e ajuda a manter as coisas em ordem.

Veja também

Responder2

Comecei a usar esse mesmo estilo de programação bash depois de lerPostagem do blog de Kfir Lavi "Programação Defensive Bash". Ele dá algumas boas razões, mas pessoalmente considero estas as mais importantes:

  • os procedimentos tornam-se descritivos: é muito mais fácil descobrir o que uma determinada parte do código deve fazer. Em vez de uma parede de código, você vê "Ah, a find_log_errorsfunção lê esse arquivo de log em busca de erros". Compare isso com encontrar muitas linhas awk/grep/sed que usam Deus sabe que tipo de regex no meio de um script longo - você não tem ideia do que está fazendo lá, a menos que haja comentários.

  • você pode depurar funções colocando entre set -xe set +x. Depois de saber que o restante do código funciona bem, você pode usar esse truque para se concentrar na depuração apenas dessa função específica. Claro, você pode incluir partes do script, mas e se for uma parte longa? É mais fácil fazer algo assim:

       set -x
       parse_process_list
       set +x
    
  • uso de impressão com cat <<- EOF . . . EOF. Eu usei isso algumas vezes para tornar meu código muito mais profissional. Além disso, parse_args()a getoptsfunção é bastante conveniente. Novamente, isso ajuda na legibilidade, em vez de colocar tudo no script como uma parede gigante de texto. Também é conveniente reutilizá-los.

E obviamente, isso é muito mais legível para alguém que conhece C ou Java, ou Vala, mas tem experiência limitada em bash. No que diz respeito à eficiência, não há muito o que você pode fazer - o bash em si não é a linguagem mais eficiente e as pessoas preferem perl e python quando se trata de velocidade e eficiência. No entanto, você pode niceuma função:

nice -10 resource_hungry_function

Comparado a chamar nice em cada linha de código, isso diminui muita digitação E pode ser usado convenientemente quando você deseja que apenas uma parte do seu script seja executada com prioridade mais baixa.

Executar funções em segundo plano, na minha opinião, também ajuda quando você deseja ter um monte de instruções para executar em segundo plano.

Alguns dos exemplos onde usei esse estilo:

Responder3

No meu comentário, mencionei três vantagens das funções:

  1. Eles são mais fáceis de testar e verificar a exatidão.

  2. As funções podem ser facilmente reutilizadas (fonte) em scripts futuros

  3. Seu chefe gosta deles.

E nunca subestime a importância do número 3.

Gostaria de abordar mais uma questão:

... portanto, poder trocar arbitrariamente a ordem de execução não é algo que geralmente faríamos. Por exemplo, você não iria querer colocar declare_variablesafter de repente walk_into_bar, isso quebraria as coisas.

Para obter o benefício de dividir o código em funções, deve-se tentar tornar as funções o mais independentes possível. Se walk_into_barrequer uma variável que não é usada em outro lugar, então essa variável deve ser definida e tornada local para walk_into_bar. O processo de separar o código em funções e minimizar suas interdependências deve tornar o código mais claro e simples.

Idealmente, as funções devem ser fáceis de testar individualmente. Se, devido às interações, eles não forem fáceis de testar, isso é um sinal de que poderão se beneficiar da refatoração.

Responder4

Você divide o código em funções pelo mesmo motivo que faria para C/C++, python, perl, ruby ​​ou qualquer código de linguagem de programação. A razão mais profunda é a abstração - você encapsula tarefas de nível inferior em primitivas (funções) de nível superior para que não precise se preocupar com como as coisas são feitas. Ao mesmo tempo, o código torna-se mais legível (e sustentável) e a lógica do programa torna-se mais clara.

Porém, olhando seu código, acho bastante estranho ter uma função para declarar variáveis; isso realmente me faz levantar uma sobrancelha.

informação relacionada