固定幅、垂直方向のキーと値のペアをCSVファイルに入れる方法は?

固定幅、垂直方向のキーと値のペアをCSVファイルに入れる方法は?

次の種類のコンテンツを含むテキストファイルがあります。

OPERATION_CONTEXT VMD1HTE1A71_ns:.oc.GJ_OAD2 alarm_object 1130 On director: VMD1HTE1A71_ns:.temip.VMD1HTE1A71_director AT Fri 18 Oct 2013 06:56:39 All Attributes

                         Identifier = 1130
                              State = Terminated
                     Problem Status = Closed
              Clearance Report Flag = True
                    Escalated Alarm = False
              Close User Identifier = "Auto-Clear"
        Termination User Identifier = "Auto-Clear"
                   Close Time Stamp = Fri 18 Oct 2013 05:01:46
             Termination Time Stamp = Fri 18 Oct 2013 05:01:46
                 Creation Timestamp = Fri 18 Oct 2013 04:37:29
               Clearance Time Stamp = Fri 18 Oct 2013 05:01:40
        Last Modification Timestamp = Fri 18 Oct 2013 05:01:46
                     Previous State = Outstanding
                     Managed Object = Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD "AMS" Node "INGJJMGRJMTSNB0001AG2OLT001"
                    Target Entities = { Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD "AMS" Node "INGJJMGRJMTSNB0001AG2OLT001" }
                         Alarm Type = CommunicationsAlarm
                         Event Time = Fri 18 Oct 2013 05:01:40
                     Probable Cause = Unknown
                  Specific Problems = {  }
            Notification Identifier = 160315
                             Domain = Domain VMD1HTE1A71_ns:.dm.GJ_OAD2
                       Alarm Origin = IncomingAlarm
                 Perceived Severity = Major
                    Additional Text = "
                                                                  nativeProbableCause: Attempt Threshold Crossed
                                                                  osTime: 20131018163727.250+0530
                                                                  neTime: 20131011174021.0+0530
                                                                  notificationId: AMS:160315
                                                                  portNumber:
                                                                  ftpNumber:
                                                                  meNm: INGJJMGRJMTSNB0001AG2OLT001
                                                                  mdNm: AMS
                                                                  objectType: OT_MANAGED_ELEMENT
                                                                  aliasValue: MGMT Security
                                      Access:INGJJMGRJMTSNB0001AG2OLT001:IP10.70.6.6.T0.S841 "
                  Original Severity = Major
                Original Event Time = Fri 11 Oct 2013 05:40:21

各行の値(識別子、状態、問題状態など)と各列ヘッダーの下の値(例:1130、終了、終了待機)を含む、次のすべての行の列ヘッダーを使用して、このテキストファイルのCSVファイルを作成したいです。 。 "="のある行では、他のものもCSVファイルに抽出したくありません。

ここで発生するもう1つの問題は、一部のフィールドに追加されたテキストなどの改行があることです。 「追加テキスト列」の下の列の「追加テキスト」値をすべて取得したいと思います。

私はLinux / Unixに初めて触れたので、これを行う方法を見つけることができません。これを行う最良の方法は何ですか?

答え1

もちろん、各レコードに常に同じ数のフィールドがあり、レコード間に何もない場合(あなたの投稿に基づいて仮定が正しいかもしれませんし、正確ではないかもしれません)、awkパスに行くことができます。これにより、列の順序と埋め込まれた改行が維持されます。次の場所にあるとしますparse.awk

BEGIN {
    RS       = "( = |\n\\s+)";
    isHeader = 0;
    Sep      = "\",\"";
    Q        = "\"";
    # WinEOL   = "\r"; # enable this if your CSV will be used on Windows
    Headers  = Fields = Q;
}

function sanitise (Entry) {
    gsub(/(^[ "]*|[" \n]*$)/, "", Entry); # Trim leading/trailing double quotes and white space
    gsub(/"/, "\"\"", Entry); # Escape double quotes
    return Entry;
}

function addField (Field) {
    Fields    = Fields FieldsSep sanitise(Field);
    isHeader  = 1;
    FieldsSep = Sep;
    FieldCounter++
}

function addHeader (Header) {
    Headers = Headers HeadersSep sanitise($0);
    isHeader = 0;
    HeadersSep = Sep;
}

1 == NR {                   # Special case of first header
    addHeader($1);
    next;
}

$0 == "\"" {                # Fields with newlines
    LongField    = $0;
    LongFieldSep = "";
    while (getline > 0) {
        LongField    = LongField LongFieldSep $0;
        LongFieldSep = "\n";
        if ($NF ~ /"$/) {
            addField(LongField);
            next;
        }
    }
}
{
    if (isHeader) {
        addHeader($0);
    }
    else {
        addField($0);
    }

    if (FieldsPerRecord == FieldCounter) {
        if (!HeadersPrinted) {
            print Headers Q WinEOL;
            HeadersPrinted = 1
        }
        print Fields Q WinEOL;
        Fields = FieldsSep = "";
        FieldCounter = 0
    }
}

FieldsPerRecordその後、コマンドラインからsetを使用して呼び出すことができます。

$ awk -v FieldsPerRecord=26 -f parse.awk data.csv

これは、LibreOffice Calcが問題なく受け入れるように見える次のCSVエンコーディングデータを生成します。

"Identifier","State","Problem Status","Clearance Report Flag","Escalated Alarm","Close User Identifier","Termination User Identifier","Close Time Stamp","Termination Time Stamp","Creation Timestamp","Clearance Time Stamp","Last Modification Timestamp","Previous State","Managed Object","Target Entities","Alarm Type","Event Time","Probable Cause","Specific Problems","Notification Identifier","Domain","Alarm Origin","Perceived Severity","Additional Text","Original Severity","Original Event Time"
"1130","Terminated","Closed","True","False","Auto-Clear","Auto-Clear","Fri 18 Oct 2013 05:01:46","Fri 18 Oct 2013 05:01:46","Fri 18 Oct 2013 04:37:29","Fri 18 Oct 2013 05:01:40","Fri 18 Oct 2013 05:01:46","Outstanding","Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD ""AMS"" Node ""INGJJMGRJMTSNB0001AG2OLT001","{ Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD ""AMS"" Node ""INGJJMGRJMTSNB0001AG2OLT001"" }","CommunicationsAlarm","Fri 18 Oct 2013 05:01:40","Unknown","{  }","160315","Domain VMD1HTE1A71_ns:.dm.GJ_OAD2","IncomingAlarm","Major","nativeProbableCause: Attempt Threshold Crossed
osTime: 20131018163727.250+0530
neTime: 20131011174021.0+0530
notificationId: AMS:160315
portNumber:
ftpNumber:
meNm: INGJJMGRJMTSNB0001AG2OLT001
mdNm: AMS
objectType: OT_MANAGED_ELEMENT
aliasValue: MGMT Security
Access:INGJJMGRJMTSNB0001AG2OLT001:IP10.70.6.6.T0.S841","Major","Fri 11 Oct 2013 05:40:21"

持ってきたので参考にしてくださいすべてを引用このアプローチは、少なくとも私にはインポート時に驚くほど少ないですが、 でQ = ""2Sep = ","行を設定することでこの機能を無効にできます。gsub()sanitise()

しかし、、私いいえこれが正規表現の問題だと思います。データは固定幅なので、次のようになります。真珠unpackおそらく最良の方法でしょう。私はこれを見つけることができませんでしたが、誰かがこれを行う方法を見せたいことを確認するための良い機会になることができますunpack

修正する

私はPerl Hacker™ではありませんが、次はうまくいくようです。複数行フィールドの内容を想定せずに、フィールドの順序とフィールド内のすべての元の間隔を維持します(ただし、ヘッダーの先行スペースは削除します)。 Perl-Free非専門家の目には美しく見えます。

BEGIN{
    our (@headers, @fields);
    our $headers_printed = 0;
}
my ($header, $field) = unpack("A36x2A*", $_); # magic!

if ("" eq $header) {            # Fields with newlines
    $fields[$#fields] .= "\n" . $field;
    next;
}

push(@headers, $header =~ s/^\s*//gr);
push(@fields, $field);

if (26 == $#headers + 1) {      # Print complete record
    printf "%s\n", join ",", @headers  unless $headers_printed;
    $headers_printed = 1;
    printf "%s\n", join ",", @fields;
    @fields = @headers = ();
}

ただ電話してください:

$ perl -nf /tmp/parse.pl /tmp/data.txt
Identifier,State,Problem Status,Clearance Report Flag,Escalated Alarm,Close User Identifier,Termination User Identifier,Close Time Stamp,Termination Time Stamp,Creation Timestamp,Clearance Time Stamp,Last Modification Timestamp,Previous State,Managed Object,Target Entities,Alarm Type,Event Time,Probable Cause,Specific Problems,Notification Identifier,Domain,Alarm Origin,Perceived Severity,Additional Text,Original Severity,Original Event Time
1130,Terminated,Closed,True,False,"Auto-Clear","Auto-Clear",Fri 18 Oct 2013 05:01:46,Fri 18 Oct 2013 05:01:46,Fri 18 Oct 2013 04:37:29,Fri 18 Oct 2013 05:01:40,Fri 18 Oct 2013 05:01:46,Outstanding,Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD "AMS" Node "INGJJMGRJMTSNB0001AG2OLT001",{ Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD "AMS" Node "INGJJMGRJMTSNB0001AG2OLT001" },CommunicationsAlarm,Fri 18 Oct 2013 05:01:40,Unknown,{  },160315,Domain VMD1HTE1A71_ns:.dm.GJ_OAD2,IncomingAlarm,Major,"
                            nativeProbableCause: Attempt Threshold Crossed
                            osTime: 20131018163727.250+0530
                            neTime: 20131011174021.0+0530
                            notificationId: AMS:160315
                            portNumber:
                            ftpNumber:
                            meNm: INGJJMGRJMTSNB0001AG2OLT001
                            mdNm: AMS
                            objectType: OT_MANAGED_ELEMENT
                            aliasValue: MGMT Security
Access:INGJJMGRJMTSNB0001AG2OLT001:IP10.70.6.6.T0.S841 ",Major,Fri 11 Oct 2013 05:40:21

を使用する方が良いかもしれませんが、そのText::CSV仕組みを理解することに興味がありますunpack。固定幅データの正規表現よりも読みやすく強力に見えます。

答え2

または、Perlの正規表現サブルーチンを使用できます。

my $grammar = qr!
    ( ?(DEFINE)
       (?<Identifier> [^=\n]+ )
       (?<Statement>
           (?: # Begin alternation
               " #Opening quotes
               [^"]+? # Any non-quotes (including a new line)
               " # Closing quotes
              | [^\n]+ # Or a single line
           )   # End alternation
        )   

   )

!x;

my $file = do { local $/; <> }; #Slurp file named on command line
my %columns;
while( $file =~ 
   m{ ((?&Identifier))[\t ]*=[ \t]*((?&Statement)) $grammar}xgc )
{ 
   my ($header,$value) = ($1,$2);

       # Remove leading spaces and quote variable if it contains commas:
   for($header,$value) { s/^\s+//mg; /,/ and s/^|$/"/g }

       # Substitute \n with \\n to make multi-line values single-line:
   for($value) { chomp; s/\n/\\n/g }

   $columns{$header}=$value
}

print join "," => sort keys %columns; # Print column headers
print "\n";
print join "," => map { $columns{$_} } sort keys %columns; # Column content
print "\n";

次のように呼び出します。

[user@host]$ /path/to/script.pl /path/to/file.txt

テーブルをCSV形式で標準出力として印刷します。

これは、複数行ステートメント"に始まりと終わりを除いて二重引用符()が含まれていないと仮定します。

答え3

さて、あまり良くありませんが、好きなようにしてください。私は上記のファイルをインポートして解析し、Text::CSVこのモジュールを使用してCSV形式に変換するスクリプトをPerlで書いた。

スクリプト

#!/usr/bin/env perl

use Text::CSV;

open(my $fh, "<data.txt");
@lines = <$fh>;
close ($fh);

my (%csv, $name, $val);

foreach my $line (@lines) {
  if ($line =~ m/=/) {
    chomp($line);
        $line =~ s/^\s+//g;
    ($name, $val) = split(/ = /, $line);
        $val =~ s/^"$//;
        $csv{$name} = $val;
  } else {
        $line =~ s/^\s+//g;
        $line =~ s/\s+$/\\n/g;
        $line =~ s/ "\\n$//;
        $csv{$name} .= $line;
  }
}

my @vals;
foreach my $i (sort keys %csv) {
  push(@vals, $csv{$i});
}

my $ccsv = Text::CSV->new();
$ccsv->combine(sort keys %csv);
$ccsv->parse($ccsv->string());
print $ccsv->string() . "\n";
$ccsv->combine(@vals);
$ccsv->parse($ccsv->string());
print $ccsv->string() . "\n";

はい

次のように実行してみてください。

$ ./csv.pl
"Additional Text","Alarm Origin","Alarm Type","Clearance Time Stamp","Close Time Stamp","Creation Timestamp",Domain,"Event Time","Last Modification Timestamp","Managed Object","Notification Identifier","Original Event Time","Original Severity","Perceived Severity","Previous State","Probable Cause","Specific Problems","Target Entities","Termination Time Stamp"
"nativeProbableCause: Attempt Threshold Crossed\nosTime: 20131018163727.250+0530\nneTime: 20131011174021.0+0530\nnotificationId: AMS:160315\nportNumber:\nftpNumber:\nmeNm: INGJJMGRJMTSNB0001AG2OLT001\nmdNm: AMS\nobjectType: OT_MANAGED_ELEMENT\naliasValue: MGMT Security\nAccess:INGJJMGRJMTSNB0001AG2OLT001:IP10.70.6.6.T0.S841",IncomingAlarm,CommunicationsAlarm,"Fri 18 Oct 2013 05:01:40","Fri 18 Oct 2013 05:01:46","Fri 18 Oct 2013 04:37:29","Domain VMD1HTE1A71_ns:.dm.GJ_OAD2","Fri 18 Oct 2013 05:01:40","Fri 18 Oct 2013 05:01:46","Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD ""AMS"" Node ""INGJJMGRJMTSNB0001AG2OLT001""",160315,"Fri 11 Oct 2013 05:40:21",Major,Major,Outstanding,Unknown,"{  }","{ Alcatel_5529OAD VMD1HTE1A71_ns:.OAD2 MD ""AMS"" Node ""INGJJMGRJMTSNB0001AG2OLT001"" }","Fri 18 Oct 2013 05:01:46"

あなたのコメントや実行に問題がある場合はお知らせください。お客様の要件を満たしている場合は、どのように動作するかについての詳細を入力します。

引用する

関連情報