Czy istnieje narzędzie Unix do dołączania sygnatur czasowych do stdin?

168

Skończyło się na napisaniu krótkiego skryptu do tego w Pythonie, ale zastanawiałem się, czy istnieje narzędzie, do którego można by wprowadzić tekst, które poprzedzałoby każdą linię jakimś tekstem - w moim przypadku znacznikiem czasu. Idealnie byłoby użyć czegoś takiego:

cat somefile.txt | prepend-timestamp

(Zanim odpowiesz sed, próbowałem tego:

cat somefile.txt | sed "s/^/`date`/"

Ale to oblicza datę tylko raz, gdy sed jest wykonywane, więc ten sam znacznik czasu jest niepoprawnie dodawany do każdej linii.)

Joe Shaw
źródło
Czy jest cat somefile.txtto trochę „mylące”? Spodziewałbym się, że stanie się to „od razu” i będzie miało jeden znacznik czasu. Czy nie byłoby to lepsze program testowy: (echo a; sleep 1; echo b; sleep 3; echo c; sleep 2)?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

185

Możesz spróbować użyć awk:

<command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }'

Być może będziesz musiał się upewnić, że <command>wytwarza buforowane liniowo wyjście, tj. Opróżnia swój strumień wyjściowy po każdej linii; awkdodanie znacznika czasu będzie oznaczało czas pojawienia się końca linii na potoku wejściowym.

Jeśli awk wyświetla błędy, spróbuj gawkzamiast tego.

Kieron
źródło
31
W moim systemie buforował się sam „awk”. (Może to być problematyczne w przypadku plików dziennika). Naprawiłem to, używając: awk '{print strftime ("% Y-% m-% dT% H:% M:% S"), $ 0; fflush (); } '
user47741
19
strftime () wydaje się być rozszerzeniem GNU awk, więc jeśli na przykład używasz Mac OS, użyj gawk zamiast awk.
Joe Shaw
Nie przestaje mnie zadziwiać, jak potężny jest bash. Nie sądziłem, że będzie tak łatwe rozwiązanie tego skomplikowanego zadania.
utwórz
2
W przypadku użytkowników OS X musisz zainstalować gawki używać tego zamiast awk. Jeśli masz MacPorts:sudo port install gawk
Matt Williamson
5
@teh_senaus O ile wiem, awknic strftime()nie ma milisekundy precyzji. Możesz jednak użyć datepolecenia. Zasadniczo po prostu przycinasz nanosekundy do trzech znaków. Będzie to wyglądać tak: COMMAND | while read -r line; do echo "$(date '+%Y-%m-%d %T.%3N') $line"; done. Zauważ, że możesz skrócić% H:% M:% S za pomocą% T. Jeśli awkz jakiegoś powodu nadal chcesz korzystać , możesz wykonać następujące czynności:COMMAND | awk '{ "date +%Y-%m-%d\\ %T.%3N" | getline timestamp; print timestamp, $0; fflush(); }'
Szósty
190

tsz moreutils doda znacznik czasu do każdego wprowadzonego wiersza. Możesz go również sformatować za pomocą strftime.

$ echo 'foo bar baz' | ts
Mar 21 18:07:28 foo bar baz
$ echo 'blah blah blah' | ts '%F %T'
2012-03-21 18:07:30 blah blah blah
$ 

Aby go zainstalować:

sudo apt-get install moreutils
Mark McKinstry
źródło
2
uchwycić i stemplować stderr w tym samym czasie: bad command 2>&1 | tswyniki wJan 29 19:58:31 -bash: bad: command not found
rymo
Chcę użyć formatu czasu 2014 Mar 21 18:07:28. Jak mogę to dostać?
becko
@becko - zgodnie ze stroną podręcznika , strftimejako argument podaj ciąg formatu: [.. command ..] | ts '%Y %b %d %T'na przykład.
Toby Speight
2
Dla OS X, brew install moreutils. Aby wyświetlić UTC, możesz ustawić TZ lokalnie, np.ls -l |TZ=UTC ts '%Y-%m-%dT%H:%M:%SZ'
karmakaze
1
@Dorian dzieje się tak, ponieważ Ruby buforuje dane wyjściowe; jeśli ruby -e "puts 1; STDOUT.flush; sleep 1; puts 2; STDOUT.flush; sleep 2; puts 3" | ts '%F %T'
opróżnisz
45

adnotate , dostępne pod tym linkiem lub jak annotate-outputw devscriptspakiecie Debiana .

$ echo -e "a\nb\nc" > lines
$ annotate-output cat lines
17:00:47 I: Started cat lines
17:00:47 O: a
17:00:47 O: b
17:00:47 O: c
17:00:47 I: Finished with exitcode 0
Ted Percival
źródło
1
Jest to świetne rozwiązanie, ale nie będzie działać z wyjściem potokowym lub wyjściem podpowłokowym. Sformatuje tylko dane wyjściowe programu lub skryptu, który podasz jako argument.
slm
1
annotate-output działa ładnie i jest łatwy w użyciu, ale jest NAPRAWDĘ wolny.
Paul Brannan
31

Podane odpowiedzi destylujemy do najprostszej możliwej:

unbuffer $COMMAND | ts

W Ubuntu pochodzą one z pakietów Expect-dev i moreutils.

sudo apt-get install expect-dev moreutils
Willem
źródło
1
Tak expectnie jest expect-dev, ale ta druga wciąga pierwszą, więc to działa. (Ubuntu 14.10, zastanawiam się, czy plik binarny został kiedyś przeniesiony do innego pakietu.)
Marius Gedminas
W przypadku Ubuntu 14.04 LTS jest nadal dostępnyexpect-dev
Willem
2
Jeśli chcesz uzyskać czas, który upłynął z dokładnością do milisekund, użyj unbuffer $COMMAND | ts -s %.s( -sjeśli dla czasu, który upłynął i %.sdla czasu w sekundach z częścią ułamkową)
Greg Witczak
24

Co powiesz na to?

cat somefile.txt | perl -pne 'print scalar(localtime()), " ";'

Sądząc po chęci uzyskania znaczników czasu na żywo, może chcesz aktualizować na żywo plik dziennika lub coś takiego? Może

tail -f /path/to/log | perl -pne 'print scalar(localtime()), " ";' > /path/to/log-with-timestamps
jj33
źródło
4
Ten przykład z Perla był dla mnie szczególnie przydatny, ponieważ pracowałem jako użytkownik na wdrożeniu Ubuntu, które najwyraźniej używa „mawk” zamiast „gawk” - i nie ma funkcji strftime. Dzięki!
opello
4
+1 do tego, że jest przydatny w domyślnej instalacji Ubuntu. Kiedyś tail -f /path/to/log | perl -pne 'use POSIX qw(strftime); print strftime "%Y-%m-%dT%H:%M:%SZ ", gmtime();'otrzymywałem datę w stylu ISO8601 / RFC3339 zamiast wacko, którą daje prostsza wersja.
natevw
1
Jeśli chcesz mieć możliwość przyjrzenia się wynikowi dziennika ze znacznikiem czasu, gdy nadejdzie, pomocne może być dodanie BEGIN { $|++; }przed instrukcją print, aby Perl opróżniał swoje wyjście z każdą linią.
2
Możesz skrócić skrypt Perla dalej do samego ls | perl -pne 'print localtime." "'. Konwersja skalarna następuje w wyniku konkatenacji ciągów.
rahul
Dlaczego używasz obu opcji -pi -n? Wydaje się, że -njest zbędny, jeśli określiłeś -p.
Davor Cubranic
19

Po prostu wyrzucę to: w narzędziach daemontools o nazwach tai64n i tai64nlocal są stworzone do dodawania znaczników czasu do dziennika komunikatów.

Przykład:

cat file | tai64n | tai64nlocal
chazomaticus
źródło
18

Odpowiedź Kierona jest jak dotąd najlepsza. Jeśli masz problemy, ponieważ pierwszy program buforuje swoje dane, możesz użyć programu niebuforującego:

unbuffer <command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; }'

Jest instalowany domyślnie w większości systemów linuxowych. Jeśli chcesz zbudować go samodzielnie, jest to część pakietu oczekującego

http://expect.nist.gov

Mark Harrison
źródło
Zauważ, że odkryłem, że 'unbuffer' czasami ukrywa wynik powrotu wywoływanego programu - więc nie jest przydatny w Jenkinsie lub innych rzeczach, które zależą od statusu zwrotu
oskarpearson
10

Użyj polecenia read (1), aby odczytać po jednym wierszu na raz ze standardowego wejścia, a następnie wypisz wiersz poprzedzony datą w wybranym przez siebie formacie przy użyciu daty (1).

$ cat timestamp
#!/bin/sh
while read line
do
  echo `date` $line
done
$ cat somefile.txt | ./timestamp
caerwyn
źródło
2
Trochę ciężki, ponieważ obejmuje nowy proces na linię, ale działa!
Joe Shaw,
3

Nie jestem facetem od Uniksa, ale myślę, że możesz użyć

gawk '{print strftime("%d/%m/%y",systime()) $0 }' < somefile.txt
PabloG
źródło
3
#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
        ($s,$ms) = gettimeofday();
        print $s . "." . $ms . " " . $_;
}'

źródło
2

Oto moje rozwiązanie awk (z systemu Windows / XP z narzędziami MKS zainstalowanymi w katalogu C: \ bin). Jest przeznaczony do dodawania aktualnej daty i czasu w postaci mm / dd gg: mm na początku każdego wiersza po pobraniu tego znacznika czasu z systemu podczas odczytywania każdego wiersza. Możesz oczywiście użyć wzorca BEGIN, aby raz pobrać znacznik czasu i dodać go do każdego rekordu (tak samo). Zrobiłem to, aby oznaczyć plik dziennika, który był generowany na stdout, znacznikiem czasu w momencie generowania komunikatu dziennika.

/"pattern"/ "C\:\\\\bin\\\\date '+%m/%d %R'" | getline timestamp;
print timestamp, $0;

gdzie „wzorzec” jest ciągiem lub wyrażeniem regularnym (bez cudzysłowów) do dopasowania w wierszu wejściowym i jest opcjonalne, jeśli chcesz dopasować wszystkie wiersze wejściowe.

Powinno to działać również w systemach Linux / UNIX, po prostu pozbądź się C \: \\ bin \\ opuszczającego linię

             "date '+%m/%d %R'" | getline timestamp;

Zakłada to oczywiście, że polecenie „date” prowadzi do standardowego polecenia wyświetlania / ustawiania daty w systemie Linux / UNIX bez określonych informacji o ścieżce (to znaczy, że zmienna środowiska PATH jest poprawnie skonfigurowana).

ElGringoGeek
źródło
Na OS XI potrzebna była spacja przed poleceniem date. Niestety nie wydaje się, aby ponownie oceniał polecenie dla każdej linii. Jak sugerowali inni, próbuję dodać sygnatury czasowe do końca -f.
Mark Bennett
2

Połączenie kilku powyższych odpowiedzi od natevw i Franka Ch. Eigler.

Ma milisekundy, działa lepiej niż wywoływanie zewnętrznej datekomendy za każdym razem, a perl można znaleźć na większości serwerów.

tail -f log | perl -pne '
  use Time::HiRes (gettimeofday);
  use POSIX qw(strftime);
  ($s,$ms) = gettimeofday();
  print strftime "%Y-%m-%dT%H:%M:%S+$ms ", gmtime($s);
  '

Alternatywna wersja z opróżnieniem i odczytem w pętli:

tail -f log | perl -pne '
  use Time::HiRes (gettimeofday); use POSIX qw(strftime);
  $|=1;
  while(<>) {
    ($s,$ms) = gettimeofday();
    print strftime "%Y-%m-%dT%H:%M:%S+$ms $_", gmtime($s);
  }'
Keymon
źródło
1
Dwie wady z powyższym: 1. $ ms nie jest wyrównane i wypełnione zerami. 2. Wszelkie kody% w dzienniku zostaną zniekształcone. Tak więc zamiast linii drukowania użyłem: printf "%s+%06d %s", (strftime "%Y-%m-%dT%H:%M:%S", gmtime($s)), $ms, $_;
Simon Pickup
2
$ cat somefile.txt | sed "s/^/`date`/"

możesz to zrobić (z gnu / sed ):

$ some-command | sed "x;s/.*/date +%T/e;G;s/\n/ /g"

przykład:

$ { echo 'line1'; sleep 2; echo 'line2'; } | sed "x;s/.*/date +%T/e;G;s/\n/ /g"
20:24:22 line1
20:24:24 line2

oczywiście możesz skorzystać z innych opcji daty programu . po prostu zastąp date +%Tto, czego potrzebujesz.

aleksandr barakin
źródło
1

Odpowiedź caerwyn może zostać uruchomiona jako podprocedura, która uniemożliwiłaby nowe procesy w linii:

timestamp(){
   while read line
      do
         echo `date` $line
      done
}

echo testing 123 |timestamp
crumplecrap
źródło
3
jak to zapobiega pojawianiu datesię w każdej linii?
Gyom
@Gyom Nie zapobiega pojawianiu się daty w każdej linii. Zapobiega powstawaniu powłoki w każdej linii. Dodam, że w bashu możesz zrobić coś takiego: timestamp() { while read line; do echo "$(date) $line"; done; }; export -f timestampw skrypcie, którego źródła.
smbear
Wywołanie daty jako podprocesu nadal oznacza tworzenie go dla każdej linii, jednak najpierw uruchamiasz skrypt.
Peter Hansen
3
Aby zapobiec poleceniu odradzania się datew każdej linii, spróbuj użyć printfwbudowanego bash z formatem % (datepec) T. Więc może to wyglądać timestamp() { while IFS= read -r line; do printf "%(%F %T)T %s\n" -1 "$line"; done; }. Wbudowane powłoki nie pojawią się. Format Datespec jest podobny do formatów strftime(3)np date. Wymaga to basha, nie jestem pewien, od której wersji obsługuje ten printfspecyfikator.
Mindaugas Kubilius
1

Zastrzeżenie : rozwiązanie, które proponuję, nie jest narzędziem wbudowanym w system Unix.

Z podobnym problemem miałem do czynienia kilka dni temu. Nie podobała mi się składnia i ograniczenia powyższych rozwiązań, więc szybko złożyłem program w Go, aby wykonać zadanie za mnie.

Możesz sprawdzić narzędzie tutaj: preftime

W sekcji Wydania projektu GitHub znajdują się gotowe pliki wykonywalne dla systemów Linux, MacOS i Windows .

Narzędzie obsługuje niekompletne wiersze wyjściowe i ma (z mojego punktu widzenia) bardziej zwartą składnię.

<command> | preftime

To nie jest idealne, ale pomyślałem, że podzielę się nim na wypadek, gdyby to komuś pomogło.

Momchil Atanasov
źródło
0

robi to z datei tra xargsna OSX:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S\" | tr \"\n\" \" \"; echo \"{}\"'"
<command> | predate

jeśli chcesz milisekund:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"

ale pamiętaj, że na OSX data nie daje opcji% N, więc musisz zainstalować gdate ( brew install coreutils) i ostatecznie dojść do tego:

alias predate="xargs -I{} sh -c 'gdate +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"
orion elenzil
źródło
-1

Jeśli dodawana wartość jest taka sama w każdym wierszu, uruchom emacsa z plikiem, a następnie:

Ctrl + <space>

na początku pliku (aby zaznaczyć to miejsce), a następnie przewiń w dół do początku ostatniej linii (Alt +> przejdzie do końca pliku ... co prawdopodobnie będzie wymagało również naciśnięcia klawisza Shift, a następnie Ctrl + a, aby przejść na początek tego wiersza) i:

Ctrl + x r t

Które polecenie należy wstawić do właśnie określonego prostokąta (prostokąta o szerokości 0).

2008-8-21 18:45 <enter>

Lub cokolwiek chcesz dodać na początku ... wtedy zobaczysz ten tekst dołączony do każdego wiersza w prostokącie o szerokości 0.

AKTUALIZACJA: Właśnie zdałem sobie sprawę, że nie chcesz tej SAMEJ daty, więc to nie zadziała ... chociaż możesz być w stanie to zrobić w emacsie za pomocą nieco bardziej skomplikowanego niestandardowego makra, ale nadal ten rodzaj edycji prostokąta jest miło wiedzieć o ...

Mike Stone
źródło