Come posso passare condizionalmente una subshell attraverso 'time'?


9

Ho uno script di installazione per un box Vagrant in cui ero solito misurare i singoli passaggi time. Ora vorrei abilitare o disabilitare condizionalmente le misurazioni del tempo.

Ad esempio, in precedenza una linea sarebbe simile a:

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

Ora pensavo di poter semplicemente fare qualcosa del genere:

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

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

Ma questo non funzionerà:

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

Qual è il problema qui?


3
Devi solo raggiungere la velocità target per far funzionare il condensatore di flusso ;)
goldilocks il

2
@goldilocks Intendi il magnete ? ;)
un CVn il

Risposte:


13

Per essere in grado di cronometrare una sottoshell, è necessario il time parola chiave , senza comando.

La timeparola chiave, parte della lingua, viene riconosciuta come tale solo se inserita letteralmente e come prima parola di un comando (e nel caso di ksh93, quindi il token successivo non inizia con a -). Anche l'immissione "time"non funzionerà, figuriamoci $TIME(e verrebbe presa come una chiamata al timecomando).

È possibile utilizzare qui gli alias che vengono espansi prima dell'esecuzione di un altro ciclo di analisi (quindi consentirebbe alla shell di riconoscere quella timeparola chiave):

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 parola chiave non accetta opzioni (ad eccezione di -pin bash), ma il formato può essere impostato con la TIMEFORMATvariabile in bash. (la shoptparte è anche bashspecifica, altre shell generalmente non ne hanno bisogno).


Hai detto "Per poter cronometrare una subshell, hai bisogno della parola chiave time". Mi chiedo che questo comportamento abbia documentato un po 'dove?
cuonglm,

@cuonglm, vediinfo -f bash --index-search=time
Stéphane Chazelas il

3

Mentre un aliasè un modo per farlo, questo può essere fatto anche con eval- è solo che non vuoi tanto eseguire evall'esecuzione del comando quanto vuoi evalla dichiarazione del comando .

Mi piace aliases - li uso sempre, ma mi piacciono le funzioni meglio - specialmente la loro capacità di gestire i parametri e che non devono necessariamente essere espansi nella posizione di comando come richiesto per aliases.

Quindi ho pensato che forse avresti voluto provare anche questo:

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

La parte $IFSriguarda principalmente $*. È importante che ( subshell bit )sia anche il risultato di un'espansione della shell e quindi per espandere gli argomenti in una stringa analizzabile che uso "$*" (non eval "$@", a proposito, a meno che tu non sia sicuro che tutti gli arg possano essere uniti su spazi) . Il delimitatore diviso tra args in "$*"è il primo byte in $IFS, quindi potrebbe essere pericoloso procedere senza accertarne il valore. Quindi la funzione salva $IFS, lo imposta a un \newline abbastanza a lungo da set ... "$*"dentro "$3", unsetè tutto, quindi resetta il suo valore, se in precedenza aveva uno.

Ecco una piccola demo:

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

Vedete, ho messo due comandi nel valore di $TIMElì - ogni numero va bene - anche nessuno - ma assicuratevi che sia evitato e citato correttamente - e lo stesso vale per gli argomenti _time(). Saranno tutti concatenati in una singola stringa di comando quando vengono eseguiti, ma ogni arg ottiene la sua \newline e quindi possono essere distribuiti relativamente facilmente. Altrimenti puoi raggrupparli tutti in uno, se vuoi, e separarli su \newline o semi-due punti o what-have-you. Assicurati solo che un singolo argomento rappresenti un comando che ti sentiresti a tuo agio nel mettere la propria linea in uno script quando lo chiami. \(, ad esempio, va bene, purché alla fine venga seguito da \). Fondamentalmente le cose normali.

Quando evalviene alimentato lo snippet precedente, sembra che:

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

E i suoi risultati sembrano ...

PRODUZIONE

+++ 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' hasherrore indica che non ho /usr/bin/timeinstallato (perché non lo faccio) e commandfacci sapere a che ora è in esecuzione. Il trailing set +xè un altro comando eseguito dopo time (che è possibile) - è importante fare attenzione con i comandi di input quando si evalesegue qualcosa.


Qualche motivo per non farcela _time() { eval "$TIME $@"; }? L'uso di una funzione ha lo svantaggio di introdurre un ambito diverso per le variabili (non un problema per i subshells)
Stéphane Chazelas,

@ StéphaneChazelas - a causa delle virgolette "$@"nell'espansione. Mi piace un solo argomento eval: altrimenti fa paura. Ummm .... come intendi riguardo al diverso ambito però? Pensavo fossero solo functionfunzioni ...
Mikeserv,

evalsi unisce ai suoi argomenti prima di eseguire, che è quello che stai cercando di fare in un modo molto contorto. Volevo dire che _time 'local var=1; blah'avrebbe reso quel varlocale a quello _time, o che _time 'echo "$#"'avrebbe stampato il $#di quella _timefunzione, non quello del chiamante.
Stéphane Chazelas,

@ StéphaneChazelas - Ho qui due schede completate per te. Giusto - evalconcilia le sue argomentazioni sugli spazi - che è, come dici tu, esattamente quello che faccio qui - anche se non era intento iniziale. Lo riparero '. evaling "$*"al contrario "$@"è un'abitudine che ho preso dopo molti command not foundscontri con quando args sono stati uniti nel posto sbagliato. In ogni caso, sebbene gli arg alla fine vengano eseguiti nella funzione, penso che sia abbastanza semplice espanderli durante l'invocazione. È quello che farei "$#"comunque.
Mikeserv,

+1 per il bel pezzo di hackery contorto ...
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.