Comment passer conditionnellement un sous-shell à travers le «temps»?

9

J'ai un script d'installation pour une boîte Vagrant où j'ai utilisé pour mesurer des étapes simples avec time. Maintenant, je voudrais activer ou désactiver conditionnellement les mesures de temps.

Par exemple, auparavant, une ligne ressemblerait à:

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

Maintenant, je pensais que je pouvais simplement faire quelque chose comme ça:

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

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

Mais cela ne fonctionnera pas:

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

Quel est le problème ici?

Der Hochstapler
la source
3
Vous avez juste besoin d'atteindre la vitesse cible pour que le condensateur de flux fonctionne;)
goldilocks
2
@goldilocks Vous voulez dire l' aimant ? ;)
un CVn du

Réponses:

13

Pour pouvoir chronométrer un sous-shell, vous avez besoin du time mot - clé , pas de la commande.

Le timemot-clé, qui fait partie du langage, n'est reconnu comme tel que lorsqu'il est entré littéralement et comme le premier mot d'une commande (et dans le cas de ksh93, alors le jeton suivant ne commence pas par a -). Même entrer "time"ne fonctionnera pas encore moins $TIME(et serait plutôt considéré comme un appel à la timecommande).

Vous pouvez utiliser ici des alias qui sont développés avant qu'un autre cycle d'analyse ne soit effectué (donc laissez le shell reconnaître ce timemot - clé):

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)

Le time mot - clé ne prend pas d'options (sauf pour -pin bash), mais le format peut être défini avec la TIMEFORMATvariable in bash. (la shoptpièce est également bashspécifique, les autres coques n'ont généralement pas besoin de cela).

Stéphane Chazelas
la source
Vous avez dit "Pour pouvoir chronométrer un sous-shell, vous avez besoin du mot-clé time". Je me demande ce comportement documenté certains où?
cuonglm
@cuonglm, voirinfo -f bash --index-search=time
Stéphane Chazelas
3

Bien que an aliassoit une façon de le faire, cela peut être fait evalaussi - c'est juste que vous ne voulez pas tant evall'exécution de evalla commande que la déclaration de commande .

J'aime aliases - je les utilise tout le temps, mais j'aime mieux les fonctions - en particulier leur capacité à gérer les paramètres et qu'elles ne doivent pas nécessairement être étendues en position de commande comme cela est requis pour aliases.

J'ai donc pensé que vous voudriez peut-être essayer aussi:

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

Le $IFSbit concerne principalement $*. Il est important que le ( subshell bit )soit également le résultat d'une expansion du shell et donc afin d'étendre les arguments dans une chaîne analysable que j'utilise "$*" (ne le faites pas eval "$@", d'ailleurs, sauf si vous êtes certain que tous les arguments peuvent être joints sur des espaces) . Le délimiteur divisé entre les arguments dans "$*"est le premier octet $IFS, et il peut donc être dangereux de continuer sans s'assurer de sa valeur. Ainsi , la fonction permet d' enregistrer $IFS, il met un \newline assez longtemps pour set ... "$*"en "$3", unsets, puis remet à zéro sa valeur si elle avait déjà un.

Voici une petite démo:

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

Vous voyez, j'ai mis deux commandes dans la valeur de $TIMEthere - n'importe quel nombre est correct - même aucun - mais assurez-vous qu'il est échappé et cité correctement - et il en va de même pour les arguments _time(). Ils seront tous concaténés en une seule chaîne de commande lors de leur exécution - mais chaque argument obtient sa propre ligne \nélectronique et peut donc être étalé relativement facilement. Ou bien, vous pouvez les \nregrouper en un seul, si vous le souhaitez, et les séparer sur des lignes électroniques ou des points-virgules ou ce que vous avez. Assurez-vous simplement qu'un seul argument représente une commande que vous seriez à l'aise de mettre sur sa propre ligne dans un script lorsque vous l'appelez. \(, par exemple est très bien, tant qu'il est finalement suivi de \). Fondamentalement, le truc normal.

Lorsque evall'extrait ci-dessus est alimenté, il ressemble à ceci:

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

Et ses résultats ressemblent à ...

PRODUCTION

+++ 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

L' hasherreur indique que je n'ai pas /usr/bin/timeinstallé (parce que je n'ai pas) et commandlaissez-nous savoir quelle heure est en cours d'exécution. Le suivi set +xest une autre commande exécutée après time (ce qui est possible) - il est important d'être prudent avec les commandes d'entrée lors de evaln'importe quoi.

mikeserv
la source
Une raison pour ne pas l'avoir fait _time() { eval "$TIME $@"; }? L'utilisation d'une fonction a l'inconvénient d'introduire un champ d'application différent pour les variables (pas un problème pour les sous-coquilles)
Stéphane Chazelas
@ StéphaneChazelas - à cause des citations dans l' "$@"extension. J'aime un seul argument pour eval- ça devient effrayant sinon. Ummm .... comment voulez-vous dire sur la portée différente cependant? Je pensais que c'était juste des functionfonctions ...
mikeserv
evalrejoint ses arguments avant d'exécuter, ce que vous essayez de faire de manière très compliquée. Je voulais dire que _time 'local var=1; blah'ferait ce varlocal que _time, ou qui _time 'echo "$#"'imprimerait la $#de cette _timefonction, et non celui de l'appelant.
Stéphane Chazelas
@ StéphaneChazelas - J'ai deux onglets complets pour vous ici. À droite - evalconcède ses arguments sur les espaces - ce qui est, comme vous le dites, exactement ce que je fais ici - même si ce n'était pas une intention initiale. Je vais arranger ça. evaling "$*"par opposition à "$@"est une habitude que j'ai prise après de nombreux command not foundrodages avec quand les arguments ont été joints au mauvais endroit. Dans tous les cas, bien que les arguments soient finalement exécutés dans la fonction, il est assez simple de les développer lors de l'invocation, je pense. C'est ce que je ferais de "$#"toute façon.
mikeserv
+1 pour le joli piratage tordu ...
Stéphane Chazelas