web-dev-qa-db-ja.com

コマンドからの出力の各行にタイムスタンプを付加する

コマンドからの出力の各行にタイムスタンプを付加したいと思います。例えば:

foo
bar
baz

なるだろう

[2011-12-13 12:20:38] foo
[2011-12-13 12:21:32] bar
[2011-12-13 12:22:20] baz

...接頭辞が付けられる時間は、行が印刷された時間です。どうすればこれを達成できますか?

205
anon

moreutils にはこれを非常にうまく行うtsが含まれています。

command | ts '[%Y-%m-%d %H:%M:%S]'

ループも不要になり、出力のすべての行にタイムスタンプが付けられます。

$ echo -e "foo\nbar\nbaz" | ts '[%Y-%m-%d %H:%M:%S]'
[2011-12-13 22:07:03] foo
[2011-12-13 22:07:03] bar
[2011-12-13 22:07:03] baz

再起動したときにサーバーが復旧したことを知りたいですか?ただ走れ ping | ts、問題は解決されました:D。

311
Mark McKinstry

まず、これらのタイムスタンプが実際にイベントを表すことを期待している場合は、多くのプログラムがラインバッファリングを実行するため(一部は他より積極的に)、これを元のラインが持つ時間に近いと考えることが重要です。発生したアクションのタイムスタンプではなく、出力されます。

コマンドに、これを実行するための専用の組み込み機能がないことを確認することもできます。例として、ping -Dは一部のpingバージョンに存在し、UNIXエポックからの経過時間を各行の前に出力します。ただし、コマンドに独自のメソッドが含まれていない場合は、使用できるメソッドとツールがいくつかあります。

POSIXシェル

多くのシェルは文字列をcstringsとして内部に格納するため、入力にnull文字(\0)、行が途中で終了する可能性があります。

command | while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"; done

GNU awk

command | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }'

Perl

command | Perl -pe 'use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

Python

command | python -c 'import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))'

Ruby

command | Ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
112
Chris Down

行ごとのデルタ測定については、 gnomon を試してください。

これは、別のコマンドの標準出力にタイムスタンプ情報を付加するコマンドラインユーティリティで、moreutilsのtsに少し似ています。長時間かかっているプロセスの履歴を記録したい、長期実行プロセスに役立ちます。

何でもgnomonにパイプすると、各行にタイムスタンプが付加され、その行がバッファーの最後の行であった時間、つまり次の行が表示されるまでにかかった時間を示します。デフォルトでは、gnomonは各行間の経過秒数を表示しますが、これは構成可能です。

gnomon demo

47
Janus Troelsen

ライアンの投稿は興味深いアイデアを提供していますが、いくつかの点で失敗しています。 tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1を使用してテストしているときに、stdoutが後で秒単位でずれていてもタイムスタンプが同じであることに気付きました。次の出力を検討してください。

[2016-07-14 01:44:25] Jul 14 01:44:32 eagle dhclient[16091]: DHCPREQUEST of 192.168.0.78 on wlan7 to 255.255.255.255 port 67 (xid=0x411b8c21)
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: Joining mDNS multicast group on interface wlan7.IPv6 with address fe80::d253:49ff:fe3d:53fd.
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: New relevant interface wlan7.IPv6 for mDNS.

私の提案するソリューションは似ていますが、適切なタイムスタンプを提供し、printfではなくechoを使用して移植性を高めています。

| xargs -L 1 bash  -c 'printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" ' bash

なぜbash -c '...' bash-cオプションにより、最初の引数は$0に割り当てられ、出力には表示されません。 -cの適切な説明については、シェルのマニュアルページを参照してください

tail -f /var/log/syslogを使用してこのソリューションをテストし、(おそらくご想像のとおり)切断して私のwifiに再接続すると、dateおよびsyslogメッセージの両方によって提供される適切なタイムスタンプが示されます

Bashはボーンのようなシェルに置き換えることができ、kshまたはdashのいずれかを使用して実行できます。少なくとも-cオプションが指定されているシェルを使用できます。

潜在的な問題:

ソリューションにはxargsが必要です。これはPOSIX準拠システムで利用できるため、ほとんどのUnixライクなシステムをカバーする必要があります。システムがPOSIXに準拠していない場合、またはGNU findutilsがない場合は、明らかに機能しません

7

私は上記のコメントを好んだでしょうが、評判的にはコメントできません。とにかく、上記のPerlサンプルは次のようにバッファリングを解除できます。

command | Perl -pe 'use POSIX strftime; 
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

最初の '$ |' STDOUTのバッファを解除します。 2つ目は、stderrを現在のデフォルト出力チャネルとして設定し、バッファリングを解除します。 selectは$ |の元の設定を返すため、selectをselect内にラップすることにより、$ |もリセットします。デフォルトのSTDOUTに戻します。

そして、はい、そのままカットアンドペーストできます。読みやすいように複数の裏地を付けました。

そして、本当に正確に取得したい場合(そして Time :: Hires がインストールされている場合):

command | Perl -pe 'use POSIX strftime; use Time::HiRes gettimeofday;
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    ($s,$ms)=gettimeofday();
                    $ms=substr(q(000000) . $ms,-6);
                    print strftime "[%Y-%m-%d %H:%M:%S.$ms]", localtime($s)'
6
mpersico

ほとんどの回答はdateの使用を推奨していますが、十分に遅いです。 bashのバージョンが4.2.0以降の場合は、代わりにprintfを使用することをお勧めします。これは、bashの組み込みです。レガシーbashバージョンをサポートする必要がある場合、log関数を作成できます。bashバージョンによって異なります。

_TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S'
# Bash version in numbers like 4003046, where 4 is major version, 003 is minor, 046 is subminor.
printf -v BV '%d%03d%03d' ${BASH_VERSINFO[0]} ${BASH_VERSINFO[1]} ${BASH_VERSINFO[2]}
if ((BV > 4002000)); then
log() {
    ## Fast (builtin) but sec is min sample for most implementations
    printf "%(${TIMESTAMP_FORMAT})T %5d %s\n" '-1' $$ "$*"  # %b convert escapes, %s print as is
}
else
log() {
    ## Slow (subshell, date) but support nanoseconds and legacy bash versions
    echo "$(date +"${TIMESTAMP_FORMAT}") $$ $*"
}
fi
_

速度の違いを見る:

_user@Host:~$time for i in {1..10000}; do printf "%(${TIMESTAMP_FORMAT})T %s\n" '-1' "Some text" >/dev/null; done

real    0m0.410s
user    0m0.272s
sys     0m0.096s
user@Host:~$time for i in {1..10000}; do echo "$(date +"${TIMESTAMP_FORMAT}") Some text" >/dev/null; done

real    0m27.377s
user    0m1.404s
sys     0m5.432s
_

UPD:$(date +"${TIMESTAMP_FORMAT}")の代わりに$(exec date +"${TIMESTAMP_FORMAT}")または$(exec -c date +"${TIMESTAMP_FORMAT}")を使用することをお勧めします。

5
Mikhail

これは、dateおよびxargsを使用して実行できます。

... | xargs -L 1 echo `date +'[%Y-%m-%d %H:%M:%S]'` $1

説明:

xargs -L 1は、入力の1行ごとに次のコマンドを実行するようにxargsに指示し、最初の行をそのまま渡します。 echo `date +'[%Y-%m-%d %H:%M:%S]'` $1基本的に、入力引数の最後に日付をエコーし​​ます

1
Ryan