Estoy trabajando en un script de shell que construye un comando complejo a partir de variables, por ejemplo, de esta manera (con una técnica que aprendí de las preguntas frecuentes de Bash ):
#!/bin/bash
SOME_ARG="abc"
ANOTHER_ARG="def"
some_complex_command \
${SOME_ARG:+--do-something "$SOME_ARG"} \
${ANOTHER_ARG:+--with "$ANOTHER_ARG"}
Esta secuencia de comandos agrega dinámicamente los parámetros --do-something "$SOME_ARG"
y --with "$ANOTHER_ARG"
de some_complex_command
si se han definido estas variables. Hasta ahora esto está funcionando bien.
Pero ahora también quiero poder imprimir o registrar el comando cuando lo estoy ejecutando, por ejemplo, cuando mi script se ejecuta en modo de depuración. Entonces, cuando se ejecuta mi script some_complex_command --do-something abc --with def
, también quiero tener este comando dentro de una variable para poder, por ejemplo, iniciar sesión en el syslog.
Las preguntas frecuentes de Bash demuestran una técnica para usar la DEBUG
trampa y la $BASH_COMMAND
variable (por ejemplo, con fines de depuración) para este propósito. Lo intenté con el siguiente código:
#!/bin/bash
ARG="test string"
trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"
echo "Command was: ${COMMAND}"
Esto funciona, pero no expande las variables en el comando:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
Yo supongo que tengo que usar eval para ampliar echo "$ARG"
a echo test string
(al menos no he encontrado una manera sin eval
embargo). Lo siguiente funciona:
eval echo "Command was: ${COMMAND}"
Produce el siguiente resultado:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string
Pero no estoy realmente seguro si puedo usarlo de eval
manera segura de esta manera. He intentado explotar sin éxito algunas cosas:
#!/bin/bash
ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'
trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER
echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"
Parece manejar esto bien, pero tengo curiosidad si alguien más ve un problema que me he perdido.
Respuestas:
Una posibilidad es hacer una función de envoltura que al mismo tiempo imprima el comando y lo ejecute, de la siguiente manera:
Para que en tu script puedas hacer:
No hay necesidad de jugar con la trampa. El inconveniente es que agrega algunas
debug
palabras clave en todo el código (pero eso debería estar bien, es común tener esas cosas, como afirmaciones, etc.).Incluso puede agregar una variable global
DEBUG
y modificar ladebug
función de esta manera:Entonces puedes llamar a tu script como:
o
o solo
dependiendo de si desea tener la información de depuración o no.
Capitalicé la
DEBUG
variable porque debería tratarse como una variable de entorno.DEBUG
es un nombre trivial y común, por lo que podría chocar con otros comandos. Quizás llamarloGNIOURF_DEBUG
oMARTIN_VON_WITTICH_DEBUG
oUNICORN_DEBUG
si como los unicornios (y entonces es probable que como ponis también).Nota. En la
debug
función, he formateado cuidadosamente cada argumento con elprintf '%q'
fin de que la salida se escape y se cite correctamente para que sea reutilizable textualmente con una copia directa y pegar. También le mostrará exactamente lo que vio el shell, ya que podrá descubrir cada argumento (en el caso de espacios u otros símbolos divertidos). Esta función también utiliza la asignación directa con el-v
interruptor deprintf
para evitar subcapas innecesarias.fuente
&
. Tuve que moverlos a la función para que funcione: no es muy agradable, pero no creo que haya una mejor manera. Pero no máseval
, así que tengo eso para mí, lo cual es bueno :)eval "$BASH_COMMAND"
ejecuta el comandoprintf '%s\n' "$BASH_COMMAND"
imprime el comando exacto especificado, más una nueva línea.Si el comando contiene variables (es decir, si es algo así
cat "$foo"
), imprimir el comando imprime el texto de la variable. Es imposible imprimir el valor de la variable sin ejecutar el comando; piense en comandos comovariable=$(some_function) other_variable=$variable
.La forma más sencilla de obtener un seguimiento de la ejecución de un script de shell es establecer la
xtrace
opción de shell ejecutando el script comobash -x /path/to/script
o invocandoset -x
dentro del shell. La traza se imprime con error estándar.fuente
xtrace
, pero eso no me da mucho control. Intenté "set -x; ...; set + x", pero: 1) el comando "set + x" para deshabilitar xtrace también se imprime 2) No puedo prefijar la salida, por ejemplo, con una marca de tiempo 3) Puedo No lo registres en syslog.BASH_COMMAND
que contenga el comando expandido, porque en algún momento tiene que hacer una expansión variable en el comando de todos modos cuando lo está ejecutando :)