
У меня есть текстовый файл со следующим содержимым:
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
Я хочу создать CSV-файл из этого текстового файла со значениями заголовков столбцов перед знаком "=" в каждой строке, например, Идентификатор, Состояние, Статус проблемы, а все последующие строки содержат значения под каждым заголовком столбца, например, 1130, Завершено, Закрыто и т. д. Кроме строк с "=" я не хочу, чтобы что-либо еще извлекалось в CSV-файл.
Другая сложность, с которой я столкнулся, заключается в том, что некоторые поля имеют символ новой строки, например, Дополнительный текст. Я хочу получить все значения для Дополнительного текста в одном столбце под Дополнительным текстовым столбцом.
Так как я новичок в 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
В результате будут получены следующие данные в формате CSV, которые LibreOffice Calc, по-видимому, принимает без проблем:
"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 = ""
, Sep = ","
и две gsub()
строки в sanitise()
.
однако, янедумаю, что это проблема регулярных выражений. Эти данные имеют фиксированную ширину, поэтому кажется, чтоПерлсunpack
вероятно, лучший подход. Я никогда не мог в этом разобраться, но это может быть хорошей возможностью для меня узнать, если кто-то захочет показать способ сделать это с помощью unpack
.
обновлять
Я не Perl Hacker™, но следующий код, похоже, работает хорошо, не делает никаких предположений о содержимом многострочных полей, сохраняет порядок полей и все исходные интервалы внутри полей (но удаляет начальные пробелы из заголовков) и на мой неопытный взгляд выглядит в стиле Perl:
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
Ладно, это не очень красиво, но делает то, что вы просите. Я написал скрипт на Perl, который возьмет указанный выше файл и проанализирует его, а затем использует модуль Text::CSV
для преобразования его в формат CSV.
Сценарий
#!/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"
Дайте мне знать, что вы думаете, или если у вас возникнут проблемы с запуском. Если он делает то, что вам нужно, я опишу детали того, как он работает.