Как преобразовать парный список ключевых значений в таблицу со столбцами с помощью AWK?

Мне нужно преобразовать набор данных из парного списка значений ключа (выходные данные Informix dbaccess) в CSV-файл с столбцами. Я вполне уверен, что это можно легко сделать с помощью awk или sed.

ОБНОВЛЕНИЕ Решение должно быть однострочным ответом. Я использую NSH (который основан на ZSH). Так что некоторые из типичных «баши»-команд работать не будут.

Вот мой набор образцов данных:

part_no            100000001
date_part          2010-10-13 12:12:12
history_code       ABCD
user_id            rsmith
other_information   note: Monday, December 10
pool_no            101011777

part_no            100000002
date_part          2010-10-21 12:12:12
history_code       GHIJ
user_id            jsmith
other_information
pool_no            101011888

part_no            100000002
date_part          2010-10-27 12:12:12
history_code       LMNO
user_id            fevers
other_information   [Mail]
pool_no            101011999

part_no            100000003
date_part          2010-11-13 12:12:12
history_code       QXRT
user_id            sjohnson
other_information   note: Tuesday, August 31
pool_no            101011111

Мне нужно, чтобы это выглядело так:

part_no,date_part,history_code,user_id,other_information,pool_no
100000001,10/13/2010 12:12:12,ABCD,rsmith,note: Monday, December 10,101011777
100000002,10/21/2010 12:12:12,GHIJ,jsmith,,101011888
100000002,10/27/2010 12:12:12,LMNO,fevers,[Mail],101011999
100000003,11/13/2010 12:12:12,QXRT,sjohnson,note: Tuesday, August 31,101011111

person 27560    schedule 29.06.2018    source источник
comment
Добро пожаловать в СО. Stack Overflow — это сайт вопросов и ответов для профессиональных программистов и программистов-энтузиастов. Цель состоит в том, чтобы вы добавили свой собственный код к своему вопросу, чтобы показать, по крайней мере, исследовательские усилия, которые вы предприняли, чтобы решить эту проблему самостоятельно.   -  person Cyrus    schedule 29.06.2018
comment
Должен ли формат даты измениться с 2010-10-13 на 10/13/2010? Кроме того, внутри полей есть запятые, но в предлагаемом выводе не используется соглашение о двойных кавычках полей, и поэтому он неоднозначен.   -  person Benjamin W.    schedule 29.06.2018
comment
Формат данных не должен меняться, а вывод может использовать поля с двойными кавычками (в данных будут присутствовать запятые).   -  person 27560    schedule 29.06.2018
comment
Обновите свой вопрос, чтобы показать ожидаемый результат, а также то, что вы пробовали до сих пор, и сделайте это быстро, прежде чем ваш вопрос будет закрыт как неясный и/или вы получите еще какие-то сумасшедшие ответы с 20 командами sed, коты, порезы, трубы и символ бэтмена.   -  person Ed Morton    schedule 29.06.2018
comment
Можем ли мы полагаться на то, что все ключи находятся в одном и том же порядке для каждой записи?   -  person glenn jackman    schedule 29.06.2018
comment
@EdMorton Я пробую каждое из предложений по мере их поступления. Мне нужно, чтобы команда была однострочным решением (а не несколькими строками). Я обновлю свой вопрос соответственно. Кроме того, к вашему сведению - это мой первый пост о переполнении стека.   -  person 27560    schedule 29.06.2018
comment
@glennjackman Я могу отсортировать вывод из informix dbaccess, так что да, я думаю   -  person 27560    schedule 29.06.2018
comment
Обратите внимание, что символы новой строки допустимы внутри строки в кавычках, так что однострочная awk-команда может по-прежнему содержать символы новой строки в кавычках. Я ничего не знаю о NSH, но если бы он этого не допускал, я был бы «шокирован»/разочарован.   -  person glenn jackman    schedule 29.06.2018
comment
@glennjackman Можете ли вы отформатировать вывод в одну строку? Я не понимаю, что вы имеете в виду   -  person 27560    schedule 29.06.2018
comment
Я имею в виду, что первый фрагмент кода RavinderSingh13 представляет собой awk-однострочный код. Предотвращают ли инструменты, которые вы должны использовать, такую ​​(читабельную) команду?   -  person glenn jackman    schedule 29.06.2018
comment
Вы создаете «однострочные» решения, создавая сценарий оболочки, который выполняет эту работу, и запуская сценарий оболочки из вашей «однолинейной» системы. При необходимости используйте абсолютный путь. В противном случае вы сражаетесь с одной рукой, связанной за спиной (на самом деле обе руки связаны за спиной).   -  person Jonathan Leffler    schedule 29.06.2018


Ответы (5)


Ваш вопрос не ясен, но это МОЖЕТ быть тем, что вы ищете:

$ cat tst.awk
BEGIN { RS=""; FS="\n"; OFS=","; ofmt="\"%s\"%s" }
{
   for (i=1; i<=NF; i++) {
       tag = val = $i
       sub(/[[:space:]].*/,"",tag)
       sub(/[^[:space:]]+[[:space:]]+/,"",val)
       tags[i] = tag
       vals[i] = val
    }
}
NR==1 {
    for (i=1; i<=NF; i++) {
        printf ofmt, tags[i], (i<NF ? OFS : ORS)
    }
}
{
    for (i=1; i<=NF; i++) {
        printf ofmt, vals[i], (i<NF ? OFS : ORS)
    }
}

$ awk -f tst.awk file
"part_no","date_part","history_code","user_id","other_information","pool_no"
"100000001","2010-10-13 12:12:12","ABCD","rsmith","note: Monday, December 10","101011777"
"100000002","2010-10-21 12:12:12","GHIJ","jsmith","other_information","101011888"
"100000002","2010-10-27 12:12:12","LMNO","fevers","[Mail]","101011999"
"100000003","2010-11-13 12:12:12","QXRT","sjohnson","note: Tuesday, August 31","101011111"
person Ed Morton    schedule 29.06.2018
comment
Можно ли запустить ваш оператор awk из канала (в одной строке)? Я использую это как часть расширенного объекта в BMC Server Automation. - person 27560; 29.06.2018
comment
Конечно. Просто замените каждую новую строку на ; и назовите ее как whatever | awk 'script'. Я понятия не имею, что такое an "extended object" in BMC Server Automation, конечно. - person Ed Morton; 29.06.2018
comment
Это сработало прекрасно! Благодарю вас! cat dbaccessoutput | awk 'BEGIN { RS=""; FS="\n"; OFS=","; ofmt="\"%s\"%s" }; {; for (i=1; i<=NF; i++) {; tag = val = $i; sub(/[[:space:]].*/,"",tag); sub(/[^[:space:]]+[[:space:]]+/,"",val); tags[i] = tag; vals[i] = val; }; }; NR==1 {; for (i=1; i<=NF; i++) {; printf ofmt, tags[i], (i<NF ? OFS : ORS); }; }; {; for (i=1; i<=NF; i++) {; printf ofmt, vals[i], (i<NF ? OFS : ORS); }; }' - person 27560; 03.07.2018
comment
Пожалуйста. Однако вам не нужно передавать файл в awk, awk вполне способен открыть файл сам по себе. Google UUOC, чтобы понять проблему и использовать awk 'script' file вместо cat file | awk 'script'. - person Ed Morton; 03.07.2018
comment
Я просто собирал файл для тестирования, в EO (расширенный объект) я выполняю свою команду dbaccess, делаю несколько greps, а затем подключаюсь к оператору awk - person 27560; 03.07.2018

Я решаю это как вопрос Informix, а не как вопрос Awk.

Используя стандартные команды Informix SQL, вы можете создать внешняя таблица в формате CSV, но вы должны знать, что существует недокументированный формат "DB2", который вы можете использовать:

DROP TABLE IF EXISTS data_table;

CREATE TABLE data_table
(
        part_no            INTEGER,
        date_part          DATETIME YEAR TO SECOND,
        history_code       VARCHAR(4),
        user_id            VARCHAR(32),
        other_information  VARCHAR(64),
        pool_no            INTEGER
);

INSERT INTO data_table VALUES(100000001, "2010-10-13 12:12:12", "ABCD", "rsmith", "note: Monday, December 10", 101011777);
INSERT INTO data_table VALUES(100000002, "2010-10-21 12:12:12", "GHIJ", "jsmith", NULL, 101011888);
INSERT INTO data_table VALUES(100000002, "2010-10-27 12:12:12", "LMNO", "fevers", "[Mail]", 101011999);
INSERT INTO data_table VALUES(100000003, "2010-11-13 12:12:12", "QXRT", "sjohnson", "note: Tuesday, August 31", 101011111);

DROP TABLE IF EXISTS csv_data;
CREATE EXTERNAL TABLE csv_data
(
    part_no            INTEGER,
    date_part          DATETIME YEAR TO SECOND,
    history_code       VARCHAR(4),
    user_id            VARCHAR(32),
    other_information  VARCHAR(64),
    pool_no            INTEGER
)
USING (FORMAT "DB2", DELIMITER ",", DATAFILES("DISK:/tmp/data/csv_data.csv"));

INSERT INTO csv_data
        SELECT part_no, date_part, history_code, user_id, other_information, pool_no
          FROM data_table;

Содержимое /tmp/data/csv_data.csv тогда выглядит так:

100000001,2010-10-13 12:12:12,"ABCD","rsmith","note: Monday, December 10",101011777
100000002,2010-10-21 12:12:12,"GHIJ","jsmith",,101011888
100000002,2010-10-27 12:12:12,"LMNO","fevers","[Mail]",101011999
100000003,2010-11-13 12:12:12,"QXRT","sjohnson","note: Tuesday, August 31",101011111

Формат UNLOAD преобразован в CSV

Выходные данные DB-Access по умолчанию не поддаются разбору на практике. Это может быть выполнимо в некоторых ограниченных случаях, таких как тот, который вы показываете, но вам лучше использовать формат UNLOAD вместо вывода командной строки, а затем преобразовать формат данных UNLOAD в CSV.

У меня есть Perl-скрипт, который это делает. Он использует модуль Perl Text::CSV для обработки форматирования CSV. Он не претендует на то, чтобы обрабатывать первую строку с именами столбцов; их нет в файле формата UNLOAD.

#!/usr/bin/env perl
#
# @(#)$Id: unl2csv.pl,v 1.3 2018/06/29 20:36:58 jleffler Exp $
#
# Convert Informix UNLOAD format to CSV

use strict;
use warnings;
use Text::CSV;
use IO::Wrap;

my $csv = new Text::CSV({ binary => 1 }) or die "Failed to create CSV handle ($!)";
my $dlm = defined $ENV{DBDELIMITER} ? $ENV{DBDELIMITER} : "|";
my $out = wraphandle(\*STDOUT);
my $rgx = qr/((?:[^$dlm]|(?:\\.))*)$dlm/sm;

# $csv->eol("\r\n");

while (my $line = <>)
{
    print "1: $line";
    MultiLine:
    while ($line eq "\\\n" || $line =~ m/[^\\](?:\\\\)*\\$/)
    {
        my $extra = <>;
        last MultiLine unless defined $extra;
        $line .= $extra;
    }
    my @fields = split_unload($line);
    $csv->print($out, \@fields);
}

sub split_unload
{
    my($line) = @_;
    my @fields;
    print "$line";

    while ($line =~ $rgx)
    {
        printf "%d: %s\n", scalar(@fields), $1;
        push @fields, $1;
    }
    return @fields;
}

__END__

=head1 NAME

unl2csv - Convert Informix UNLOAD to CSV format

=head1 SYNOPSIS

unl2csv [file ...]

=head1 DESCRIPTION

The unl2csv program converts a file from Informix UNLOAD file format to
the corresponding CSV (comma separated values) format.

The input delimiter is determined by the environment variable
DBDELIMITER, and defaults to the pipe symbol "|".
It is not assumed that each input line is terminated with a delimiter
(there are two variants of the UNLOAD format, one with and one without
the final delimiter).

=head1 EXAMPLES

Input:

  10|12|excessive|cost \|of, living|
  20|40|bou\\ncing tigger|grrrrrrrr|

Output:

  10,12,"excessive","cost |of, living"
  20,40,"bou\ncing tigger",grrrrrrrr

=head1 PRE-REQUISITES

Text::CSV_XS

=head1 AUTHOR

Jonathan Leffler <[email protected]>

=cut

Вы должны использовать такую ​​​​команду (через DB-Access):

UNLOAD TO "datatable.unl" SELECT * FROM DataTable;

а затем запустите:

perl unl2csv datatable.unl > datatable.csv

программа SQLCMD

Если у вас есть моя программа SQLCMD (доступна на веб-сайте IIUG в репозитории программного обеспечения — и совершенно не связанный с johnny-come-lately от Microsoft с тем же именем), то вы можете выгрузить прямо в формат CSV:

sqlcmd -d database -F csv -e 'unload to "data_table.csv" select * from data_table'
person Jonathan Leffler    schedule 29.06.2018

Попробуй это:

cat $file | cut -d ' ' -f 2- | sed 's/^[ \t]*//' | sed 's/$/,/' \
| xargs  | sed 's/ , /\n/g' | sed 's/.$//' | sed 's/, /,/g' \
| sed '1ipart_no,date_part,history_code,user_id,other_information,pool_no'
person Bernat Pedrol Vozmediano    schedule 29.06.2018
comment
Я должен был упомянуть, что использую NSH (сетевую оболочку), основанную на ZSH. Вот результат, который я получил: sed: : Нет такого файла или каталога nsh: команда не найдена: xargs sed: : Нет такого файла или каталога sed: 1: 1ipart_no,date_part,his ...: команда, которую я ожидаю \, за которой следует текст - person 27560; 29.06.2018
comment
Вы должны были, я попробовал это с Ubuntu, и это сработало. Извини чувак. $file — это имя файла, который вы хотите преобразовать. - person Bernat Pedrol Vozmediano; 29.06.2018
comment
Я изменил $file на имя моего файла, и это был результат, который я получил - person 27560; 29.06.2018

Я знаю, что ОП сказал awk, но Баш просто сидел там.

#
# line to be printed
line=""

#
# first value on a line flag
first=""

#
# read the file
while read key val; do
    #
    # if key is empty then the input line is empty.
    if [ "$key" = "" ] ; then
        #
        # skip leading blank lines in the file
        if [ "$line" = "" ] ; then
            continue
        else
            #
            # print and reset the line
            echo $line
            line=""
            first=""
        fi
    else
        #
        # place the first comma after the first value
        if [ "$first" = "" ] ; then
            line="\"$val\""
            first="1"
        else
            line="$line,\"$val\""
        fi
    fi
done < file.txt

#
# print the last line, if there is one
if [ "$line" != "" ] ; then
    echo $line
fi
person 7 Reeds    schedule 29.06.2018
comment
Голосовать против этого было бы жестоко; но на самом деле, не делайте этого. - person tripleee; 29.06.2018
comment
Согласовано. См. unix.stackexchange.com/q/169716/133219 всего несколько причин, но есть и другие. . - person Ed Morton; 30.06.2018

Не могли бы вы попробовать следовать и дайте мне знать, если это поможет вам.

awk -v s1="," '/part_no/ && value{if(header){print header;flag=1;header=""};print value;value=""}  NF{if(!flag){header=(header?header s1 "":"")$1};sub(/^[^[:space:]]+[[:space:]]+/,"");value=value?value s1 $0:$0} END{if(value){print value}}'  Input_file

Вывод будет следующим.

part_no,date_part,history_code,user_id,other_information,pool_no
100000001,2010-10-13 12:12:12,ABCD,rsmith,note: Monday, December 10,101011777
100000002,2010-10-21 12:12:12,GHIJ,jsmith,,101011888
100000002,2010-10-27 12:12:12,LMNO,fevers,[Mail],101011999
100000003,2010-11-13 12:12:12,QXRT,sjohnson,note: Tuesday, August 31,101011111

Теперь также добавлена ​​форма решения, не состоящая из одного вкладыша.

awk -v s1="," '
/part_no/ && value{
  if(header){
    print header;
    flag=1;
    header=""}
  print value;
  value=""
}
NF{
  if(!flag){
    header=(header?header s1 "":"")$1}
  sub(/^[^[:space:]]+[[:space:]]+/,"")
  value=value?value s1 $0:$0
}
END{
  if(value){
    print value}
}'   Input_file
person RavinderSingh13    schedule 29.06.2018
comment
Может ли это стать однострочным оператором? - person 27560; 29.06.2018
comment
@MattRagland, пожалуйста, проверьте мой РЕДАКТИРОВАТЬ и просмотрите эту ссылку один раз stackoverflow.com/help/someone-answers - person RavinderSingh13; 29.06.2018
comment
Вот что я получил: part_no,date_part,history_code,user_id,other_information,pool_no ,101011777day,,December,10 ,,101011888,12:12:12 ,1010119997,12:12:12 ,101011111sday,,August,31 - person 27560; 29.06.2018
comment
@MattRagland, теперь я получаю правильный вывод, добавил его в свой пост, можете ли вы один раз проверить, есть ли в вашем файле возврат каретки, выполнив cat -v file, если да, то удалите их, выполнив awk '{gsub(/\r$/,"")} 'Input_file`, и дайте мне знать тогда ?? - person RavinderSingh13; 29.06.2018
comment
Ах, хороший улов, да, у меня был возврат каретки, я преобразовал его в формат unix, и это помогло, однако я получаю лишние запятые там, где их быть не должно. Видите другой информационный столбец? - person 27560; 29.06.2018
comment
@MattRagland, какие столбцы или команды? Использовать dos2unix еще? и посмотрите эту ссылку один раз stackoverflow.com/help/someone-answers - person RavinderSingh13; 29.06.2018
comment
Я отметил ваш ответ положительным, однако в нем говорилось, что, поскольку моя репутация меньше 15 (поскольку это мой 1-й пост), он не будет отображаться во всем мире. Даты в столбце other_information содержат лишние запятые, которые необходимо очистить. - person 27560; 29.06.2018
comment
@MattRagland, если значение равно NULL, оно печатается как ,,, то есть только по необходимости, верно? - person RavinderSingh13; 29.06.2018
comment
Это не проблема, в значении даты есть пробелы, посмотрите на столбец other_information, значение в нем разбивается на 3 разных столбца Понедельник, 10 декабря. Должно быть понедельник, 10 декабря. Также вы можете добавить кавычки в столбцы, так что запятые являются допустимыми значениями? - person 27560; 29.06.2018
comment
Найдите секунду, чтобы подумать о своем троичном выражении, и вы поймете, почему оно может потерпеть неудачу. Кроме того, присвоение $1 преобразует все остальные пробелы во входных данных в запятые. Наконец, явно плохой практикой является жесткое кодирование строки заголовка и, таким образом, тесная привязка вашего сценария к конкретным значениям и порядку их появления во входных данных, когда значения уже присутствуют во входных данных в том порядке, в котором они должны быть напечатаны. - person Ed Morton; 29.06.2018
comment
@EdMorton, конечно, Эд, я извиняюсь, согласно вашему предложению, я удалил часть жесткого кодирования заголовков, но не уверен, как написать регулярное выражение о $1, чтобы я мог удалить его аннулирование, не могли бы вы помочь мне здесь, буду благодарен вы, сэр. - person RavinderSingh13; 29.06.2018
comment
Ваши тернарии вместо var = var ? var s1 $X: s1 $X должны быть var = (var == "" ? "" : var s1) $X, чтобы избежать дублирования кода, не вызывать синтаксических ошибок в некоторых awks, и чтобы они не терпели неудачу, когда первое значение var численно оценивается как 0. относительно присваивания $1 - я действительно не уверен что вы пытаетесь сделать, но я ДУМАЮ, что это может быть просто избавление от значения заголовка из каждой строки, и это будет просто sub(/^[^[:space:]]+[[:space:]]+/,"") вместо $1=""; gsub(/^ +|^,/,""); - person Ed Morton; 29.06.2018
comment
@EdMorton, уверен, что Эд сделал это, и, как обычно, ОГРОМНОЕ СПАСИБО за руководство, очень благодарен вам, счастливых выходных. - person RavinderSingh13; 30.06.2018