Comment chaîner les commandes 'date -d @xxxxxx' et 'find ./'?

14

J'ai des répertoires dont les noms sont des horodatages, donnés en millisecondes depuis le 01/01/1970:

1439715011728
1439793321429
1439879712214
.
.

Et j'ai besoin d'une sortie comme:

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

Je peux lister tous les répertoires par commande:

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

Mais je ne peux pas mettre la sortie à la commande suivante: date -d @xxxxxxet manipuler la sortie.

Comment puis-je faire ceci?

BlaMa
la source
2
Comment ces horodatages se traduisent-ils à l'époque? Parce que vos numéros sont trop longs ... (Ce premier - est Fri Oct 2 05:35:28 47592)
Sobrique
1
@Sobrique Clairement millisecondes depuis l'époque.
Gilles 'SO- arrête d'être méchant'

Réponses:

10

Vous êtes sur la bonne voie (pour une solution plus simple, n'exécutant que 2 ou 3 commandes, voir ci-dessous). Vous devriez utiliser *au lieu de ./pour vous débarrasser du répertoire courant¹ et cela simplifie quelque peu la coupe des millisecondes, puis il suffit de diriger le résultat en GNU parallelou xargs²:

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

obtenir

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

et pour ajouter le décalage des secondes avant cela comme l'indique votre exemple:

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)'

obtenir:

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

Cependant, c'est plus simple à faire³:

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

ce qui vous donne à nouveau la même sortie demandée.

L'inconvénient de l'utilisation *est que vous êtes limité par votre ligne de commande pour son expansion, l'avantage est cependant que vous obtenez vos répertoires triés par valeur d'horodatage. Si le nombre de répertoires pose problème -mindepth 1, mais perdez l'ordre:

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

et insérer sortsi besoin:

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

¹ Cela suppose qu'il n'y a pas de sous-répertoires imbriqués, comme cela semble être le cas dans votre exemple. Vous pouvez également utiliser ./ -mindepth 1au lieu de*
² Vous pouvez remplacer parallelpar xargs -I{}ici comme @hobbs et @don_crissti l'ont suggéré, c'est juste plus verbeux. ³ basé sur la réponse de Gilles pour utiliser dateles capacités de lecture de fichiers de s

Anthon
la source
Ou xargssi vous n'en avez pas parallel, ce que beaucoup de gens n'ont probablement pas.
hobbs
@hobbs AFAIK xargsn'a pas la possibilité de spécifier où l'argument va comme parallela avec {}.
Anthon
4
Il le fait:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti
@Anthon le fait si vous utilisez l' -Ioption.
hobbs
1
@Anthon, les options longues GNU peuvent être abrégées tant qu'elles ne sont pas ambiguës. --dou --dafonctionnerait avec les versions actuelles de GNU date, mais il pourrait cesser de fonctionner le jour dateintroduit une --dalekoption (pour les dates dans le calendrier Dalek).
Stéphane Chazelas
10

J'éviterais d'exécuter plusieurs commandes par fichier dans une boucle. Puisque vous utilisez déjà GNUisms:

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

Qui exécute simplement deux commandes. strftime()est spécifique à GNU, comme date -d.

Stéphane Chazelas
la source
Cela ne coupe pas les millisecondes des noms de répertoires mais affiche les 13 caractères complets au lieu des 10 premiers demandés
Anthon
@Anthon, ah oui, j'ai raté cette exigence. Ça devrait aller maintenant.
Stéphane Chazelas
8

Tu as déjà:

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

qui vous donne probablement les horodatages au format d'époque. Ajoutez maintenant une boucle while:

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

Notez cependant que dans certains shells, cette syntaxe obtient la boucle while dans un sous-shell, ce qui signifie que si vous essayez de définir une variable là-bas, elle ne sera pas visible une fois que vous aurez quitté la boucle. Pour résoudre ce problème, vous devez légèrement tourner les choses:

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

ce qui place le finddans le sous-shell, et conserve la boucle while dans le shell principal. Cette syntaxe (AT & T ksh, zshet bashspécifique) est nécessaire que si vous cherchez à réutiliser un résultat à l' intérieur de la boucle, cependant.

Wouter Verhelst
la source
peu importe, dire qu'il est spécifique à bash n'est pas correct :)
Wouter Verhelst
En fait, comme vous l'aviez initialement écrit, done <(find)au lieu de done < <(find), c'était correct pour yash(où <(...)est la redirection de processus, pas la substitution de processus), donc mon montage était un peu cavalier car il aurait pu être le shell pour lequel vous vouliez dire.
Stéphane Chazelas
6

Si vous avez une date GNU, elle peut convertir les dates lues à partir d'un fichier d'entrée. Il vous suffit de masser un peu les horodatages pour qu'il puisse les reconnaître. La syntaxe d'entrée pour un horodatage basé sur l'époque Unix est @suivie du nombre de secondes, qui peut contenir un point décimal.

find ./ -type d ! -name '*[!0-9]*' |
sed -e 's~.*/~@~' -e 's~[0-9][0-9][0-9]$~.&~' |
date -f - +'%s  %c'
Gilles 'SO- arrête d'être méchant'
la source
+1 pour utiliser datela lecture de fichiers s. Cela donnera un à date: invalid date ‘@’cause de la traduction du répertoire courant ( ./). Et comme vous pouvez jeter les millisecondes, vous pouvez simplifier la deuxième sedmodification pour simplement supprimer les 3 derniers caractères. Ou supprimez tout cela et utilisezfind * -type d -printf "@%.10f" | date ...
Anthon
5

Je le ferais périlleusement - alimenter une liste d'horodatages:

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

Cela produit:

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 vous souhaitez un format de sortie spécifique, vous pouvez utiliser strftimepar exemple:

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

Que pour transformer cela en une doublure dans votre tuyau:

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

Mais je suggérerais probablement plutôt d'utiliser le File::Findmodule et de faire le tout en perl à la place. Si vous donnez un exemple de votre structure de répertoires avant de le couper, je vais vous donner un exemple. Mais ce serait quelque chose comme:

#!/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
la source
2

Avec zshet le strftime intégré:

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

cela suppose que tous les noms de votre répertoire dans le répertoire actuel sont en fait des époques.
Un filtrage / traitement supplémentaire est possible à condition de clarifier la façon dont ces chiffres dans votre exemple doivent être traités (ils ressemblent plus à des époques correspondant aux dates de naissance de la princesse Leia et de Luke Skywalker ...), par exemple, rechercher récursivement des noms de répertoire qui correspondent au moins 10 chiffres et calculez la date sur la base des 10 premiers chiffres:

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

Utilisation de GNU Parallel:

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

Si vous pouvez accepter \ t au lieu de l'espace:

find ./ -type d | cut -c 3-12 | parallel -k --tag date -d @{}
Ole Tange
la source
Notez que parallelc'est écrit perl. Cela semble exagéré étant donné qu'il perla un strftime()opérateur. J'aimeperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Stéphane Chazelas
2
1. Il est plus court. 2. Vous n'avez pas besoin d'apprendre Perl.
Ole Tange
1
C'est 27% plus court, mais c'est plusieurs ordres de grandeur moins efficace (environ 800 fois plus lent dans le test que j'ai fait; considérez qu'il doit générer un shell (votre shell, pas / bin / sh) et une commande de date pour chaque ligne) et hostile au système car il charge tous les processeurs à la fois. Et vous devez encore apprendre parallel. IMO, parallelest un excellent outil pour paralléliser les tâches gourmandes en ressources processeur, mais pas vraiment approprié pour ce type de tâche ici.
Stéphane Chazelas
Il existe de nombreux contextes où l'efficacité n'est pas un problème, c'est donc toujours une solution acceptable, mais cela vaut toujours la peine de mentionner le problème de performance, surtout si l'on considère que le parallèle rime généralement avec haute performance dans l'esprit des gens.
Stéphane Chazelas
0

Normalement, la commande find peut être chaînée avec n'importe quelle commande utilisant un execargument.

Dans votre cas, vous pouvez faire comme ceci:

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

Utiliser Python (c'est la solution la plus lente probablement)

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

donne:

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
la source
Pourquoi ne pas tout faire en python? Plutôt que d'enchaîner un tas de tuyaux?
Sobrique
Cela aurait plus de sens. Je suis d'accord.
lukaz