Jak połączyć polecenia „date -d @xxxxxx” i „find ./”?

14

Mam katalogi, których nazwy są znacznikami czasu, podawanymi w milisekundach od 1970-01-01:

1439715011728
1439793321429
1439879712214
.
.

Potrzebuję danych wyjściowych takich jak:

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

Mogę wyświetlić wszystkie katalogi według polecenia:

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

Ale nie mogę umieścić danych wyjściowych w następnym poleceniu: date -d @xxxxxxi manipulować danymi wyjściowymi.

W jaki sposób mogę to zrobić?

BlaMa
źródło
2
Jak te znaczniki czasu przekładają się na epokę? Ponieważ twoje liczby są za długie ... (Ten pierwszy - jest Fri Oct 2 05:35:28 47592)
Sobrique
1
@Sobrique Wyraźnie milisekundy od epoki.
Gilles „SO- przestań być zły”

Odpowiedzi:

10

Jesteś na dobrej drodze (dla prostszego rozwiązania, uruchamiając tylko 2 lub 3 polecenia, patrz poniżej). Powinieneś użyć *zamiast ./pozbyć się bieżącego katalogu¹, a to nieco upraszcza wycinanie milisekund, a następnie po prostu przesłać wynik do GNU parallellub xargs²:

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

dostać

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

i aby dodać przesunięcie sekund przed tym, jak pokazuje przykład:

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

lub:

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

uzyskać:

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

Łatwiej jednak to zrobić:

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

co daje ci to samo żądane wyjście jeszcze raz.

Wadą używania *jest to, że Twój wiersz poleceń ogranicza jego rozszerzenie, jednak zaletą jest sortowanie katalogów według wartości znacznika czasu. Jeśli liczba katalogów stanowi problem, użyj -mindepth 1, ale stracisz kolejność:

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

i w sortrazie potrzeby wstaw :

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

¹ Zakłada się, że nie ma zagnieżdżonych podkatalogów, jak wydaje się w przypadku twojego przykładu. Można również użyć ./ -mindepth 1zamiast*
² można zastąpić parallelz xargs -I{}tutaj jako @hobbs i @don_crissti zasugerował, to po prostu bardziej gadatliwy. ³ na podstawie odpowiedzi Gillesa na wykorzystanie datemożliwości odczytu plików

Anthon
źródło
Lub xargsjeśli nie masz parallel, czego wiele osób prawdopodobnie nie ma.
hobbs
@hobbs AFAIK xargsnie ma opcji, aby określić, gdzie argument idzie jak parallelma z {}.
Anthon
4
Czyni to:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti
@Anthon robi to, jeśli skorzystasz z -Iopcji.
hobbs
1
@Anthon, długie opcje GNU można skracać, o ile nie są one niejednoznaczne. --dlub --dadziałałby z aktualnymi wersjami GNU date, ale może przestać działać dzień datewprowadza --dalekopcję (dla dat w kalendarzu Dalek).
Stéphane Chazelas
10

Unikałbym uruchamiania kilku poleceń na plik w pętli. Ponieważ już używasz GNUisms:

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

Który po prostu uruchamia dwa polecenia. strftime()jest specyficzny dla GNU, jak date -d.

Stéphane Chazelas
źródło
Nie skraca to milisekund nazw katalogów, ale wyświetla pełne 13 znaków zamiast żądanych pierwszych 10
Anthon
@Anthon, ach tak, nie spełniłem tego wymogu. Teraz powinno być OK.
Stéphane Chazelas,
8

Już masz:

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

co prawdopodobnie zapewnia znaczniki czasu w formacie epoki. Teraz dodaj pętlę while:

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

Zauważ jednak, że w niektórych powłokach ta składnia dostaje pętlę while w podpowłoce, co oznacza, że ​​jeśli spróbujesz ustawić tam zmienną, nie będzie ona widoczna po wyjściu z pętli. Aby to naprawić, musisz lekko odwrócić głowę:

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

który wstawia findpodpowłokę i utrzymuje pętlę while w głównej powłoce. Że składnia (AT & T ksh, zsha bashspecyficzna) jest potrzebne tylko wtedy, gdy szukasz ponowne wynik od wewnątrz pętli, choć.

Wouter Verhelst
źródło
niezależnie od tego, że mówienie, że jest to specyficzne dla basha, nie jest poprawne :)
Wouter Verhelst
Właściwie, tak jak to początkowo napisałeś, done <(find)zamiast done < <(find)tego było poprawne dla yash(gdzie <(...)jest przekierowanie procesu, a nie podstawienie procesu), więc moja edycja była nieco bardziej nonszalancka, ponieważ mogła to być powłoka, dla której miałeś to na myśli.
Stéphane Chazelas,
6

Jeśli masz datę GNU, może konwertować daty odczytane z pliku wejściowego. Musisz tylko trochę masować znaczniki czasu, aby mógł je rozpoznać. Po składni wejściowej znacznika czasu opartego na epoce Uniksa @następuje liczba sekund, która może zawierać kropkę dziesiętną.

find ./ -type d ! -name '*[!0-9]*' |
sed -e 's~.*/~@~' -e 's~[0-9][0-9][0-9]$~.&~' |
date -f - +'%s  %c'
Gilles „SO- przestań być zły”
źródło
+1 za korzystanie z dateodczytu pliku s. To da date: invalid date ‘@’ze względu na tłumaczenie bieżącego katalogu ( ./). A ponieważ możesz wyrzucić milisekundy, możesz uprościć drugą sededycję, aby po prostu upuścić 3 ostatnie znaki. Lub usuń to wszystko i użyjfind * -type d -printf "@%.10f" | date ...
Anthon
5

Zrobiłbym to perwersyjnie - podaj listę znaczników czasu:

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

To daje:

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

Jeśli chcesz określonego formatu wyjściowego, możesz użyć strftimenp .:

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

Które zamienić to w jedną wkładkę w rurze:

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

Ale prawdopodobnie sugerowałbym zamiast tego skorzystanie z File::Findmodułu i zrobienie tego w Perlu. Jeśli podasz przykładową strukturę katalogu przed jej wycięciem, dam ci przykład. Ale byłoby to coś takiego:

#!/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
źródło
2

Z zsha strftime wbudowane:

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

zakłada to, że wszystkie nazwy katalogów w bieżącym katalogu są w rzeczywistości epokami.
Możliwe jest dalsze filtrowanie / przetwarzanie pod warunkiem wyjaśnienia, w jaki sposób należy przetworzyć te liczby w twoim przykładzie (wyglądają bardziej jak epoki odpowiadające datom urodzin księżniczki Lei i Luke'a Skywalkera ...) np. Rekurencyjnie szukaj nazw katalogów, które pasują co najmniej 10 cyfr i oblicz datę na podstawie pierwszych 10 cyfr:

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

Korzystanie z GNU Parallel:

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

Jeśli możesz zaakceptować \ t zamiast spacji:

find ./ -type d | cut -c 3-12 | parallel -k --tag date -d @{}
Ole Tange
źródło
Uwaga, która paralleljest napisana w perl. Wydaje się to przesadą, biorąc pod uwagę, że perlma strftime()operatora. Jakperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Stéphane Chazelas
2
1. Jest krótszy. 2. Nie musisz się uczyć Perla.
Ole Tange,
1
Jest o 27% krótszy, ale jest o kilka rzędów wielkości mniej wydajny (około 800 razy wolniejszy w teście, który wykonałem; weź pod uwagę, że potrzebuje spawnować powłokę (twoja powłoka, nie / bin / sh) i polecenie daty dla każdej linii) i nieprzyjazny dla systemu, ponieważ obciąża wszystkie procesory naraz. I nadal musisz się uczyć parallel. IMO parallelto świetne narzędzie do równoległego wykonywania zadań intensywnie wykorzystujących procesor, ale tak naprawdę nie jest odpowiednie do tego typu zadań.
Stéphane Chazelas,
Istnieje wiele kontekstów, w których wydajność nie jest problemem, więc nadal jest to akceptowalne rozwiązanie, ale nadal warto wspomnieć o problemie z wydajnością, szczególnie biorąc pod uwagę, że równolegle zwykle rymuje się z wysoką wydajnością w umyśle ludzi.
Stéphane Chazelas,
0

Zwykle polecenie find może być powiązane z dowolnym poleceniem używającym execargumentu.

W twoim przypadku możesz to zrobić w następujący sposób:

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

Korzystanie z Pythona (jest to możliwie najbardziej wolniejsze rozwiązanie)

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

daje:

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
źródło
Dlaczego nie zrobić tego wszystkiego w Pythonie? Zamiast wiązać wiązkę rur?
Sobrique,
To miałoby lepszy sens. Zgadzam się.
lukaz