HPR4678: Tempo Decorrido de Alta Resolução em Scripts Shell

Por Whiskeyjack08/07/2026 às 00:0049 visualizações
Foto: CC BY-SA / Hacker Public Radio
🎙 Whiskeyjack · Hacker Public Radio

Este programa foi marcado como "Limpo" pelo anfitrião.

01 Introdução

Neste episódio, eu vou descrever como calcular o tempo decorrido em scripts bash ou outros scripts de shell.

Embora isso possa parecer uma coisa muito simples e básica de fazer, há um aspecto ligeiramente mais complexo nisso se você quiser calcular o tempo decorrido com uma resolução maior que um segundo.

02

Existem muitas razões para calcular o tempo decorrido em um script de shell.

Por exemplo, você pode querer simplesmente relatar quanto tempo levou para uma operação ser executada.

Outra razão pode ser que você está tentando acelerar um script e precisa calcular dados de referência para ver como diferentes métodos alternativos se comportam.

03

O que pode parecer uma tarefa simples se torna um pouco mais complicada se você quiser executá-la em múltiplos sistemas operacionais diferentes, mesmo que todos sejam relacionados ao Unix, como veremos.

--------------------

04 Sistemas Operacionais Testados

Para os fins deste episódio, eu realizei testes na versão atual dos seguintes sistemas operacionais.

Alma

Alpine

Debian

FreeBSD

OpenBSD

Raspberry Pi

OpenSuse

Ubuntu 2604

Alma é uma cópia próxima do Red Hat que podemos considerar como representando distribuições de estilo Red Hat.

--------------------

05 Método Simples de Temporização de Baixa Resolução

Começarei com o método simples e óbvio antes de descrever os menos óbvios.

Isso utiliza o comando data para obter o horário atual em segundos desde o epoch Unix.

Isso é simplesmente

data '+%s'

06

Salve isso em uma variável usando o método de sua preferência.

Por exemplo.

próxima linha=$(data '+%s')

07

Em seguida, faça as operações que desejar cronometrar.

Use o comando data para obter novamente o horário atual.

finaltime=$(data '+%s')

08

Agora, simplesmente subtraia o tempo de início do tempo final usando a aritmética de shell.

Isso deve ser muito óbvio e básico.

--------------------

09 Sincronização de Resolução Mais Alta

No entanto, se desejarmos medir o tempo com precisão superior a um segundo.

Precisamos fazer duas coisas.

O primeiro é obter o horário atual com um grau de precisão mais elevado.

O segundo é realizar os cálculos com um grau de precisão mais elevado.

10

Infelizmente, a precisão do tempo padrão para shells POSIX parece ser de 1 segundo.

Algumas conchas oferecem maior precisão, mas outras não.

Além disso, a aritmética de shell padrão utiliza inteiros, o que limita os cálculos a 1 segundo de precisão.

--------------------

Variável de Shell de Alta Resolução Bash 11

Felizmente, o Bash é um que oferece uma data de alta precisão.

Se você estiver usando o Bash 5.0 ou versão mais recente, há uma variável de shell chamada EPOCHREALTIME que oferece o tempo desde o epoch Unix (ou seja, desde o primeiro de janeiro de 1970, às 00:00:00 UTC) em segundos, com até 6 casas decimais de precisão.

12

Exemplo

`echo $EPOCHREALTIME`

1779634800.184926

13

Isso está relacionado à variável Bash semelhante chamada EPOCHSECONDS, que fornece o número de segundos desde o epoch Unix.

14

Exemplo

`echo $EPOCHSECONDS`

1779634800

15

Se você estiver usando bash, medir o tempo é muito simples.

--------------------

Mas será que é realmente Bash?

Seu script está realmente usando bash?

O Debian e suas derivadas, na verdade, têm duas shells.

A primeira, a shell interativa é o bash.

A segunda, a shell não interativa é o dash, que significa "shell almquist do Debian".

17

Se você abrir um terminal, você terá o bash.

Se o seu script começar com uma linha "bin/bash" shebang, você terá o bash.

No entanto, se seu script começa com uma linha de "bin/sh", você obtém o dash.

Algumas pessoas se encontram pegas por isso quando tentam algo em um terminal, mas descobrem que não funciona em seu script que começou com "bin/sh".

18

Muitas outras, mas nem todas, distribuições Linux usam bash para ambos os shells interativos e não interativos, então "bin/sh" e "bin/bash" funcionam da mesma maneira com essas.

Então, se você pretende usar bash, certifique-se de que seu script chame bash na primeira linha.

--------------------

19 A Variável SHELL

Então, como um script pode dizer que shell ele está rodando?

Existe uma variável de shell chamada "SHELL" que dirá o nome do shell.

Bem, mais ou menos.

20

No Debian e derivados, o "SHELL" dirá "bash" independentemente de o shell real ser bash ou dash.

Em alguns outros sistemas operacionais, o "SHELL" simplesmente dirá "sh", mesmo que seja algo completamente diferente.

Então, precisamos fazer alguns níveis adicionais de verificação para ver o que temos.

21

Para começar, aqui está o que cada uma das distribuições de teste relata como "SHELL".

Alma: bash

Alpine: sh

Debian: bash

FreeBSD: sh

OpenBSD: ksh

Raspberry-Pi: bash

Suse: bash

Ubuntu 26.04: bash

--------------------

22 Bash vs. Dash

Primeiro, vamos tentar ver quais são bash e quais são dash.

A primeira coisa que podemos verificar é a variável de shell BASH_VERSION.

23

Exemplo

A tradução do texto fornecido para o português brasileiro é: ```bash echo $BASH_VERSION ```

Se o shell for bash, ele informará uma string de versão.

Se o shell não for bash, então ele retornará um valor vazio.

24

Usando este teste, podemos ver que Alma e Opensuse realmente estão utilizando bash.

No entanto, precisamos verificar o Debian, Raspberry Pi e Ubuntu quando rodam em um script "sh".

Para verificar isso, podemos usar o comando "which" para ver o que "sh" realmente é.

25

Exemplo

O texto fornecido não parece ser um texto jornalístico, mas sim uma sequência de comandos de shell. A tradução para o português brasileiro seria: "Ecoa $(lista de arquivos -l $(localização do sh) | reversa | corta -d' ' -f1 | corta -d '/' -f1 | reversa)"

26

que mostra" nos o caminho para "sh

No entanto, isso é um link, então precisamos usar

"ls -l" para encontrar o executável real.

"rev" inverte a string.

27

"cut" pega o primeiro elemento separado por espaços.

O segundo

cut" pega o primeiro elemento separado pelos caracteres "/

O final "rev" pega essa string e a inverte novamente para obter a ordem correta.

28

No caso do Debian, Raspberry Pi e Ubuntu, isso nos diz que é "dash".

--------------------

29 OpenBSD

O OpenBSD relata sua shell como "ksh", que significa Shell Korn.

É de fato a Shell Korn, então simplesmente deixamos como está.

--------------------

30 Alpine e Freebsd

Em seguida, temos o Alpine Linux e o Freebsd, que ambos relatam como "sh".

No caso do Freebsd, não parece haver mais para onde ir, do que eu saiba.

É simples "sh".

É uma shell POSIX básica que parece ser semelhante à shell Unix original, a Shell Bourne.

Versões mais antigas do FreeBSD usavam uma shell diferente conhecida como tsch (a Shell C), mas eu não a testei, então a ignorarei aqui.

31

Com o Alpine Linux, no entanto, podemos obter a shell real usando o mesmo método que usamos para o Debian Linux.

Isso é relatado como sendo "busybox".

32

Busybox é uma shell limitada destinada a ser usada em sistemas embarcados.

O Alpine era originalmente uma distribuição embarcada, mas algumas pessoas começaram a usá-lo para contêineres.

O Alpine é Linux, mas não é GNU/Linux, e há vários pontos que podem te enganar se você não estiver ciente deles.

Então, seja extra cuidadoso se estiver usando para qualquer coisa, e teste tudo.

--------------------

Resumo dos tipos de shells atuais

Aqui está a nossa lista revisada com os shells reais utilizados ao solicitar "sh", até onde podemos determinar.

Alma: bash

Alpine: busybox

Debian: dash

FreeBSD: sh

OpenBSD: ksh

Raspberry-Pi: dash

Suse: bash

Ubuntu 2604: dash

34

Existem outras shells, mas nenhuma delas é a shell padrão para nenhuma das distribuições na nossa lista, então eu não as testei.

--------------------

35 Soluções para Medir o Tempo

Agora precisamos encontrar soluções para bash, dash, ksh, sh e busybox.

--------------------

36 Bash

Para bash, podemos simplesmente usar EPOCHREALTIME, como mencionado acima.

--------------------

37 Dash

Para hachur, podemos usar o comando de data.

Esta é uma método muito convencional, e provavelmente a primeira resposta que qualquer pessoa daria para esta situação.

No entanto, embora funcione na maioria dos casos, não funcionará em todos, então não é uma solução universal.

38

Para usar a data, simplesmente chamamos ela com a string de formato correta.

Isso utiliza %s para obter segundos desde o epoch, e %N para obter nanosegundos do segundo atual.

Se você colocar um separador decimal entre os dois, ele aparecerá na saída.

Você pode usar o separador decimal correto para o seu locale, mas eu não vou entrar nisso aqui.

Em vez disso, vou apenas assumir um ponto ou vírgula.

39

Exemplo

Formato de data '+%s.%N'

1779634800.358916385

--------------------

40 problemas com datas em Alpine e OpenBSD

A função date funciona para bash, dash e sh em FreeBSD.

No entanto, não funciona para ksh em OpenBSD, nem para busybox em Alpine.

41

Com busybox em Alpine, ele simplesmente ignora o especificador de formato %N e imprime apenas o epoch em segundos seguido pelo separador decimal.

=

Exemplo

date '+%s.%N'

1779634800.

42

Com ksh no OpenBSD, ele imprime o epoch em segundos seguido pelo separador decimal e, em seguida, o %N como um N literal.

A data '+%s.%N'

1779634800.N

43

Felizmente, temos alternativas para esses dois casos.

--------------------

44 OpenBSD

O OpenBSD tem a utilidade "ts" ou timestamp instalada por padrão.

O ts imprime um carimbo de data e hora na frente de cada linha que ele recebe da entrada padrão.

Não vou entrar em detalhes sobre todos os aspectos do ts aqui, deixarei isso para outra pessoa.

Em vez disso, vou focar em como usá-lo para nossos propósitos específicos aqui.

45

Precisamos fornecer um especificador de formato para `ts`, que, neste caso, é `"%.s"`.

Também precisamos fornecer algo para a entrada padrão, caso contrário, `ts` ficará ali esperando pela entrada.

Então, o que precisamos fazer é ecoar nada através de um pipe para `ts` enquanto também fornecemos ao `ts` o especificador de formato correto.

46

Exemplo:

`echo | ts "%.s"`

47

Isso sairá o tempo de época em segundos com precisão de seis casas decimais.

`ts` está instalado em OpenBSD e Freebsd por padrão e pode ser usado em ambos.

Também pode ser instalado em muitos outros distribuições.

--------------------

48 Busybox no Alpine

Nenhum dos métodos discutidos até agora funcionará para busybox no Alpine.

No entanto, há uma maneira, mas é um pouco óbvia e um pouco hack.

49

O Busybox inclui um comando chamado "adjtimex".

Isso é normalmente usado para ajustar o tempo de hardware.

No entanto, se for executado sem argumentos, ele relatará as configurações atuais.

50

Esses incluem o tempo atual em segundos, e em outro campo, o tempo em microsegundos.

São relatados como pares de valores-chave.

Então, o que precisamos fazer é o seguinte:

51

Execute o adjtimex

Capture a saída.

Procure por "time.tv_sec"

Procure por "time.tv_usec"

Use o comando cut para extrair o valor de tempo em cada caso.

Use o comando tr para remover espaços extras em cada caso.

Combine os dois em uma string com um separador decimal entre eles.

52

Isso leva um total de 4 linhas de script de shell.

Eu vou descrever brevemente aqui, veja as notas do show para detalhes.

53

Primeiro, queremos capturar a saída de adjtimex em uma única operação.

Execute adjtimex e redirecione a saída através de grep para capturar linhas contendo "time.tv_".

E salve isso em uma variável.

# Extraia o tempo atual de alta resolução de adjtimex.

# Queremos dois pares chave-valor, identificados por time.tv_sec e time.tv_usec.

A variável `tvals` é atribuída ao resultado da comando `adjtimex` filtrado pela expressão regular `"time.tv_"`.

54

O próximo passo é ecoar o conteúdo dessa variável e passar por `grep`, `cut` e `tr` para obter primeiro os segundos e depois os microsegundos, removendo também espaços extras.

Guarde esses dois em variáveis separadas.

"tempo.tv_sec" é o tempo em segundos desde o epoch.

"tempo.tv_usec" é o número de microsegundos no segundo atual.

# Obtenha o tempo desde a época Unix em segundos e microsegundos.

tempo_secundario=$(echo "$tvals" | grep "time.tv_sec" | cut -d: -f2 | tr -d " ")

tempo_usado=$(echo "$tvals" | grep "time.tv_usec" | cut -d: -f2 | tr -d " ")

55

O Adjtimex não completa com zeros a valor de tempo em microsegundos, então precisamos cuidar disso usando printf antes de anexar ao valor em segundos. Não precisamos fazer isso com data, onde o caractere de formato %N faz isso automaticamente.

Nesta instância, a string de formato printf é '%06d'

padusec=$( printf '%06d' $timeusec )

Agora, combine esses em um único número com um separador decimal usando a concatenação simples de strings.

# Combine-os em um único número.

timehires="$timesec"."$padusec"

--------------------

56 Resumo dos Métodos

Vamos resumir onde estamos até agora em termos de métodos que podemos usar para obter o tempo atual como um número de alta resolução.

Alma: utilize EPOCHREALTIME ou data.

Debian (bash): utilize EPOCHREALTIME ou data.

Raspberry-Pi (bash): utilize EPOCHREALTIME ou data.

Ubuntu2604 (bash): utilize EPOCHREALTIME ou data.

Suse: utilize EPOCHREALTIME ou data.

Debian (dash): utilize data.

Raspberry-Pi (dash): utilize data.

Ubuntu2604 (dash): utilize data.

Alpine: utilize adjtimex e analise a saída.

FreeBSD: use data ou ts.

OpenBSD: use ts.

--------------------

57 Outras alternativas.

Existem algumas alternativas que ainda não discutimos.

58 Bash com Dash.

No caso de Debian, Raspberry Pi e Ubuntu com Dash, como o bash está disponível, é possível escrever um script bash separado que simplesmente ecoa EPOCHREALTIME e, em seguida, chame-o do script Dash, capturando a saída.

Embora funcione, provavelmente não há muito sentido nisso.

Se você puder confiar na presença do bash, então basta alterar a primeira linha do script e torná-lo um script bash.

Adicionando Pacotes ao Alpine

A ferramenta ts ou timestamp é uma utilidade Unix comum que pode ser instalada se não estiver presente por padrão.

Isso realmente produz timestamps de alta resolução no Alpine.

No Alpine Linux, isso vem como parte do pacote "moreutils".

Para adicionar o pacote, use o seguinte:

sudo apk add moreutils

echo | ts "%.s"

1779634800.959948

60

Você também pode adicionar o GNU coreutils, que fornecerá um comando de data de alta resolução que funciona como nos outros exemplos.

Para adicionar o pacote, use o seguinte:

sudo apk add coreutils

data '+%s.%N'

1779634800.212897332

61

Se você puder instalar mais pacotes no seu sistema Alpine, provavelmente um dos dois acima será preferível à análise da saída de adjtimex.

62 Programas de Carimbo de Tempo Personalizados

Você também pode escrever um programa muito curto em Python, Perl, Tcl ou alguma outra linguagem e fazer com que ele saia o tempo atual em época.

Eu não discutirei isso aqui, porém.

--------------------

Calculando diferenças de tempo

A aritmética da Shell é apenas de inteiros.

Se desejamos utilizar dados de temporização de alta resolução, precisamos fazer algo para não perdermos a precisão pela qual trabalhamos tão duro.

Existem várias soluções possíveis.

Mudar a Base de Tempo

Uma metodologia é mudar a base de tempo de segundos para milissegundos, microsegundos ou nanosegundos.

Isso pode ser feito simplesmente multiplicando os valores de tempo pela quantidade apropriada (por exemplo, 1000, 1.000.000, etc.) antes de subtraí-los.

Isso permite a aritmética de inteiros em valores de alta resolução sem perder a precisão.

Use o calculador de precisão arbitrária Shell bc

O calculador de linha de comando bc realizará cálculos usando números reais e é fácil de usar em scripts.

Ele está presente por padrão na maioria das distribuições.

`echo "scale=9; $endtime - $starttime" | bc`

onde endtime e starttime são variáveis contendo valores de tempo.

66

No entanto, por alguma razão inexplicável, nem Debian nem Opensuse o instalam por padrão.

Ele está presente no Ubuntu e Raspberry Pi, que são derivados do Debian, e pode ser adicionado a distribuições que não o possuem.

Use awk

O awk também pode realizar cálculos usando números reais e está presente em quase todas as distribuições, incluindo todas as testadas aqui.

echo "$endtime $starttime" | awk '{printf "%.6f\n", $1 - $2}'

--------------------

68 Benchmarks (Testes de Benchmark)

E, claro, nenhuma avaliação comparativa estaria completa sem benchmarks onde vemos como cada método se compara a outro em termos de velocidade.

No teste de benchmark, executei cada método em um loop através de múltiplas iterações, medí o tempo decorrido, subtraí o tempo de um loop vazio e, então, comparei com métodos alternativos.

Para qualquer coisa além de EPOCHREALTIME, o tempo do loop vazio é negligenciável e não tem efeito real nos resultados.

69

De forma interessante, encontrei um bug que causava a execução lenta do comando data se chamado imediatamente após o uso de EPOCHREALTIME no bash.

O efeito do bug era tornar o teste de benchmark de data aproximadamente 24 vezes mais lento.

Isso foi corrigido em versões mais recentes, mas se você estiver usando uma versão antiga da distribuição, fique atento a esse bug.

Consegui contorná-lo colocando um atraso de sono entre o benchmarking de EPOCHREALTIME e a data de benchmarking, ou simplesmente testando a data antes de testar o EPOCHREALTIME.

70

Para realizar testes adicionais, instalei o ts em Ubuntu e Alpine, e a versão GNU da data em Alpine.

71 EPOCHREALTIME vs. data em Ubuntu 2604 bash

O método EPOCHREALTIME é 3103 vezes mais rápido que a data.

No entanto, quando o mesmo teste é executado em Ubuntu 2404, e o teste de data é feito antes do teste de EPOCHREALTIME, o EPOCHREALTIME é 1240 vezes mais rápido que a data.

Outras distribuições Linux apresentam desempenho comparável ao Ubuntu 2404.

Parece que um efeito colateral da correção do bug é o desaceleração da data.

No entanto, provavelmente não é um problema significativo em circunstâncias normais.

72 data versus ts no Ubuntu 2604 bash

O método data é 3,7 vezes mais rápido que ts.

73 data versus ts no Ubuntu 2604 dash

O método data é 4,9 vezes mais rápido que ts.

74 data versus ts no FreeBSD sh

O método data é 2,5 vezes mais rápido que ts.

75 data versus adjtimex no Alpine Busybox

O método date é 6,0 vezes mais rápido que adjtimex.

76 datas versus ts no Alpine Busybox.

O método date é 20,0 vezes mais rápido que ts.

77 bc versus awk no Ubuntu 2604.

Eu comparei o cálculo da diferença entre dois números ao usar bc versus awk.

A diferença é negligenciável, com bc sendo apenas 7% mais rápido que awk.

78 Conclusão para Benchmarks.

Com base nesses resultados, se você precisa medir o tempo decorrido com alta resolução e se importa em executar o comando com o menor sobrecarga possível, então a ordem de preferência deve ser a seguinte.

79

Se você estiver usando uma versão mais nova do bash, utilize EPOCHREALTIME.

Se isso não estiver disponível, use a data, desde que permita resoluções de tempo de alta precisão.

Se as duas opções anteriores não puderem ser usadas, utilize ts.

Se você estiver usando Busybox e não puder instalar nem o GNU date nem o ts, utilize adjtimex.

Data é a mais próxima em termos de ser a solução universal portátil, mas não funciona em todos os casos.

80

Não comparei diferentes plataformas entre si em termos de desempenho, pois isso seria um problema muito mais complexo que está fora do escopo deste episódio.

No entanto, diferentes sistemas operacionais implementam comandos de maneiras diferentes.

81

Por exemplo, em Openbsd e Freebsd, ts parece ser um binário ELF. Ou seja, é código de máquina executável, possivelmente escrito em C.

No Ubuntu, no entanto, parece ser um script Perl.

Como resultado disso, a vantagem que o date tem sobre o ts é muito menor no FreeBSD do que no Ubuntu (e provavelmente em outras distribuições Linux), pois no FreeBSD não é necessário carregar um interpretador Perl para executar o ts.

--------------------

Conclusão Geral 82

Sem dúvida, você pensou que medir o tempo decorrido seria tão simples, e como alguém poderia extrair um podcast inteiro de um assunto tão simples?

E, no entanto, aqui estamos meia hora depois com apenas uma visão geral básica do assunto.

83

Espero que você tenha achado isso interessante e informativo.

Por favor, nos informe nos comentários se você acha que eu fiz algo incorretamente, ou se você tem outra maneira de fazer as coisas.

Espero vê-los todos novamente em outro episódio futuro do HPR.

--------------------

Forneçafeedbacksobre este episódio.

Fonte
Hacker Public Radio
Abrir original ↗

Conteúdo traduzido automaticamente por máquina.

Esta notícia foi útil?

Debates 0

Seja o primeiro a contribuir com o debate.

Difunda suas informações e promova seu argumento

Não se acanhe de publicar alguma informação ou dado que possa ser positivo ou útil.

Para participar do debate, entre com sua conta ou crie uma gratuita.