Este es un seguimiento de mi pregunta aquí: Cómo escribir la función bash para imprimir y ejecutar el comando cuando el comando tiene argumentos con espacios o cosas para expandir
Supongamos que tengo esta función para imprimir y ejecutar un comando almacenado en una matriz:
# Print and run the cmd stored in the passed-in array print_and_run() { echo "Running cmd: $*" # run the command by calling all elements of the command array at once "$@" }Esto funciona bien:
cmd_array=(ls -a /) print_and_run "${cmd_array[@]}"Pero esto no funciona:
cmd_array=(ls -a / | grep "home") print_and_run "${cmd_array[@]}" Error: syntax error near unexpected token `|' :
eRCaGuy_hello_world/bash$ ./print_and_run.sh ./print_and_run.sh: line 55: syntax error near unexpected token `|' ./print_and_run.sh: line 55: `cmd_array=(ls -a / | grep "home")' ¿Cómo puedo hacer que este concepto funcione con el operador de tubería ( | ) en el comando?
Si desea tratar un elemento de matriz que contiene solo | como una instrucción para generar una canalización, puede hacerlo. No lo recomiendo , significa que tiene un riesgo de seguridad si no verifica que las variables en su cadena no pueden consistir solo en un solo carácter de canalización, pero es posible.
A continuación, creamos un sigilo aleatorio "$pipe" de un solo uso para que el ataque sea más difícil. Si no está dispuesto a hacer eso, cambie [[ $arg = "$pipe" ]] a [[ $arg = "|" ]] .
# generate something random to make an attacker's job harder pipe=$(uuidgen) # use that randomly-generated sigil in place of | in our array cmd_array=( ls -a / "$pipe" grep "home" ) exec_array_pipe() { local arg cmd_q local -a cmd=( ) while (( $# )); do arg=$1; shift if [[ $arg = "$pipe" ]]; then # log an eval-safe copy of what we're about to run printf -v cmd_q '%q ' "${cmd[@]}" echo "Starting pipeline component: $cmd_q" >&2 # Recurse into a new copy of ourselves as a child process "${cmd[@]}" | exec_array_pipe "$@" return fi cmd+=( "$arg" ) done printf -v cmd_q '%q ' "${cmd[@]}" echo "Starting pipeline component: $cmd_q" >&2 "${cmd[@]}" } exec_array_pipe "${cmd_array[@]}"Vea esto ejecutándose en un entorno limitado en línea en https://ideone.com/IWOTfO
La dirección general es que no. No almacena toda la línea de comando para imprimirla más tarde, y esta no es la dirección que debe tomar.
La solución "mala" es usar eval .
La solución "buena" es almacenar el literal '|' carácter dentro de la matriz (o alguna mejor representación de la misma) y analice la matriz, extraiga las partes de la tubería y ejecútelas. Esto lo presenta Charles en la otra respuesta asombrosa. Simplemente está reescribiendo el analizador que ya existe en el shell. Requiere un trabajo significativo, y expandirlo requerirá un trabajo significativo.
El resultado final es que está reimplementando partes de shell dentro de shell. Básicamente escribiendo un intérprete de shell en shell. En este punto, puede considerar tomar las fuentes de Bash e implementar una nueva opción shopt -o print_the_command_before_executing en las fuentes, que podría ser más simple.
Sin embargo, creo que el objetivo final es brindar a los usuarios una forma de ver lo que se está ejecutando. Propondría abordarlo como lo hace .gitlab-ci.yml con script: declaraciones. Si desea inventar su propio lenguaje con soporte de "depuración", hágalo en lugar de medias tintas. Considere el siguiente archivo YAML:
- ls -a / | grep "home" - echo other commands - for i in "stuff"; do echo "$i"; done - | for i in "stuff"; do echo "$i" doneLuego el siguiente "corredor":
import yaml import shlex import os import sys script = [] input = yaml.safe_load(open(sys.argv[1], "r")) for line in input: script += [ "echo + " + shlex.quote(line).replace("\n", "<newline>"), # some unicode like  would look nice line, ] os.execvp("bash", ["bash", "-c", "\n".join(script)])Ejecutar el corredor da como resultado:
+ ls -a / | grep "home" home + echo other commands other commands + for i in "stuff"; do echo "$i"; done stuff + for i in "stuff"; do<newline> echo "$i"<newline>done<newline> stuffEsto ofrece una mayor flexibilidad y es bastante simple, admite cualquier construcción de caparazón con facilidad. Puede probar gitlab-ci/cd en su repositorio y leer los documentos .
El formato YAML es solo un ejemplo del formato de entrada. El uso de comentarios especiales como # --- cut --- entre partes y la extracción de cada parte con el analizador permitirá ejecutar shellcheck sobre el script. En lugar de generar un script con declaraciones de echo , puede ejecutar Bash de forma interactiva, imprimir la parte que se ejecutará y luego "alimentar" la parte que se ejecutará a Bash interactivo. ¿Esto permitirá conservar $? .
De cualquier manera, con una solución "buena", terminas con un analizador personalizado.
Haz esto en su lugar. Funciona.
print_and_run() { echo "Running cmd: $1" eval "$1" }Ejemplo de uso:
cmd='ls -a / | grep -C 9999 --color=always "home"' print_and_run "$cmd"Producción:
Running cmd: ls -a / | grep -C 9999 --color=always "home" (rest of output here, with the word "home" highlighted in red)