Como encadear os comandos 'date -d @xxxxxx' e 'find ./'?


14

Eu tenho diretórios cujos nomes são timestamps, dados em milissegundos desde 01-01-2009:

1439715011728
1439793321429
1439879712214
.
.

E eu preciso de uma saída como:

1442039711    Sat Sep 12 08:35:11 CEST 2015
1442134211    Sun Sep 13 10:50:11 CEST 2015
1442212521    Mon Sep 14 08:35:21 CEST 2015
.
.

Eu posso listar todos os diretórios por comando:

find ./ -type d | cut -c 3-12

Mas não posso colocar a saída no próximo comando: date -d @xxxxxxe manipular a saída.

Como posso fazer isso?


2
Como esses carimbos de hora se traduzem em época? Porque os números são muito longos ... (Essa primeira - é Fri Oct 2 05:35:28 47592)
Sobrique

1
@Sobrique Claramente milissegundos desde a época.
Gilles 'SO- stop be evil'

Respostas:


10

Você está no caminho certo (para uma solução mais simples, executando apenas 2 ou 3 comandos, veja abaixo). Você deve usar em *vez de ./se livrar do diretório atual¹ e isso simplifica um pouco o corte dos milissegundos, em seguida, basta canalizar o resultado no GNU parallelou xargs²:

find * -type d | cut -c 1-10 | parallel date --date=@{} +%c

para obter

Sat 12 Sep 2015 08:35:11 CEST
Sun 13 Sep 2015 10:50:11 CEST
Mon 14 Sep 2015 08:35:21 CEST

e para adicionar o deslocamento dos segundos antes disso, como indica o seu exemplo:

find * -type d | cut -c 1-10 | parallel 'echo "{} "  $(date --date=@{} +%c)'

ou:

find * -type d | cut -c 1-10 | xargs -I{} bash -c 'echo "{} "  $(date --date=@{} +%c)'

para obter:

1442039711  Sat 12 Sep 2015 08:35:11 CEST
1442134211  Sun 13 Sep 2015 10:50:11 CEST
1442212521  Mon 14 Sep 2015 08:35:21 CEST

No entanto, é mais simples de fazer³:

find * -type d -printf "@%.10f\n" | date -f - +'%s  %c'

que fornece a mesma saída solicitada mais uma vez.

A desvantagem do uso *é que você está limitado pela linha de comando para sua expansão, mas a vantagem é que você classifica seus diretórios pelo valor do carimbo de data / hora. Se o número de diretórios for um problema -mindepth 1, use , mas perca a ordem:

find ./ -mindepth 1 -type d -printf "@%.10f\n" | date -f - +'%s  %c'

e insira, sortse necessário:

find ./ -mindepth 1 -type d -printf "@%.10f\n" | sort | date -f - +'%s  %c'

¹ Isso pressupõe que não haja subdiretórios aninhados, como parece ser o caso do seu exemplo. Você também pode usar ./ -mindepth 1em vez de*
² Você pode substituir parallelcom xargs -I{}aqui como @hobbs e @don_crissti sugeriu, apenas mais detalhado. ³ com base na resposta de Gilles para usar dateos recursos de leitura de arquivos


Ou xargsse você não tiver parallel, o que muitas pessoas provavelmente não têm.
Hbbs #

@hobbs AFAIK xargsnão tem a opção de especificar onde o argumento vai como paralleltem com {}.
Anthon

4
Faz:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti 15/09/2015

@Anton faz se você usar a -Iopção.
Hbbs #

1
@Anthon, as opções longas do GNU podem ser abreviadas desde que não sejam ambíguas. --dou --dafuncionaria com as versões atuais do GNU date, mas poderia parar de funcionar quando o dia dateintroduzir uma --dalekopção (para datas no calendário Dalek).
Stéphane Chazelas

10

Eu evitaria executar vários comandos por arquivo em um loop. Como você já está usando o GNUisms:

find . ! -name . -prune -type d |
  awk '{t = substr($0, 3, 10); print t, strftime("%a %b %d %T %Z %Y", t)}'

Que apenas executa dois comandos. strftime()é específico do GNU, como date -d.


Isso não reduz os milissegundos dos nomes de diretório, mas exibe os 13 caracteres completos em vez dos 10 primeiros solicitados.
Anthon

@ Anthon, ah sim, perdeu esse requisito. Deve estar bem agora.
Stéphane Chazelas

8

Você já tem:

find ./ -type d | cut -c 3-12

o que provavelmente fornece os registros de data e hora em formato de época. Agora adicione um loop while:

find ./ -type d | cut -c 3-12 | while read datestamp
do
    printf %s "$datestamp"
    date -d "@$datestamp"
done

Observe, porém, que em alguns shells, essa sintaxe obtém o loop while em um subshell, o que significa que se você tentar definir uma variável lá, ela não ficará visível quando você sair do loop. Para consertar isso, você precisa virar um pouco as coisas:

while read datestamp
do
    printf %s "$datestamp"
    date -d "@$datestamp"
done < <(find ./ -type d | cut -c 3-12)

o que coloca o find subshell e mantém o loop while no shell principal. Que a sintaxe (AT & T ksh, zshe bashespecífico) só é necessário se você estiver olhando para reutilizar um resultado de dentro do loop, no entanto.


independentemente, dizendo que é específico da festa não é correto :)
Wouter Verhelst

Na verdade, como você o escreveu inicialmente, em done <(find)vez de done < <(find)estar correto yash(onde <(...)está o redirecionamento do processo, não a substituição do processo), minha edição foi um pouco descuidada, pois poderia ter sido o shell para o qual você pretendia.
Stéphane Chazelas 15/09/2015

6

Se você tiver uma data GNU, ele poderá converter as datas lidas de um arquivo de entrada. Você só precisa massagear um pouco os timestamps para que eles possam reconhecê-los. A sintaxe de entrada para um registro de data e hora com base na época do Unix é @seguida pelo número de segundos, que pode conter um ponto decimal.

find ./ -type d ! -name '*[!0-9]*' |
sed -e 's~.*/~@~' -e 's~[0-9][0-9][0-9]$~.&~' |
date -f - +'%s  %c'

+1 para usar datea leitura de arquivos s. Isso dará uma date: invalid date ‘@’causa da tradução do diretório atual ( ./). E como você pode jogar fora os milissegundos, você pode simplificar a segunda sededição para soltar apenas os últimos 3 caracteres. Ou remover tudo isso e usarfind * -type d -printf "@%.10f" | date ...
Anthon

5

Eu faria isso perlishly - alimente uma lista de timestamps:

#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;

while ( my $ts = <DATA> ) { 
   chomp ( $ts );
   my $t = Time::Piece->new();
   print $t->epoch, " ", $t,"\n";
}

__DATA__
1442039711  
1442134211  
1442212521

Isso gera:

1442039711 Sat Sep 12 07:35:11 2015
1442134211 Sun Sep 13 09:50:11 2015
1442212521 Mon Sep 14 07:35:21 2015

Se você deseja um formato de saída específico, pode usar strftime por exemplo:

print $t->epoch, " ", $t->strftime("%Y-%m-%d %H:%M:%S"),"\n";

Qual transformar isso em um forro no seu tubo:

 perl -MTime::Piece -nle '$t=Time::Piece->new($_); print $t->epoch, "  ", $t, "\n";'

Mas eu provavelmente sugeriria, em vez disso, examinar o File::Findmódulo e fazer a coisa toda em perl. Se você der um exemplo da sua estrutura de diretórios antes de cortá-la, darei um exemplo. Mas seria algo como:

#!/usr/bin/env perl

use strict;
use warnings;
use Time::Piece;
use File::Find; 

sub print_timestamp_if_dir {
   #skip if 'current' item is not a directory. 
   next unless -d; 
   #extract timestamp (replicating your cut command - I think?)
   my ( $timestamp ) = m/.{3}(\d{9})/; #like cut -c 3-12;

   #parse date
   my $t = Time::Piece->new($timestamp);
   #print file full path, epoch time and formatted time; 
   print $File::Find::name, " ", $t->epoch, " ", $t->strftime("%Y-%m-%d %H:%M:%S"),"\n";
}

find ( \&print_timestamp_if_dir, "." ); 

2

Com zshe o tempo de execução incorporado:

zmodload zsh/datetime
for d (*(/))
strftime '%s %a %b %d %T %Z %Y' $d

isso pressupõe que todos os nomes de diretório no diretório atual sejam realmente épocas.
É possível filtrar / processar mais, desde que você esclareça como esses números no seu exemplo devem ser processados ​​(eles se parecem mais com os tempos da época correspondentes às datas de nascimento da princesa Leia e Luke Skywalker ...). Por exemplo, procure recursivamente por nomes de diretórios que correspondam a pelo menos 10 dígitos e calcule a data com base nos 10 primeiros dígitos:

setopt extendedglob
zmodload zsh/datetime
for d (**/[0-9](#c10,)(/))
strftime '%s %a %b %d %T %Z %Y' ${${d:t}:0:10}

2

Usando o GNU Parallel:

find ./ -type d | cut -c 3-12 | parallel -k 'echo {} `date -d @{}`'

Se você pode aceitar \ t em vez de espaço:

find ./ -type d | cut -c 3-12 | parallel -k --tag date -d @{}

Observe que parallelestá escrito em perl. Isso parece um exagero, considerando que perltem um strftime()operador. Likeperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Stéphane Chazelas 15/09/2015

2
1. É mais curto. 2. Você não precisa aprender Perl.
precisa saber é o seguinte

1
É 27% mais curto, mas é várias ordens de magnitude menos eficiente (cerca de 800 vezes mais lenta no teste que fiz; considere que ele precisa gerar um shell (seu shell, não / bin / sh) e um comando de data para cada linha) e hostil ao sistema, pois sobrecarrega todas as CPUs ao mesmo tempo. E você ainda precisa aprender parallel. O IMO parallelé uma ótima ferramenta para paralelizar tarefas intensivas da CPU, mas não é realmente apropriado para esse tipo de tarefa aqui.
Stéphane Chazelas

Existem muitos contextos em que a eficiência não é uma preocupação, por isso ainda é uma solução aceitável, mas ainda vale a pena mencionar a questão do desempenho, especialmente quando se considera que o paralelo geralmente rima com alto desempenho na mente das pessoas.
Stéphane Chazelas

0

Normalmente, o comando find pode ser encadeado com qualquer comando usando o execargumento

No seu caso, você pode fazer assim:

find . -type d | cut -c 3-12 | while read line
do
       echo -n "${line}  "
       date -d $line
done

0

Usando Python (é a solução mais lenta possível)

for i in $(ls -A); do echo $i | xargs python -c "from sys import argv;from time import strftime;from datetime import datetime;print datetime.fromtimestamp(float(argv[1][:-3])).strftime('%Y-%m-%d %H:%M:%S'),'---',argv[1]"; done

dá:

2015-08-30 08:48:59 --- 1440917339340
2015-08-31 08:00:22 --- 1441000822458
2015-09-01 08:00:32 --- 1441087232437
2015-09-01 16:48:43 --- 1441118923773
2015-09-02 08:00:11 --- 1441173611869
2015-09-03 08:00:32 --- 1441260032393
2015-09-04 08:00:21 --- 1441346421651

Por que não fazer tudo isso em python? Em vez de encadear um monte de canos?
Sobrique 21/09

Isso faria mais sentido. Concordo.
Lukaz 23/09/2015
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.