¿Es común dividir una secuencia de comandos más grande en varias secuencias de comandos y obtenerlas en la secuencia de comandos principal?

23

En este momento estoy desarrollando un script Bash más grande (es un proyecto mío de código abierto) y está empezando a convertirse en un desastre. He dividido la lógica en funciones, uso variables locales donde puedo y solo he declarado un puñado de variables globales. Aún así, se está volviendo bastante difícil de mantener.

Pensé en dividir el guión en varios guiones y buscarlos en mi guión principal (similar a las importaciones en otros idiomas).

Pero me pregunto si este es un enfoque factible. Primero, el abastecimiento de múltiples scripts podría ralentizar severamente el tiempo de ejecución del script, y en segundo lugar, dificulta la distribución.

Entonces, ¿es este un buen enfoque, y otros proyectos (de código abierto) lo hacen de la misma manera?

método de ayuda
fuente
44
Busqué un script de shell realmente largo y con 3500 líneas y 125 KB, no querría tratar de mantenerlo. El shell es realmente amigable cuando se conectan programas, pero cuando intenta hacer cómputo, se pone bastante feo. Sé que el código que tiene funciona principalmente y los costos de portarlo son altos, pero es posible que desee considerar otra cosa en el futuro.
msw
1
Me he encontrado con el mismo problema. Tengo un proyecto bash moderadamente grande sourceforge.net/projects/duplexpr . Actualmente, cada script es autónomo, pero estoy pensando en mover todas las funciones comunes a un archivo (s) separado e incluirlas donde sea necesario para eliminar tener que actualizarlas en varios lugares. En general, creo que sería mejor simplemente llamar a cada secuencia de comandos sucesiva en lugar de obtenerla. Hace correr las partes independientemente posible. Luego, debe pasar las variables como argumentos o en archivos de parámetros (o puede exportarlas si no desea independiente)
Joe

Respuestas:

13

Sí, es una práctica común. Por ejemplo, en los primeros días de Unix, el código de shell responsable de guiar el sistema a través de sus fases de arranque a la operación multiusuario era un solo archivo /etc/rc. Hoy en día, el proceso de arranque está controlado por muchos scripts de shell, desglosados ​​por función, con funciones y variables comunes que se obtienen de las ubicaciones centrales según sea necesario. Las distribuciones de Linux, Macs, BSD, todas han adoptado este enfoque en mayor o menor grado.

Kyle Jones
fuente
2
aunque en ese caso, es más por reutilización. Diferentes scripts usan una biblioteca común de funciones de shell.
Stéphane Chazelas
16

¿Es Shell la herramienta adecuada para el trabajo en ese momento? Como desarrollador que se ha topado con problemas de código de crecimiento excesivo, puedo decirle que no se debe considerar una reescritura, sino considerar la separación de las piezas en algo más adecuado para la escala que está buscando para hacer crecer su aplicación, tal vez python o ruby ​​o incluso perl ?

Shell es un lenguaje de utilidad, es un lenguaje de secuencias de comandos, por lo que será difícil hacer que crezca a esos tamaños.

JasonG
fuente
Esto es exactamente lo que estaba a punto de publicar.
zwol
especialmente dado que incluso los sistemas operativos más antiguos como Solaris se envían con perl y python, etc., ahora. Una de las razones por las que los sistemas más antiguos usaban scripts de shell es porque el shell siempre estaba disponible, pero los Unices más grandes, como HP-UX y Solaris, y AIX, no podían garantizar que incluyeran otras herramientas.
Tim Kennedy
7

Si facilita su mantenimiento, puede tener ambos. Divídalo en partes lógicas para que pueda mantenerlo fácilmente, luego escriba (por ejemplo) un Makefile para volver a armarlo todo para su distribución. Puede escribir algunos scripts rápidos para copiar las funciones del archivo de inclusión al archivo de salida en lugar de la sourcelínea o simplemente haga algo trivial como este (tendrá que volver a tabular esto, según las makepestañas requeridas):

all: myscript

myscript: includes/* body/*
    cat $^ > "$@" || (rm -f "$@"; exit 1)

Luego tiene una versión "fuente" (utilizada para la edición) y una versión "binaria" (utilizada para la instalación trivial).

derobert
fuente
1
¡Ajá! Alguien más que usa el enfoque de gato simple :)
Clayton Stanley
4

Un guión se puede dividir como usted describe, se puede hacer casi cualquier cosa. Yo diría que el 'buen enfoque' sería compartimentar su gran script, descubrir dónde se podrían ejecutar partes de él como un proceso separado, comunicándose a través de mecanismos de IPC.

Más allá de eso, para un script de shell, lo empaquetaría como un solo archivo. Como usted dice, hace que la distribución sea más difícil: debe saber dónde se encuentran los scripts de 'biblioteca' (no hay un buen estándar para los scripts de shell) o confiar en que el usuario establezca su ruta correctamente.

Podrías distribuir un programa de instalación que maneje todo esto por ti, extrayendo los archivos, colocándolos en el lugar adecuado, diciéndole al usuario que agregue algo como export PROGRAMDIR=$HOME/lib/PROGRAMel archivo ~ / .bashrc. Entonces, el programa principal podría fallar si $PROGRAMDIRno está configurado o no contiene los archivos que espera.

No me preocuparía tanto por la sobrecarga de cargar los otros scripts. La sobrecarga es realmente solo abrir un archivo; El procesamiento del texto es el mismo, especialmente si son definiciones de funciones.

Arcege
fuente
1

Práctica común o no, no creo que sea una buena idea obtener otra cosa que no sea un conjunto de exportaciones. Encuentro que la ejecución del código mediante el aprovisionamiento es simplemente confuso y limita la reutilización, porque la configuración ambiental y otras configuraciones variables hacen que el código derivado sea altamente dependiente del código de abastecimiento.

Es mejor dividir su aplicación en scripts más pequeños y autónomos, luego ejecutarlos como una serie de comandos. Esto facilitará la depuración, ya que puede ejecutar cada script autónomo en un shell interactivo, examinando archivos, registros, etc. entre cada invocación de comando. Su único script de aplicación grande se convierte en un script de control más simple que solo ejecuta una serie de comandos después de que haya finalizado la depuración.

Un grupo de scripts más pequeños y autónomos se encuentra con el problema más difícil de instalar.

Bruce Ediger
fuente
1

Una alternativa para obtener los scripts es simplemente llamarlos con argumentos. Si ya ha dividido la mayor parte de su funcionalidad en funciones de shell, es probable que ya esté muy cerca de poder hacerlo. El siguiente fragmento de Bash permite que cualquier función declarada en un script se use como un subcomando:

if [[ ${1:-} ]] && declare -F | cut -d' ' -f3 | fgrep -qx -- "${1:-}"
then "$@"
else main "$@" # Try the main function if args don't match a declaration.
fi

La razón para no hacerlo sourcees evitar la contaminación ambiental y de opciones.

merienda
fuente
0

Aquí está mi ejemplo de cómo un script bash grande puede dividirse en múltiples archivos y luego integrarse en un script resultante: https://github.com/zinovyev/bash-project

Yo uso un Makefilepara este propósito:

TARGET_FILE = "target.sh"
PRJ_SRC = "${PWD}/src/main.sh"
PRJ_LIB = $(shell ls -d ${PWD}/lib/*) # All files from ./lib

export PRJ_LIB

SHELL := /bin/env bash
all: define_main add_dependencies invoke_main

define_main:
    echo -e "#!/usr/bin/env bash\n" > ${TARGET_FILE}
    echo -e "function main() {\n" >> ${TARGET_FILE}
    cat "${PRJ_SRC}" | sed -e 's/^/  /g' >> ${TARGET_FILE}
    echo -e "\n}\n" >> ${TARGET_FILE}

invoke_main:
    echo "main \$$@" >> ${TARGET_FILE}

add_dependencies:
    for filename in $${PRJ_LIB[*]}; do cat $${filename} >> ${TARGET_FILE}; echo >> ${TARGET_FILE}; done
zinovyev
fuente