Làm cách nào để xâu chuỗi các lệnh 'date -d @xxxxxx' và 'find ./'?


14

Tôi có các thư mục có tên là dấu thời gian, được đưa ra trong một phần nghìn giây kể từ 1970-01-01:

1439715011728
1439793321429
1439879712214
.
.

Và tôi cần một đầu ra như:

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

Tôi có thể liệt kê tất cả các thư mục bằng lệnh:

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

Nhưng tôi không thể đặt đầu ra cho lệnh tiếp theo: date -d @xxxxxxvà thao tác đầu ra.

Tôi có thể làm cái này như thế nào?


2
Làm thế nào để những dấu thời gian dịch sang epoch? Bởi vì số của bạn quá dài ... (Đó là số đầu tiên - là Fri Oct 2 05:35:28 47592)
Sobrique

1
@Sobrique Rõ ràng là mili giây kể từ thời đại.
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời:


10

Bạn đang đi đúng hướng (đối với một giải pháp đơn giản hơn, chỉ chạy 2 hoặc 3 lệnh, xem bên dưới). Bạn nên sử dụng *thay vì ./loại bỏ thư mục hiện tại¹ và điều này giúp đơn giản hóa việc cắt phần nghìn giây, sau đó chỉ cần đưa kết quả vào GNU parallelhoặc xargs²:

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

để có đượ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

và để thêm giây bù trước đó như ví dụ của bạn chỉ ra:

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

hoặc là:

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

để có đượ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

Tuy nhiên, nó đơn giản hơn để làm³:

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

một lần nữa giúp bạn có cùng một đầu ra được yêu cầu.

Nhược điểm của việc sử dụng *là bạn bị giới hạn bởi dòng lệnh của mình cho việc mở rộng của nó, tuy nhiên ưu điểm là bạn có được các thư mục được sắp xếp theo giá trị dấu thời gian. Nếu số lượng thư mục là một vấn đề sử dụng -mindepth 1, nhưng mất thứ tự:

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

và chèn sortnếu cần:

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

¹ này giả định không có thư mục con lồng nhau, như có vẻ là trường hợp từ ví dụ của bạn. Bạn cũng có thể sử dụng ./ -mindepth 1thay vì*
² Bạn có thể thay thế parallelbằng xargs -I{}ở đây như @hobbs và @don_crissti đề xuất, nó chỉ dài dòng hơn. ³ dựa trên Gilles' câu trả lời cho sử dụng datecủa khả năng tập đọc


Hoặc xargsnếu bạn không có parallel, điều mà nhiều người có thể không có.
hobbs

@hobbs AFAIK xargskhông có tùy chọn để chỉ định nơi lập luận đi như thế parallelcó với {}.
Anthon

4
Nó hiện:find ./ -type d | cut -c 3-12 | xargs -I{} date --d @{} +'%Y-%m-%d'
don_crissti

@Anthon nó làm nếu bạn sử dụng -Itùy chọn.
hobbs

1
@Anthon, các tùy chọn dài của GNU có thể được viết tắt miễn là chúng không mơ hồ. --dhoặc --dasẽ hoạt động với các phiên bản hiện tại của GNU date, nhưng nó có thể ngừng hoạt động vào ngày dategiới thiệu một --dalektùy chọn (cho các ngày trong lịch Dalek).
Stéphane Chazelas

10

Tôi sẽ tránh chạy một số lệnh cho mỗi tệp trong một vòng lặp. Vì bạn đã sử dụng GNU:

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

Mà chỉ chạy hai lệnh. strftime()là đặc trưng của GNU, như date -d.


Điều này không cắt giảm mili giây của tên thư mục nhưng hiển thị đầy đủ 13 ký tự thay vì 10 ký tự đầu tiên được yêu cầu
Anthon

@Anthon, ah vâng, bỏ lỡ yêu cầu đó. Nên ổn bây giờ.
Stéphane Chazelas

8

Bạn đã có:

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

có lẽ sẽ giúp bạn có dấu thời gian ở định dạng epoch. Bây giờ thêm một vòng lặp while:

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

Lưu ý rằng trong một số shell, cú pháp đó có vòng lặp while trong một lớp con, điều đó có nghĩa là nếu bạn cố gắng đặt một biến ở đó, nó sẽ không hiển thị khi bạn rời khỏi vòng lặp. Để khắc phục điều đó, bạn cần xoay nhẹ mọi thứ trên đầu chúng:

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

trong đó đặt findlớp vỏ con và giữ vòng lặp while trong vỏ chính. Cú pháp (AT & T ksh, zshbashcụ thể) chỉ cần thiết nếu bạn đang tìm kiếm để tái sử dụng Kết quả là từ bên trong vòng lặp, mặc dù.


bất kể, nói rằng đó là bash cụ thể không chính xác :)
Wouter Verhelst

Trên thực tế, như ban đầu bạn đã viết nó, done <(find)thay vì done < <(find), nó đúng cho yash(nơi <(...)chuyển hướng quá trình, không phải thay thế quá trình), vì vậy chỉnh sửa của tôi là một chút ung dung vì nó có thể là vỏ mà bạn muốn nói.
Stéphane Chazelas

6

Nếu bạn có ngày GNU, nó có thể chuyển đổi ngày đọc từ tệp đầu vào. Bạn chỉ cần xoa bóp dấu thời gian một chút để nó có thể nhận ra chúng. Cú pháp đầu vào cho dấu thời gian dựa trên epoch Unix được @theo sau bởi số giây, có thể chứa dấu thập phân.

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

+1 để sử dụng dateđọc tệp s. Điều này sẽ cung cấp cho một date: invalid date ‘@’bản dịch của thư mục hiện tại ( ./). Và vì bạn có thể vứt đi một phần nghìn giây, bạn có thể đơn giản hóa sedchỉnh sửa thứ hai để chỉ bỏ 3 ký tự cuối cùng. Hoặc xóa tất cả những thứ đó và sử dụngfind * -type d -printf "@%.10f" | date ...
Anthon

5

Tôi sẽ làm điều đó một cách thành công - đưa vào danh sách các dấu thời gian:

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

Kết quả này:

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

Nếu bạn muốn một định dạng đầu ra cụ thể, bạn có thể sử dụng, strftimeví dụ:

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

Mà để biến điều này thành một lớp lót trong đường ống của bạn:

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

Nhưng có lẽ tôi đề nghị thay vì nhìn vào việc sử dụng File::Findmô-đun và thực hiện toàn bộ trong perl thay thế. Nếu bạn đưa ra một ví dụ về cấu trúc thư mục của bạn trước khi cắt nó, tôi sẽ cho bạn một ví dụ. Nhưng nó sẽ giống như:

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

Với zshvà các strftime dựng sẵn:

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

điều này giả sử tất cả các tên thư mục của bạn trong thư mục hiện tại thực sự là thời gian kỷ nguyên.
Có thể lọc / xử lý thêm với điều kiện bạn làm rõ cách xử lý các số đó trong ví dụ của bạn (chúng trông giống thời gian kỷ nguyên tương ứng với ngày sinh của Công chúa Leia và Luke Skywalker ...), ví dụ: tìm kiếm đệ quy các tên thư mục khớp với ít nhất 10 chữ số và tính ngày dựa trên 10 chữ số đầu tiên:

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

2

Sử dụng song song GNU:

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

Nếu bạn có thể chấp nhận thay vì dấu cách:

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

Lưu ý rằng parallelđược viết bằng perl. Điều này có vẻ quá mức cần thiết khi xem xét perlcó một strftime()nhà điều hành. Thíchperl -MPOSIX -lpe '$_.=strftime(" %c", localtime substr $_, 2, 10)'
Stéphane Chazelas

2
1. Nó ngắn hơn. 2. Bạn không cần phải học Perl.
Ole Tange

1
Nó ngắn hơn 27%, nhưng nó có hiệu suất thấp hơn vài lần (chậm hơn khoảng 800 lần trong thử nghiệm tôi đã thực hiện; xem xét nó cần sinh ra một vỏ (vỏ của bạn, không phải / bin / sh) và lệnh ngày cho mỗi dòng) và không thân thiện với hệ thống vì nó gánh tất cả các CPU cùng một lúc. Và bạn vẫn cần phải học parallel. IMO, parallellà một công cụ tuyệt vời để song song hóa các tác vụ chuyên sâu của CPU, nhưng không thực sự phù hợp với loại nhiệm vụ này ở đây.
Stéphane Chazelas

Có rất nhiều bối cảnh mà hiệu quả không phải là vấn đề đáng lo ngại, vì vậy đây vẫn là một giải pháp chấp nhận được, nhưng vẫn đáng đề cập đến vấn đề hiệu suất, đặc biệt là khi xem xét rằng song song thường có vần với hiệu suất cao trong tâm trí mọi người.
Stéphane Chazelas

0

Thông thường lệnh find có thể được nối với bất kỳ lệnh nào bằng cách sử dụng execđối số.

Trong trường hợp của bạn, bạn có thể làm như thế này:

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

0

Sử dụng Python (đó là giải pháp chậm nhất có thể)

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

cho:

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

Tại sao không làm tất cả trong python? Thay vì xâu chuỗi một loạt các đường ống?
Sobrique

Nó sẽ có ý nghĩa tốt hơn. Tôi đồng ý.
lukaz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.