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_commandsi 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 DEBUGtrampa y la $BASH_COMMANDvariable (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 evalembargo). 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 evalmanera 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
debugpalabras 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
DEBUGy modificar ladebugfunció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
DEBUGvariable porque debería tratarse como una variable de entorno.DEBUGes un nombre trivial y común, por lo que podría chocar con otros comandos. Quizás llamarloGNIOURF_DEBUGoMARTIN_VON_WITTICH_DEBUGoUNICORN_DEBUGsi como los unicornios (y entonces es probable que como ponis también).Nota. En la
debugfunció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-vinterruptor deprintfpara 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
xtraceopción de shell ejecutando el script comobash -x /path/to/scripto invocandoset -xdentro 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_COMMANDque 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 :)