Jak zamienić znaczniki czasu epoki w pliku na inne formaty?

10

Mam plik zawierający daty epoki, które muszę przekonwertować na czytelne dla człowieka. Wiem już, jak wykonać konwersję daty, np .:

[server01 ~]$ date -d@1472200700
Fri 26 Aug 09:38:20 BST 2016

.. ale staram się wymyślić, jak sedprzejść przez plik i przekonwertować wszystkie wpisy. Format pliku wygląda następująco:

#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web
mechanik
źródło
1
Dla przyszłego odniesienia (zakładając, że jest to plik historii Bash; wygląda jak jeden), spójrz na HISTTIMEFORMATzmienną powłoki, aby kontrolować format w czasie pisania.
Toby Speight
@Toby wartość HISTTIMEFORMAT jest używana podczas wyświetlania (do standardowego wyjścia), ale tylko jej status (ustawiony na cokolwiek równy zero lub nie ustawiony) ma znaczenie przy pisaniu HISTFILE.
dave_thompson_085
Dzięki @dave, nie wiedziałem o tym (sam nie jestem użytkownikiem historii).
Toby Speight
date -dnie jest przenośny, żeby powiedzieć, że Solaris ... Zakładam, że jest to system z większością narzędzi GNU? (GNU AWK / Perl to zwykle bardziej przenośne metody radzenia sobie z konwersjami dat). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimewydaje się nieprzenośny ...)
Gert van den Berg

Odpowiedzi:

6

Zakładając spójny format pliku, bashmożesz odczytać plik linia po linii, sprawdzić, czy jest w danym formacie, a następnie wykonać konwersję:

while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && \
      date -d@"${BASH_REMATCH[1]}"; done <file.txt

BASH_REMATCHto tablica, której pierwszym elementem jest pierwsza grupa przechwycona w dopasowaniu Regex =~, w tym przypadku epoka.


Jeśli chcesz zachować strukturę plików:

while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' \
   "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt

spowoduje to przesłanie zmodyfikowanej zawartości do STDOUT, aby zapisać ją w pliku np . out.txt:

while ...; do ...; done >out.txt

Teraz, jeśli chcesz, możesz zastąpić oryginalny plik:

mv out.txt file.txt

Przykład:

$ cat file.txt
#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web

$ while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && date -d@"${BASH_REMATCH[1]}"; done <file.txt
Wed Aug 24 20:09:55 BDT 2016
Wed Aug 24 20:11:46 BDT 2016
Wed Aug 24 20:13:58 BDT 2016

$ while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt
#Wed Aug 24 20:09:55 BDT 2016
ll /data/holding/email
#Wed Aug 24 20:11:46 BDT 2016
cat /etc/rsyslog.conf
#Wed Aug 24 20:13:58 BDT 2016
ll /data/holding/web
heemayl
źródło
Fajnie .... drukuje przekonwertowaną datę na ekran, a teraz jak uzyskać to polecenie, aby zastąpić wpisy w pliku?
mechanik
@machinist Sprawdź moje zmiany ..
heemayl
1
Jeśli używasz najnowszej wersji bash, printfmoże to zrobić sam konwersji: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
chepner
14

Chociaż jest to możliwe w GNU sedz takimi rzeczami jak:

sed -E 's/^#([0-9]+).*$/date -d @\1/e'

Byłoby to wyjątkowo nieefektywne (i łatwo wprowadzić luki w zabezpieczeniach polegające na wstrzykiwaniu dowolnych poleceń 1 ), ponieważ oznaczałoby to uruchomienie jednej powłoki i jednego datepolecenia dla każdej #xxxxlinii, praktycznie tak źle, jak while readpętla powłoki . Lepiej byłoby użyć takich rzeczy jak perllub gawk, to znaczy narzędzi do przetwarzania tekstu, które mają wbudowane funkcje konwersji daty:

perl  -MPOSIX -pe 's/^#(\d+).*/ctime $1/se'

Lub:

gawk '/^#/{$0 = strftime("%c", substr($0, 2))};1'

1 Gdybyśmy napisali ^#([0-9]).*zamiast ^#([0-9]).*$(jak to zrobiłem we wcześniejszej wersji tej odpowiedzi), to w lokalizacjach wielobajtowych, takich jak UTF-8 (obecnie norma), z wejściem typu #1472047795<0x80>;reboot, gdzie to <0x80>jest wartość bajtu 0x80, która nie tworzy poprawnego znaku, to spolecenie skończyłoby się date -d@1472047795<0x80>; rebootna przykład uruchomieniem . Chociaż z dodatkowymi $wierszami te nie zostałyby zastąpione. Alternatywnym podejściem byłoby:, s/^#([0-9])/date -d @\1 #/eto znaczy pozostawić część po #xxxdacie jako komentarz powłoki

Stéphane Chazelas
źródło
1
Co powiesz na użycie tylko jednej instancjidate -f do wykonania wszystkich konwersji w sposób strumieniowy?
Cyfrowa trauma
Polecenie perl wydaje się dodawać nową linię po ctime 1 $ i nie mogę znaleźć żadnego sposobu, aby ją usunąć.
Alex Harvey
1
@Alex. Dobrze. Zobacz edycję. Dodanie sflagi powoduje, że .*zawiera także nowy wiersz na wejściu. Możesz także użyć strftime "%c", localtime $1.
Stéphane Chazelas
@ StéphaneChazelas dzięki bardzo. To świetna odpowiedź.
Alex Harvey
3

Wszystkie pozostałe odpowiedzi datepowodują pojawienie się nowego procesu dla każdej daty epoki, którą należy przekonwertować. Może to potencjalnie zwiększyć obciążenie wydajnościowe, jeśli dane wejściowe są duże.

Jednak data GNU ma przydatną -fopcję, która pozwala jednemu procesowi datena ciągłe odczytywanie danych wejściowych bez potrzeby nowego widelca. Możemy więc użyć sed, pastei datew ten sposób, że każdy zostanie spawnowany tylko raz (2x dla sed), niezależnie od tego, jak duże jest wejście:

$ paste -d '\n' <( sed '2~2d;y/#/@/' epoch.txt | date -f - ) <( sed '1~2d' epoch.txt )
Wed Aug 24 07:09:55 PDT 2016
ll /data/holding/email
Wed Aug 24 07:11:46 PDT 2016
cat /etc/rsyslog.conf
Wed Aug 24 07:13:58 PDT 2016
ll /data/holding/web
$ 
  • Te dwa sedpolecenia odpowiednio usuwają parzyste i nieparzyste linie danych wejściowych; pierwszy zastępuje również #ze @dać poprawny format epoka datownika.
  • Następnie sedprzesyłane jest pierwsze wyjście, do date -fktórego dokonuje wymaganej konwersji daty, dla każdego otrzymanego wiersza danych wejściowych.
  • Te dwa strumienie są następnie przeplatane do pojedynczego wymaganego wyjścia za pomocą paste. Te <( )konstrukcje są bash substytucje procesowe , które skutecznie sztuczka wklej do myśli, że czyta z podanych nazwach plików, kiedy jest w rzeczywistości czyta wyjście rurami od wewnątrz polecenia. -d '\n'każe pasteoddzielić nieparzyste i parzyste linie wyjściowe znakiem nowej linii. Możesz to zmienić (lub usunąć), jeśli na przykład chcesz znacznik czasu w tym samym wierszu, co w innym tekście.

Zauważ, że w tym poleceniu jest kilka GNUizmów i Baszizmów. Nie jest to zgodne z Posix i nie należy oczekiwać, że będzie przenośne poza światem GNU / Linux. Na przykład date -frobi coś innego w wariancie BSD OSXes date.

Cyfrowa trauma
źródło
date -d(z pytania) jest również nieprzenośny ... (Na FreeBSD będzie próbował zadzierać z ustawieniami DST, w Solarisie da błąd ...) Pytanie nie określa jednak systemu operacyjnego ...
Gert van den Berg
@GertvandenBerg tak, jest to omówione w ostatnim akapicie tej odpowiedzi.
Cyfrowa trauma
Chodzi mi o to, że próbka pytającego ma również problemy z przenośnością ... (Prawdopodobnie powinni oznaczyć system operacyjny ...)
Gert van den Berg
1

Zakładając, że format daty, który masz w swoim poście, jest tym, czego chcesz, następujący regex powinien pasować do twoich potrzeb.

sed -E 's/\#(1[0-9]{9})(.*)/echo \1 $(date -d @\1)/e' log.file

Pamiętaj, że zastąpi to tylko jedną epokę w wierszu.

Hatclock
źródło
Otrzymuję następujący błąd z tym poleceniem: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
mechanik
1
Mój błąd, zredagowałem post.
Hatclock
0

przy użyciu sed:

sed -r 's/\#([0-9]*)/echo $(date -d @\1)/eg' test.txt

wynik :

ر أغس 24 16:09:55 EET 2016
ll /data/holding/email
ر أغس 24 16:11:46 EET 2016
cat /etc/rsyslog.conf
ر أغس 24 16:13:58 EET 2016
ll /data/holding/web

ponieważ moim językiem regionalnym jest arabski :)

hassan
źródło
0

Moje rozwiązanie, jak to zrobić w potoku

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
kayn
źródło