Как объединить команды date -d @xxxxxx и find ./?


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

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возможности чтения файлов


Или, xargsесли у вас нет parallel, что многие люди, вероятно, не имеют.
Хоббс

@hobbs AFAIK xargsне имеет возможности указать, куда идет аргумент, как parallelс {}.
Антон

4
Это делает:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti

@ Нет, если вы используете -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

@Антон, ах да, пропустил это требование. Теперь должно быть в порядке.
Стефан Шазелас

8

У тебя уже есть:

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

который предположительно получает вам метки времени в формате эпохи. Теперь добавьте цикл while:

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специфический) нужен только если вы хотите повторно использовать результат внутри цикла, однако.


независимо от того, говоря, что это специфично для bash, это не правильно :)
Wouter Verhelst

На самом деле, как вы изначально написали, done <(find)вместо этого done < <(find)оно было правильным yash(где <(...)перенаправление процесса, а не подстановка процесса), поэтому мое редактирование было немного кавалернее, поскольку это могла быть оболочка, для которой вы имели в виду.
Стефан Шазелас

6

Если у вас есть GNU date, он может конвертировать даты, прочитанные из входного файла. Вам просто нужно немного помассировать временные метки, чтобы они могли их распознать. Синтаксис ввода для временной метки, основанной на эпохе Unix, @сопровождается числом секунд, которое может содержать десятичную точку.

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

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

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модуля и сделать все это в Perl. Если вы приведете пример своей структуры каталогов, прежде чем разрезать ее, я приведу вам пример. Но это было бы что-то вроде:

#!/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()оператор. Нравитсяperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Стефан Шазелас

2
1. Это короче. 2. Вам не нужно изучать Perl.
Оле Танге,

1
Он на 27% короче, но на несколько порядков менее эффективен (примерно в 800 раз медленнее в тесте, который я сделал; учтите, что он должен порождать оболочку (ваша оболочка, а не / bin / sh) и команду date для каждой строки) и недружелюбно относится к системе, поскольку она обременяет все процессоры одновременно. И вам все еще нужно учиться 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.