¿Cómo puedo pasar condicionalmente una subshell a través del 'tiempo'?

9

Tengo un script de configuración para un cuadro Vagrant donde solía medir pasos individuales con time. Ahora me gustaría habilitar o deshabilitar condicionalmente las mediciones de tiempo.

Por ejemplo, anteriormente una línea se vería así:

time (apt-get update > /tmp/last.log 2>&1)

Ahora pensé que simplemente podría hacer algo como esto:

MEASURE_TIME=true
[[ $MEASURE_TIME = true ]] && TIME="time --format=%e" || TIME=""

$TIME (apt-get update > /tmp/last.log 2>&1)

Pero esto no funcionará:

syntax error near unexpected token `apt-get'
`$TIME (apt-get update > /tmp/last.log 2>&1)'

¿Cuál es el problema aquí?

Der Hochstapler
fuente
3
Solo necesita alcanzar la velocidad objetivo para que funcione el condensador de flujo ;)
goldilocks
2
@goldilocks ¿Te refieres al imán ? ;)
un CVn

Respuestas:

13

Para poder medir el tiempo de un subnivel, necesita la time palabra clave , no de comandos.

La timepalabra clave, parte del lenguaje, solo se reconoce como tal cuando se ingresa literalmente y como la primera palabra de un comando (y en el caso de ksh93, el siguiente token no comienza con a -). Incluso ingresar "time"no funcionará y mucho menos $TIME(y se tomaría como una llamada al timecomando).

Puede usar alias aquí que se expanden antes de que se realice otra ronda de análisis (por lo que permitiría que el shell reconozca esa timepalabra clave):

shopt -s expand_aliases
alias time_or_not=
TIMEFORMAT=%E

MEASURE_TIME=true
[[ $MEASURE_TIME = true ]] && alias time_or_not=time

time_or_not (apt-get update > /tmp/last.log 2>&1)

La time palabra clave no toma opciones (excepto -pin bash), pero el formato se puede establecer con la TIMEFORMATvariable in bash. (la shoptparte también es bashespecífica, otras conchas generalmente no lo necesitan).

Stéphane Chazelas
fuente
Dijiste "Para poder cronometrar una subshell, necesitas la palabra clave time". Me pregunto si este comportamiento documentado en alguna parte.
Cuonglm
@cuonglm, verinfo -f bash --index-search=time
Stéphane Chazelas
3

Si bien una aliases una forma de hacerlo, esto también se puede hacer eval: es solo que no desea tanto evalla ejecución del comando como evalla declaración del comando .

Me gustan aliaslos: los uso todo el tiempo, pero me gustan más las funciones, especialmente su capacidad para manejar parámetros y que no necesariamente se deben expandir en la posición de comando como se requiere para los aliases.

Así que pensé que quizás también quieras probar esto:

_time() if   set -- "${IFS+IFS=\$2;}" "$IFS" "$@" && IFS='
';      then set -- "$1" "$2" "$*"; unset IFS
             eval "$1 $TIME ${3#"$1"?"$2"?}"
        fi

El $IFSbit es principalmente sobre $*. Es importante que el ( subshell bit )es también el resultado de un desarrollo del forro y así con el fin de ampliar los argumentos en una cadena de uso parsable I "$*" (no hacerlo eval "$@", por cierto, a menos que esté seguro de todos los argumentos se pueden unir bajo espacios) . El delimitador dividido entre args in "$*"es el primer byte in $IFS, por lo que podría ser peligroso proceder sin asegurarse de su valor. Por lo que la función guarda $IFS, lo configura a un \newline el tiempo suficiente para set ... "$*"dentro "$3", unsetes que, a continuación, restablece su valor si antes tenía uno.

Aquí hay una pequeña demostración:

TIME='set -x; time'
_time \( 'echo "$(echo any number of subshells)"' \
         'command -V time'                        \
         'hash time'                              \
      \) 'set +x'

Verá, pongo dos comandos en el valor de $TIMEallí: cualquier número está bien, incluso ninguno, pero asegúrese de que se escape y se cite correctamente, y lo mismo ocurre con los argumentos _time(). Todos se concatenarán en una sola cadena de comando cuando se ejecuten, pero cada \nargumento tiene su propia línea de conexión y, por lo tanto, se pueden distribuir con relativa facilidad. O bien, puede agruparlos todos en uno, si lo desea, y separarlos en \nlíneas electrónicas o punto y coma o lo que sea que tenga. Solo asegúrese de que un solo argumento represente un comando con el que se sienta cómodo al poner su propia línea en un script cuando lo llame. \(, por ejemplo, está bien, siempre que finalmente se siga con \). Básicamente las cosas normales.

Cuando evalse alimenta el fragmento anterior, se ve así:

+ eval 'IFS=$2;set -x; time (
echo "$(echo any number of subshells)"
command -V time
hash time
)
set +x'

Y, sus resultados parecen ...

SALIDA

+++ echo any number of subshells
++ echo 'any number of subshells'
any number of subshells
++ command -V time
time is a shell keyword
++ hash time
bash: hash: time: not found

real    0m0.003s
user    0m0.000s
sys     0m0.000s
++ set +x

El hasherror indica que no tengo /usr/bin/timeinstalado (porque no lo tengo) y commandnos permite saber a qué hora se está ejecutando. El seguimiento set +xes otro comando ejecutado después time (lo cual es posible) : es importante tener cuidado con los comandos de entrada al hacer evalcualquier cosa.

mikeserv
fuente
¿Alguna razón para no hacerlo _time() { eval "$TIME $@"; }? El uso de una función tiene el inconveniente de introducir un alcance diferente para las variables (no es un problema para las subcapas)
Stéphane Chazelas
@ StéphaneChazelas - debido a las citas en la "$@"expansión. Me gusta un solo argumento eval: de lo contrario, da miedo. Ummm ... ¿qué quieres decir con el alcance diferente? Yo creía que era sólo functionfunciones ...
mikeserv
evalune sus argumentos antes de ejecutar, que es lo que está tratando de hacer de una manera muy complicada. Quise decir que eso _time 'local var=1; blah'haría que eso fuera varlocal _time, o que _time 'echo "$#"'imprimiría la función $#de esa _timefunción, no la de la persona que llama.
Stéphane Chazelas
@ StéphaneChazelas - Aquí tengo dos pestañas completas para ti. Derecha: evalconcatena sus argumentos en espacios, que es, como usted dice, exactamente lo que hago aquí, aunque no fue una intención inicial. Voy a arreglar eso. evalEn "$*"contraste con esto, "$@"es un hábito que aprendí después de muchos encuentros con command not foundargs cuando se unieron en el lugar equivocado. En cualquier caso, aunque los args finalmente se ejecutan en la función, creo que es lo suficientemente simple como para expandirlos en la invocación. Es lo que haría con de "$#"todos modos.
mikeserv
+1 para la bonita pieza retorcida de
piratería informática