Como posso passar condicionalmente um subshell pelo 'time'?

9

Eu tenho um script de instalação para uma caixa do Vagrant em que costumava medir etapas únicas time. Agora eu gostaria de ativar ou desativar condicionalmente as medições de tempo.

Por exemplo, anteriormente uma linha seria semelhante a:

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

Agora eu pensei que poderia simplesmente fazer algo assim:

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

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

Mas isso não vai funcionar:

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

Qual é o problema aqui?

Der Hochstapler
fonte
3
Você só precisa atingir a velocidade alvo para que o capacitor de fluxo funcione;)
goldilocks
2
@goldilocks Você quer dizer o ímã ? ;)
um CVn

Respostas:

13

Para poder cronometrar um subshell, você precisa da time palavra - chave , não do comando.

A timepalavra-chave, parte do idioma, é reconhecida apenas como tal quando inserida literalmente e como a primeira palavra de um comando (e, no caso de ksh93, o próximo token não começa com a -). Mesmo a entrada "time"não funcionará muito menos $TIME(e seria tomada como uma chamada ao timecomando).

Você pode usar aliases aqui que são expandidos antes que outra rodada de análise seja realizada (para que o shell reconheça essa timepalavra-chave):

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)

A time palavra-chave não leva opções (exceto -pem bash), mas o formato pode ser definido com a TIMEFORMATvariável bash. (a shoptparte também é bashespecífica, outras conchas geralmente não precisam disso).

Stéphane Chazelas
fonte
Você disse "Para poder cronometrar um subshell, você precisa da palavra-chave time". Eu me pergunto esse comportamento documentado em algum lugar?
amigos estão dizendo sobre cuonglm
@cuonglm, veja #info -f bash --index-search=time
Stéphane Chazelas
3

Embora uma aliasseja uma maneira de fazer isso, isso também pode ser feito eval- é que você não deseja tanto evala execução do comando quanto evala declaração do comando .

Eu gosto de aliases - eu os uso o tempo todo, mas gosto mais de funções - especialmente sua capacidade de lidar com parâmetros e que eles não precisam necessariamente ser expandidos na posição de comando, conforme é necessário para aliases.

Então eu pensei que talvez você queira tentar isso também:

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

A parte $IFSé principalmente sobre $*. É importante que isso também( subshell bit ) seja o resultado de uma expansão do shell e, assim, para expandir os argumentos em uma string analisável que eu uso "$*" (nãoeval "$@" use , a propósito, a menos que você tenha certeza de que todos os argumentos podem ser unidos em espaços) . O delimitador de divisão entre args in "$*"é o primeiro byte in $IFSe, portanto, pode ser perigoso continuar sem garantir seu valor. Assim, a função salva $IFS, define-lo para um \newline tempo suficiente para set ... "$*"nos "$3", unseté ele, em seguida, redefine seu valor se ele já tinha um.

Aqui está uma pequena demonstração:

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

Você vê que eu coloquei dois comandos no valor de $TIMElá - qualquer número é bom - mesmo nenhum -, mas tenha certeza de que foi escapado e citado corretamente - e o mesmo vale para os argumentos _time(). Todos eles serão concatenados em uma única cadeia de comando quando forem executados - mas cada argumento recebe seu próprio \newline e, portanto, pode ser espalhado com relativa facilidade. Ou então, você pode agrupá-los todos em um, se quiser, e separá-los em \nlinhas de telefone ou ponto-e-vírgula ou o que tiver. Apenas certifique-se de que um único argumento represente um comando que você se sinta confortável em colocar sua própria linha em um script quando o chamar. \(, por exemplo, é bom, desde que seja seguido por \). Basicamente, as coisas normais.

Quando evalo snippet acima é alimentado, ele se parece com:

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

E seus resultados parecem ...

RESULTADO

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

O hasherro indica que não tenho um /usr/bin/timeinstalado (porque não tenho) e commandvamos saber a que horas está sendo executado. O trailing set +xé outro comando executado depois time (o que é possível) - é importante ter cuidado com os comandos de entrada ao executar evalqualquer coisa.

mikeserv
fonte
Alguma razão para não fazer isso _time() { eval "$TIME $@"; }? Utilizando uma função de tem o inconveniente da introdução de um âmbito diferente para as variáveis (não é uma questão para subcamadas)
Stéphane Chazelas
@ StéphaneChazelas - por causa das aspas na "$@"expansão. Eu gosto de um único argumento para eval- fica assustador de outra maneira. Ummm .... o que você quer dizer com o escopo diferente? Eu pensei que era apenas functionfunções ...
mikeserv
evaljunta seus argumentos antes de executar, e é isso que você está tentando fazer de uma maneira muito complicada. Eu quis dizer que _time 'local var=1; blah'tornaria isso varlocal para isso _time, ou que _time 'echo "$#"'imprimiria a função $#dessa _timefunção, não a do chamador.
Stéphane Chazelas
@ StéphaneChazelas - Eu tenho duas guias completas para você aqui. Certo - evalconcatena seus argumentos em espaços - que é, como você diz, exatamente o que faço aqui - embora não tenha sido uma intenção inicial. Eu vou consertar isso. evalpor outro lado "$*", "$@"é um hábito que adquiri depois de muitos command not founddesentendimentos com quando args foram juntados no lugar errado. De qualquer forma, embora os argumentos sejam finalmente executados na função, é simples o suficiente expandi-los na invocação, eu acho. É o que eu faria de "$#"qualquer maneira.
mikeserv
No entanto, +1 para o belo pedaço de hackery torcido ...
Stéphane Chazelas