¿Por qué escribir un script bash completo en funciones?

¿Por qué escribir un script bash completo en funciones?

En el trabajo, escribo scripts bash con frecuencia. Mi supervisor ha sugerido que todo el script se divida en funciones, similar al siguiente ejemplo:

#!/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

¿Hay alguna razón para hacer esto además de la "legibilidad", que creo que podría establecerse igualmente bien con algunos comentarios más y algo de espacio entre líneas?

¿Hace que el script se ejecute de manera más eficiente (en realidad esperaría lo contrario, en todo caso) o facilita la modificación del código más allá del potencial de legibilidad antes mencionado? ¿O es realmente sólo una preferencia estilística?

Tenga en cuenta que, aunque el script no lo demuestra bien, el "orden de ejecución" de las funciones en nuestros scripts reales tiende a ser muy lineal ( walk_into_bardepende de las cosas que i_am_foose han hecho y do_bazactúa sobre las cosas configuradas por walk_into_bar), por lo que poder cambiar arbitrariamente el orden de ejecución no es algo que normalmente haríamos. Por ejemplo, no querrás ponerlo repentinamente declare_variablesdespués walk_into_bar, ya que eso rompería las cosas.

Un ejemplo de cómo escribiría el script anterior sería:

#!/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

Respuesta1

Legibilidades una cosa. Pero hay más quemodularizaciónque solo esto. (SemimodularizaciónQuizás sea más correcto para las funciones).

En funciones puedes mantener algunas variables locales, lo que aumentafiabilidad, disminuyendo la posibilidad de que las cosas se estropeen.

Otra ventaja de las funciones esreutilización. Una vez codificada una función, se puede aplicar varias veces en el script. También puedes portarlo a otro script.

Su código ahora puede ser lineal, pero en el futuro puede entrar en el reino desubprocesos múltiples, omultiprocesamientoen el mundo Bash. Una vez que aprenda a hacer cosas en funciones, estará bien equipado para dar el paso al paralelo.

Un punto más que añadir. Como señala Etsitpab Nioliv en el comentario a continuación, es fácil redirigir funciones como una entidad coherente. Pero hay un aspecto más de las redirecciones con funciones. Es decir, las redirecciones se pueden configurar a lo largo de la definición de la función. P.ej.:

f () { echo something; } > log

Ahora las llamadas a funciones no necesitan redirecciones explícitas.

$ f

Esto puede ahorrar muchas repeticiones, lo que nuevamente aumenta la confiabilidad y ayuda a mantener todo en orden.

Ver también

Respuesta2

Empecé a usar este mismo estilo de programación bash después de leerPublicación del blog de Kfir Lavi "Programación defensiva de Bash". Da bastantes buenas razones, pero personalmente considero que estas son las más importantes:

  • Los procedimientos se vuelven descriptivos: es mucho más fácil descubrir qué se supone que debe hacer una parte particular del código. En lugar de un muro de código, verá "Oh, la find_log_errorsfunción lee ese archivo de registro en busca de errores". Compárelo con encontrar un montón de líneas awk/grep/sed que usan Dios sabe qué tipo de expresión regular en medio de un script largo; no tienes idea de qué hace allí a menos que haya comentarios.

  • puede depurar funciones encerrándolas en set -xy set +x. Una vez que sepas que el resto del código funciona bien, puedes usar este truco para concentrarte en depurar solo esa función específica. Claro, puedes adjuntar partes del guión, pero ¿qué pasa si es una parte larga? Es más fácil hacer algo como esto:

       set -x
       parse_process_list
       set +x
    
  • Uso de impresión con cat <<- EOF . . . EOF. Lo he usado varias veces para hacer mi código mucho más profesional. Además, parse_args()la getoptsfunción es bastante cómoda. Nuevamente, esto ayuda con la legibilidad, en lugar de meter todo en un guión como una pared gigante de texto. También es conveniente reutilizarlos.

Y obviamente, esto es mucho más legible para alguien que sabe C, Java o Vala, pero tiene experiencia limitada en bash. En lo que respecta a la eficiencia, no hay mucho que puedas hacer: bash en sí no es el lenguaje más eficiente y la gente prefiere Perl y Python cuando se trata de velocidad y eficiencia. Sin embargo, puedes niceuna función:

nice -10 resource_hungry_function

En comparación con llamar amablemente a todas y cada una de las líneas de código, esto reduce la cantidad de escritura Y puede usarse convenientemente cuando desea que solo una parte de su secuencia de comandos se ejecute con menor prioridad.

En mi opinión, ejecutar funciones en segundo plano también ayuda cuando desea tener un montón de declaraciones para ejecutar en segundo plano.

Algunos de los ejemplos donde he usado este estilo:

Respuesta3

En mi comentario, mencioné tres ventajas de las funciones:

  1. Son más fáciles de probar y verificar la corrección.

  2. Las funciones se pueden reutilizar (obtener) fácilmente en scripts futuros

  3. A tu jefe le gustan.

Y nunca subestimes la importancia del número 3.

Me gustaría abordar una cuestión más:

... por lo que poder cambiar arbitrariamente el orden de ejecución no es algo que normalmente haríamos. Por ejemplo, no querrás ponerlo repentinamente declare_variablesdespués walk_into_bar, ya que eso rompería las cosas.

Para obtener el beneficio de dividir el código en funciones, se debe intentar que las funciones sean lo más independientes posible. Si walk_into_barrequiere una variable que no se usa en ningún otro lugar, entonces esa variable debe definirse y hacerse local en walk_into_bar. El proceso de separar el código en funciones y minimizar sus interdependencias debería hacer que el código sea más claro y sencillo.

Idealmente, las funciones deberían ser fáciles de probar individualmente. Si, debido a las interacciones, no son fáciles de probar, entonces es una señal de que podrían beneficiarse de la refactorización.

Respuesta4

Divide el código en funciones por la misma razón que lo haría con C/C++, python, perl, ruby ​​o cualquier código de lenguaje de programación. La razón más profunda es la abstracción: encapsula tareas de nivel inferior en primitivas (funciones) de nivel superior para que no tenga que preocuparse por cómo se hacen las cosas. Al mismo tiempo, el código se vuelve más legible (y mantenible) y la lógica del programa se vuelve más clara.

Sin embargo, al observar su código, me parece bastante extraño tener una función para declarar variables; Esto realmente me hace levantar una ceja.

información relacionada