Як ланцюжок команд 'дата -d @xxxxxx' та 'знайти ./'?


14

У мене є каталоги, імена яких - часові позначки, наведені в мілісекундах з 1970-01-01:

1439715011728
1439793321429
1439879712214
.
.

І мені потрібен вихід типу:

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

Я можу перелічити всі каталоги за командою:

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

Але я не можу перевести висновок до наступної команди: date -d @xxxxxxі маніпулювати результатом.

Як я можу це зробити?


2
Як ці часові позначки перекладаються на епоху? Тому що ваші номери занадто довгі ... (Той перший - є Fri Oct 2 05:35:28 47592)
Смутний

1
@Sobrique Ясно мілісекунд з епохи.
Жиль "ТАК - перестань бути злим"

Відповіді:


10

Ви на правильному шляху (простіше рішення, виконуючи лише 2 або 3 команди, див. Нижче). Ви повинні використовувати *замість./ щоб позбутися поточного каталогу¹, і це дещо спрощує скорочення мілісекунд, а потім просто передайте результат у GNU parallelабо xargs²:

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

отримати

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

і додати зміщення секунд перед цим, як ваш приклад вказує:

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

або:

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

отримати:

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

Однак зробити це простіше:

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

що ще раз отримує той самий запитуваний вихід.

Недоліком використання *є те, що ви обмежені вашою командною лінією для її розширення, проте перевага полягає в тому, що ви отримуєте свої каталоги відсортовані за значенням часової мітки. Якщо кількість каталогів є проблемою використання -mindepth 1, але втратите впорядкування:

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

і вставте, sortякщо потрібно:

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

¹ Це передбачає, що немає вкладених підкаталогів, як це здається у вашому прикладі. Ви можете також використовувати ./ -mindepth 1замість*
² Ви можете замінити parallelз xargs -I{}тут @hobbs і @don_crissti запропонував, він просто більш багатослівним. ³ ґрунтується на відповіді Гілла щодо використання dateможливостей зчитування файлів s


Або xargsякщо у вас немає parallel, чого, мабуть, багато людей.
варення

@hobbs AFAIK xargsне має можливості визначити , де аргумент йде , як parallelє з {}.
Антон

4
Це робиться:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti

@Anthon це робить, якщо ви використовуєте цю -Iопцію.
варення

1
@Anthon, довгі параметри GNU можна скоротити, якщо вони не неоднозначні. --dабо --daпрацюватиме з поточними версіями GNU date, але це може перестати працювати в день, коли dateвводиться --dalekопція (для дат у календарі Dalek).
Стефан Шазелас

10

Я б не запускав кілька команд на файл у циклі. Оскільки ви вже використовуєте GNUisms:

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

Який просто виконує дві команди. strftime()є специфічним для GNU, як date -d.


Це не скорочує мілісекунд імен каталогів, але відображає повних 13 символів замість запитуваних перших 10
Anthon

@Anthon, так, пропустив цю вимогу. Зараз має бути гаразд.
Стефан Шазелас

8

У вас вже є:

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

яка, ймовірно, отримує вам часові позначки у форматі епохи. Тепер додайте цикл:

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

Зауважте, що в деяких оболонках цей синтаксис отримує цикл while у підшалі, це означає, що якщо ви спробуєте встановити змінну там, вона не буде видно, як тільки ви вийдете з циклу. Щоб виправити це, вам потрібно трохи повернути речі на голову:

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

який поміщає findв нижню частину корпусу і зберігає цикл while в основній оболонці. Цей синтаксис (AT & T ksh, zshі bashспецифічний) потрібен тільки якщо ви хочете повторно використовувати результат всередині циклу, однако.


незважаючи на те, що це баш-специфічно не правильно :)
Wouter Verhelst

Насправді, як ви писали це спочатку, done <(find)замість done < <(find)цього було правильно yash(де <(...)перенаправлення процесу, а не підміна процесу), тому моє редагування було трохи кавалерніше, оскільки це могло бути оболонкою, для якої ви це мали на увазі.
Стефан Шазелас

6

Якщо у вас є дата GNU, вона може конвертувати дати, прочитані з вхідного файлу. Вам просто потрібно помасажувати часові позначки, щоб вони могли їх розпізнати. Синтаксис введення для позначки часу на основі епохи Unix @супроводжується кількістю секунд, яка може містити десяткову точку.

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

+1 для використання dateзчитування файлу s. Це призведе date: invalid date ‘@’до перекладу поточного каталогу ( ./). А оскільки ви можете викинути мілісекунди, ви можете спростити друге sedредагування, щоб просто скинути останні три символи. Або видаліть усе це і використовуйтеfind * -type d -printf "@%.10f" | date ...
Антон

5

Я б це зробив спритно - подайте у список часових позначок:

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

Цей результат:

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

Якщо вам потрібен певний формат виводу, ви можете використовувати, strftimeнаприклад:

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

Що перетворити це на один вкладиш у вашій трубі:

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

Але я, мабуть, запропонував би замість цього переглянути File::Findмодуль і зробити все це на перлі. Якщо ви наводите приклад структури свого каталогу, перш ніж вирізати, я наведу вам приклад. Але це було б щось на кшталт:

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

З zshі STRFTIME вбудована:

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

це передбачає, що всі назви ваших каталогів у поточному каталозі насправді є епохами.
Можлива подальша фільтрація / обробка за умови, що ви поясните, як слід обробляти ці цифри у вашому прикладі (вони виглядають більше як епохальний час, що відповідає датам народження принцеси Леї та Люка Скайуокера ...), наприклад, рекурсивно шукайте імена каталогів, що відповідають щонайменше. 10 цифр і обчисліть дату на основі перших 10 цифр:

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

2

Використання GNU Parallel:

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

Якщо ви можете прийняти \ t замість місця:

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

Зверніть увагу, що parallelнаписано в perl. Це здається непосильним, враховуючи, що у perlнього є strftime()оператор. Likeperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Stéphane Chazelas

2
1. Це коротше. 2. Вам не потрібно вчитися Perl.
Оле Танге

1
Це на 27% коротше, але на кілька порядків менш ефективне (приблизно в 800 разів повільніше в тесті, який я зробив; вважайте, що він повинен нереститися оболонкою (ваша оболонка, а не / бін / ш) та командою дати для кожного рядка) і недоброзичливий до системи, оскільки він обтяжує всі CPU одночасно. І вам ще потрібно вчитися parallel. IMO, parallelце чудовий інструмент для паралельного виконання інтенсивних завдань процесора, але не дуже підходить для такого роду завдань тут.
Стефан Шазелас

Існує безліч контекстів, коли ефективність не викликає особливих проблем, тому це все-таки прийнятне рішення, але все ж варто згадати питання про продуктивність, особливо якщо врахувати, що паралельно зазвичай римується з високою ефективністю в розумі людей.
Стефан Шазелас

0

Зазвичай команда find може бути пов'язана будь-якою командою за допомогою execаргументу.

У вашому випадку ви можете зробити так:

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

0

Використання Python (це найбільш можливо повільне рішення)

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

дає:

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

Чому б не зробити все це в python? Замість того, щоб прикувати купу труб?
Sobrique

Було б краще сенсу. Я згоден.
луказ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.