Як ланцюжок команд 'дата -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.