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ć?


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


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.


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


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'

+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, "." ); 

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}

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 @{}

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

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

Dlaczego nie zrobić tego wszystkiego w Pythonie? Zamiast wiązać wiązkę rur?
— Sobrique,

To miałoby lepszy sens. Zgadzam się.
— lukaz
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.