Wie kann ich eine Unterschale bedingt durch "Zeit" führen?

9

Ich habe ein Setup-Skript für eine Vagrant-Box, mit der ich einzelne Schritte gemessen habe time. Jetzt möchte ich die Zeitmessungen bedingt aktivieren oder deaktivieren.

Zuvor sah eine Zeile beispielsweise folgendermaßen aus:

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

Jetzt dachte ich, ich könnte einfach so etwas machen:

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

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

Aber das wird nicht funktionieren:

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

Was ist das Problem hier?

Der Hochstapler
quelle
3
Sie müssen nur die Zielgeschwindigkeit erreichen, damit der Flusskondensator funktioniert;)
Goldlöckchen
2
@goldilocks Du meinst den Magneten ? ;)
ein CVn

Antworten:

13

Um eine Subshell zeitlich festlegen zu können, benötigen Sie das time Schlüsselwort und nicht den Befehl.

Das timeSchlüsselwort, ein Teil der Sprache, wird nur dann als solches erkannt, wenn es wörtlich und als erstes Wort eines Befehls eingegeben wird (und im Fall von ksh93beginnt das nächste Token nicht mit a -). Selbst die Eingabe "time"funktioniert nicht, geschweige denn $TIME(und wird stattdessen als Aufruf des timeBefehls verstanden).

Sie können hier Aliase verwenden, die erweitert werden, bevor eine weitere Analyse durchgeführt wird (damit die Shell dieses timeSchlüsselwort erkennt ):

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)

Das time Schlüsselwort akzeptiert keine Optionen (außer -pin bash), aber das Format kann mit der TIMEFORMATVariablen in festgelegt werden bash. (Das shoptTeil ist auch bash-spezifisch, andere Muscheln brauchen das im Allgemeinen nicht).

Stéphane Chazelas
quelle
Sie sagten "Um eine Subshell zeitlich festlegen zu können, benötigen Sie das Schlüsselwort time". Ich frage mich, ob dieses Verhalten irgendwo dokumentiert hat.
Cuonglm
@ Cuonglm, sieheinfo -f bash --index-search=time
Stéphane Chazelas
3

Während ein aliaseine Möglichkeit , es zu tun ist, dies kann mit gemacht werden evalals auch - es ist nur , dass Sie nicht so viel wollen Sie evaldie Befehlsausführung , wie Sie mögen evalden Befehl Erklärung .

Ich mag aliases - ich benutze sie die ganze Zeit, aber ich mag Funktionen besser - insbesondere ihre Fähigkeit, mit Parametern umzugehen, und dass sie nicht unbedingt in der Befehlsposition erweitert werden müssen, wie es für aliases erforderlich ist .

Also dachte ich, vielleicht möchten Sie das auch versuchen:

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

Das $IFSbisschen geht hauptsächlich um $*. Es ist wichtig , dass das ( subshell bit )ist auch das Ergebnis einer Shell - Erweiterung und so um die Argumente in einen parsable String I Gebrauch zu erweitern "$*" (nicht eval "$@", nebenbei gesagt, es sei denn , Sie sind sie sicher alle der args auf Räume verbunden werden) . Das geteilte Trennzeichen zwischen args in "$*"ist das erste Byte in $IFS, und daher kann es gefährlich sein, fortzufahren, ohne seinen Wert sicherzustellen. So ist die Funktion speichert $IFS, setzt er auf eine \nlange genug ewline zu set ... "$*"in "$3", unsetes s, dann wird ihr Wert setzt , wenn sie zuvor einen hatte.

Hier ist eine kleine Demo:

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

Sie sehen, ich habe zwei Befehle in den Wert von $TIMEdort eingefügt - jede Zahl ist in Ordnung - sogar keine -, aber stellen Sie sicher, dass sie maskiert und richtig zitiert wird - und dasselbe gilt für die Argumente dazu _time(). Sie werden alle bei ihrer Ausführung zu einer einzigen Befehlszeichenfolge verkettet - aber jedes \nArgument erhält seine eigene Ewline und kann daher relativ einfach verteilt werden. Oder Sie können sie alle in einem zusammenfassen, wenn Sie \nmöchten , und sie in Ewlines oder Semikolons oder was-haben-Sie trennen . Stellen Sie nur sicher, dass ein einzelnes Argument einen Befehl darstellt, den Sie beim Aufrufen in einem Skript in eine eigene Zeile einfügen möchten. \(Zum Beispiel ist in Ordnung, solange es schließlich mit folgt \). Grundsätzlich das normale Zeug.

Wenn evaldas obige Snippet gefüttert wird, sieht es so aus:

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

Und die Ergebnisse sehen aus wie ...

AUSGABE

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

Der hashFehler zeigt an, dass ich keine /usr/bin/timeinstalliert habe (weil ich keine habe) und commandlässt uns wissen, welche Zeit läuft. Das Trailing set +xist ein weiterer Befehl, der nach ausgeführt wird time (was möglich ist). Es ist wichtig, bei Eingabebefehlen vorsichtig zu sein, wenn evalSie etwas eingeben .

mikeserv
quelle
Gibt es einen Grund, es nicht zu schaffen _time() { eval "$TIME $@"; }? Die Verwendung einer Funktion hat den Nachteil, dass ein anderer Bereich für Variablen eingeführt wird (kein Problem für Unterschalen)
Stéphane Chazelas,
@ StéphaneChazelas - wegen der Anführungszeichen in der "$@"Erweiterung. Ich mag ein einziges Argument dazu eval- sonst wird es beängstigend. Ähm ... wie meinst du das mit dem unterschiedlichen Umfang? Ich dachte, das function
wären
evalVerbindet seine Argumente vor der Ausführung, was Sie auf sehr verschlungene Weise versuchen. Ich meinte, das _time 'local var=1; blah'würde das varlokal machen _time, oder das _time 'echo "$#"'würde die $#dieser _timeFunktion drucken , nicht die des Anrufers.
Stéphane Chazelas
@ StéphaneChazelas - Ich habe hier zwei Tab-Vervollständigungen für Sie. Richtig - evalkonzentriert seine Argumente auf Räume - was, wie Sie sagen, genau das ist, was ich hier mache - obwohl es keine ursprüngliche Absicht war. Ich werde das beheben. evalIm "$*"Gegensatz zu "$@"ist eine Gewohnheit, die ich nach vielen Run-In's aufgegriffen habe, command not foundals Args an der falschen Stelle zusammengefügt wurden. Obwohl die Argumente letztendlich in der Funktion ausgeführt werden, ist es auf jeden Fall einfach genug, sie beim Aufruf zu erweitern, denke ich. Damit würde ich "$#"sowieso machen.
Mikeserv
+1 für das schöne verdrehte Stück Hackery ...
Stéphane Chazelas