AWK:引用符で囲まれた複数の単語を含む列を正しく表示するには?

AWK:引用符で囲まれた複数の単語を含む列を正しく表示するには?

私は次のようにawkを使用します。

grep -i 'logged in' path-to-file | tail -n -10 | awk '{ print $6, "logged in on ",substr($2,1,8),$1"."; }' | sed 's/"//g'

ただし、列はユーザ​​ーによって生成されるため、時には単語列$6であり、"nickname"複数の単語で構成されることもあります。

2017-12-21 21:54:01.714540 ユーザー #41 ニックネーム: "sarah the Princes" ユーザー名: "guest" IP アドレス: 111111111, UDP アドレス: udp ログインしました。

サラ王子のニックネーム全体を印刷するのではなく、サラという最初の単語だけを表示します。

答え1

awkのgsub()機能を使用して、"and "(引用符の後にスペースがあり、スペースの後に引用符が続く)を任意の区切り文字に置き換え、FSを対応する区切り文字に設定し、必要なものを抽出できます。 FS を変更すると、フィールドの数も変更されます。また、次の入力行を正しく処理するには、FSを元の値にリセットする必要があります。

あなたの場合は、フィールドからいくつかのデータ(日付と時刻)を抽出したいと思います。今後FSが変わりました。

たとえば、./file5つの行がある場合、各行は提供されたサンプル行の正確なコピーです。

$ grep -i 'logged in' ./file | tail | awk '
{ d=$1;
  t=$2; sub(/\..*/,"",t);

  FS="XXX";
  gsub(/" | "/,"XXX",$0);
  print $2,"logged in at", t, d;
  FS="[[:space:]]+"
}'
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21

使ったXXX入力のどこにも表示されないため、フィールド区切り文字として使用されます。この例では、タブはうまく機能しますが、フィールド区切り文字が単一の文字である必要はないことを証明しません。タブがある単一文字を識別できない、または簡単に識別できない場合は重要です。入力のどこでも使用されません。

フィールドデータを抽出する必要がある場合、状況はより複雑になります。後ろに二重引用符で囲まれたフィールド(IPアドレスまたはUDPポートフィールドなど) - フィールドgsub番号が何であるかがわからないため、以前は抽出できません。私はperlこの時点で@Wildcardを使用したいのですが(またはsed@Wildcardの答えのように)、関数呼び出しの正規表現を適切にawk拡張することが1つのアプローチです。gsubたとえば、スクリプトを次のように置き換えますawk

$ grep -i 'logged in' ./file | tail | awk '
{   d=$1;
    t=$2;
    sub(/\..*/,"",t);

    FS="XXX";
    gsub(/" | "|address: |, /,"XXX",$0);
    sub(/ .*/,"",$8);      # get rid of trailing junk after udp port

    print $2,"logged in at", t, d, "as" ,$4, "from", $6":"$8;

    FS="[[:space:]]+"
}'

次の出力が生成されます。

sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp

perl完璧にするためにPerlコアモジュールを使用する1つの方法は次のとおりですText::ParseWords

#!/usr/bin/perl

use strict;
use Text::ParseWords;

my $keep=1;  # keep " chars in output.  set to 0 to strip them.

while(<>) {
  my @F = quotewords('\s+', $keep, $_);

  $F[1] =~ s/\..*//;  # strip decimal fraction from time field
  $F[10] =~ s/,//;    # strip trailing comma from IP address field

  # remember: perl array indices start at zero, not one.
  printf "%s logged in at %s %s as %s from %s:%s\n", @F[5,1,0,7,10,13];
}

quotewords()from関数を使用してText::Parsewords各入力行をフィールド(という配列に格納されている)に分割し、いくつかのフィールドを少しクリーンアップし、次を使用して必要なフィールド@Fを印刷します。printf

一行で言うと次のようになります。

grep -i 'logged in' ./file | tail | perl -MText::ParseWords -n -e '
  @F = quotewords(q/\s+/, 1, $_);
  $F[1] =~ s/\..*//;
  $F[10] =~ s/,//;
  printf "%s logged in at %s %s as %s from %s:%s\n", @F[5,1,0,7,10,13]'

私が変更した方法に注意してください'/s+'- q/\s+/Perlには素晴らしい機能があります参照演算子これを防ぐために使用できます。一重引用符内の一重引用符に関する問題

答え2

サイズに合わせて着てみてください:

sed -En '
  /^(....-..-..) (..:..:..)[^:]*nickname: "?([^":]+)"? username:.*logged in.*$/ {
    s//\3 logged in at \2 on \1./p
  }
' path-to-file | tail -n 10

関連情報