¿Cómo encadenar los comandos 'date -d @xxxxxx' y 'find ./'?

14

Tengo directorios cuyos nombres son marcas de tiempo, dadas en milisegundos desde 1970-01-01:

1439715011728
1439793321429
1439879712214
.
.

Y necesito una salida 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
.
.

Puedo enumerar todos los directorios por comando:

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

Pero no puedo poner la salida al siguiente comando: date -d @xxxxxxy manipular la salida.

¿Cómo puedo hacer esto?

BlaMa
fuente
2
¿Cómo se traducen esas marcas de tiempo en época? Porque tus números son demasiado largos ... (Ese primero es Fri Oct 2 05:35:28 47592)
Sobrique
1
@Sobrique Claramente milisegundos desde la época.
Gilles 'SO- deja de ser malvado'

Respuestas:

10

Está en el camino correcto (para una solución más simple, ejecutando solo 2 o 3 comandos, consulte a continuación). Debería usar en *lugar de ./deshacerse del directorio actual¹ y esto simplifica un poco el corte de los milisegundos, luego simplemente canalice el resultado en GNU parallelo xargs²:

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

Llegar

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

y para agregar los segundos de desplazamiento antes de eso, como indica su ejemplo:

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

o:

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

Llegar:

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

Sin embargo, es más simple hacer³:

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

lo que te da el mismo resultado solicitado una vez más.

La desventaja de usar *es que está limitado por su línea de comandos para su expansión, sin embargo, la ventaja es que ordena sus directorios por valor de marca de tiempo. Si el número de directorios es un problema -mindepth 1, use , pero pierda el orden:

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

e inserte sortsi es necesario:

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

¹ Esto supone que no hay subdirectorios anidados, como parece ser el caso de su ejemplo. También se puede utilizar ./ -mindepth 1en lugar de*
² Se puede reemplazar parallelcon xargs -I{}aquí como @hobbs y @don_crissti sugerido, simplemente más prolija. ³ basado en la respuesta de Gilles para usar datelas capacidades de lectura de archivos

Anthon
fuente
O xargssi no tienes parallel, lo que probablemente muchas personas no tienen.
hobbs
@hobbs AFAIK xargsno tiene la opción de especificar dónde va el argumento como lo hizo parallelcon {}.
Anthon
44
Lo hace:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti
@Anthon lo hace si usa la -Iopción.
hobbs
1
@Anthon, las opciones largas de GNU pueden abreviarse siempre que no sean ambiguas. --do --dafuncionaría con versiones actuales de GNU date, pero podría dejar de funcionar el día que dateintroduce una --dalekopción (para fechas en el calendario Dalek).
Stéphane Chazelas
10

Evitaría ejecutar varios comandos por archivo en un bucle. Como ya estás usando GNUisms:

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

Que solo ejecuta dos comandos. strftime()es específico de GNU, como date -d.

Stéphane Chazelas
fuente
Esto no corta los milisegundos de los nombres de directorio pero muestra los 13 caracteres completos en lugar de los primeros 10 solicitados
Anthon
@Anthon, ah sí, no cumplí ese requisito. Debería estar bien ahora.
Stéphane Chazelas
8

Tu ya lo tienes:

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

que presumiblemente le proporciona las marcas de tiempo en formato de época. Ahora agregue un ciclo while:

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

Sin embargo, tenga en cuenta que en algunos shells, esa sintaxis obtiene el bucle while en una subshell, lo que significa que si intenta establecer una variable allí, no será visible una vez que haya dejado el bucle. Para solucionarlo, debes cambiar un poco las cosas:

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

que pone el finden la subshell y mantiene el bucle while en el shell principal. Que la sintaxis (AT & T ksh, zshy bashespecífico) sólo es necesario si usted está mirando para reutilizar resultado desde el interior del bucle, sin embargo.

Wouter Verhelst
fuente
independientemente, decir que es específico de bash no es correcto :)
Wouter Verhelst
En realidad, como lo había escrito inicialmente, en done <(find)lugar de done < <(find), era correcto para yash(dónde <(...)está la redirección del proceso, no la sustitución del proceso), por lo que mi edición fue un poco arrogante, ya que podría haber sido el caparazón para el que quiso decir eso.
Stéphane Chazelas
6

Si tiene una fecha GNU, puede convertir fechas leídas de un archivo de entrada. Solo necesita masajear las marcas de tiempo un poco para que pueda reconocerlas. La sintaxis de entrada para una marca de tiempo basada en la época de Unix es @seguida por el número de segundos, que puede contener un punto decimal.

find ./ -type d ! -name '*[!0-9]*' |
sed -e 's~.*/~@~' -e 's~[0-9][0-9][0-9]$~.&~' |
date -f - +'%s  %c'
Gilles 'SO- deja de ser malvado'
fuente
+1 para usar datela lectura de archivos s. Esto dará un date: invalid date ‘@’debido a la traducción del directorio actual ( ./). Y dado que puede tirar los milisegundos, puede simplificar la segunda sededición para soltar los últimos 3 caracteres. O elimine todo eso y usefind * -type d -printf "@%.10f" | date ...
Anthon
5

Lo haría con dulzura: alimentar en una lista de marcas de tiempo:

#!/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

Esto produce:

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

Si desea un formato de salida específico, puede usar, strftimepor ejemplo:

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

Para convertir esto en un revestimiento en tu tubería:

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

Pero probablemente sugeriría en cambio mirar el uso del File::Findmódulo y hacer todo en perl. Si da un ejemplo de la estructura de su directorio antes de cortarlo, le daré un ejemplo. Pero sería 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, "." ); 
Sobrique
fuente
2

Con zshy el strftime incorporado:

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

Esto supone que todos los nombres de directorio en el directorio actual son en realidad tiempos de época.
Es posible un mayor filtrado / procesamiento siempre que aclare cómo deben procesarse esos números en su ejemplo (se parecen más a los tiempos de época correspondientes a las fechas de nacimiento de la princesa Leia y Luke Skywalker ...) por ejemplo, busque de forma recursiva nombres de directorio que coincidan al menos 10 dígitos y calcule la fecha en función de los primeros 10 dígitos:

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

Usando GNU Paralelo:

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

Si puede aceptar \ t en lugar de espacio:

find ./ -type d | cut -c 3-12 | parallel -k --tag date -d @{}
Ole Tange
fuente
Tenga en cuenta que parallelestá escrito en perl. Esto parece excesivo teniendo en perlcuenta que tiene un strftime()operador. Me gustaperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Stéphane Chazelas
2
1. Es más corto. 2. No necesitas aprender Perl.
Ole Tange
1
Es un 27% más corto, pero es menos eficiente en varios órdenes de magnitud (aproximadamente 800 veces más lento en la prueba que hice; tenga en cuenta que necesita generar un shell (su shell, no / bin / sh) y un comando de fecha para cada línea) y hostil para el sistema, ya que carga todas las CPU a la vez. Y aún necesitas aprender parallel. IMO, paralleles una gran herramienta para paralelizar tareas intensivas de CPU, pero no es realmente apropiado para este tipo de tareas aquí.
Stéphane Chazelas
Hay muchos contextos donde la eficiencia no es una preocupación, por lo que sigue siendo una solución aceptable, pero vale la pena mencionar el problema del rendimiento, especialmente cuando se considera que el paralelo generalmente rima con un alto rendimiento en la mente de las personas.
Stéphane Chazelas
0

Normalmente, el comando find se puede encadenar con cualquier comando usando execargumentos.

En su caso, puede hacer lo siguiente:

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

Usando Python (es la solución más lenta)

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

da:

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
lukaz
fuente
¿Por qué no hacerlo todo en python? ¿En lugar de encadenar un montón de tuberías?
Sobrique
Tendría más sentido. Estoy de acuerdo.
lukaz