【问题标题】:How do I convert key value paired list into table with columns using AWK?如何使用 AWK 将键值对列表转换为包含列的表?
【发布时间】:2018-06-29 15:09:41
【问题描述】:

我需要将数据集从键值配对列表(informix dbaccess 输出)转换为列式 csv。我相当肯定这可以通过 awk 或 sed 轻松完成。

UPDATE 解决方案需要是单行响应。我正在使用 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

【问题讨论】:

  • 欢迎来到 SO。 Stack Overflow 是一个面向专业和狂热程序员的问答网站。目标是您将一些自己的代码添加到您的问题中,以至少显示您为解决这个问题所做的研究工作。
  • 日期格式是否必须从2010-10-13 更改为10/13/2010?此外,字段中有逗号,但建议的输出不使用约定来双引号字段,因此是模棱两可的。
  • 数据格式不用改变,输出可以使用双引号字段(数据中会有逗号)
  • 更新您的问题以显示 预期输出以及您迄今为止尝试过的内容,并在您的问题因不清楚和/或您变得更加疯狂之前快速完成用 20 个 sed 命令、猫、切口、管道和蝙蝠侠符号回答。
  • 您可以创建“单行”解决方案,方法是创建一个执行该工作的 shell 脚本,然后从“单行”系统运行该 shell 脚本。如有必要,请使用绝对路径名。否则,你就是一只手被绑在背后(实际上,双手被绑在背后)战斗。

标签: csv awk zsh informix keyvaluepair


【解决方案1】:

您的问题不清楚,但这可能是您正在寻找的:

$ 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"

【讨论】:

  • 您的 awk 语句可以从管道(在单行上)运行吗?我将其用作 BMC Server Automation 中“扩展对象”的一部分。
  • 当然。只需用; 替换每个换行符并将其称为whatever | awk 'script'。我当然不知道an "extended object" in BMC Server Automation 是什么。
  • 效果很好!谢谢! cat dbaccessoutput | awk 'BEGIN { RS=""; FS="\n"; OFS=","; ofmt="\"%s\"%s" }; {; for (i=1; i&lt;=NF; i++) {; tag = val = $i; sub(/[[:space:]].*/,"",tag); sub(/[^[:space:]]+[[:space:]]+/,"",val); tags[i] = tag; vals[i] = val; }; }; NR==1 {; for (i=1; i&lt;=NF; i++) {; printf ofmt, tags[i], (i&lt;NF ? OFS : ORS); }; }; {; for (i=1; i&lt;=NF; i++) {; printf ofmt, vals[i], (i&lt;NF ? OFS : ORS); }; }'
  • 不客气。您不需要将文件分类到 awk,但是,awk 完全能够自行打开文件。谷歌 UUOC 了解问题并使用awk 'script' file 而不是cat file | awk 'script'
  • 我只是对文件进行测试,在 EO(扩展对象)中,我正在执行我的 dbaccess 命令,执行一些 grep,然后管道到 awk 语句
【解决方案2】:

我将此作为 Informix 问题而不是 Awk 问题来处理。

使用标准的 Informix SQL 命令,您也可以创建一个 CSV 格式的 external table — 但您必须知道您可以使用一种未记录的格式 "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 <jonathan.leffler@hcl.com>

=cut

您会使用这样的命令(通过 DB-Access):

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

然后运行:

perl unl2csv datatable.unl > datatable.csv

SQLCMD 程序

如果您有我的 SQLCMD 程序(可从软件存储库中的 IIUG 网站获得,并且与 Microsoft 的同名 johnny-come-lately 完全无关),那么您可以直接卸载为 CSV 格式:

sqlcmd -d database -F csv -e 'unload to "data_table.csv" select * from data_table'

【讨论】:

    【解决方案3】:

    试试这个:

    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'
    

    【讨论】:

    • 我应该提到我正在使用基于 ZSH 的 NSH(网络外壳)。这是我收到的输出: sed: : No such file or directory nsh: command not found: xargs sed: : No such file or directory sed: 1: "1ipart_no,date_part,his ...": command i expects \ follow按文字
    • 你确实应该这样做,我用Ubuntu 尝试过,它成功了。对不起伙计。 $file 是您要转换的文件的名称。
    • 我将 $file 更改为我的文件名,这就是我收到的输出
    【解决方案4】:

    我知道 OP 说 awk 但 bash 只是坐在那里。

    #
    # 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
    

    【讨论】:

    【解决方案5】:

    您能否尝试关注一下,如果这对您有帮助,请告诉我。

    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
    

    【讨论】:

    • 这可以变成单行语句吗?
    • 啊,很好,是的,我的有回车符,我将它转换为 unix 格式,这成功了,但是我在不应该有的地方得到了额外的逗号。看到其他信息栏了吗?
    • 花点时间想想你的三元表达式,你就会明白为什么它会失败。此外,分配给 $1 会将输入中的所有其他空格转换为逗号。最后,对标题行进行硬编码显然是不好的做法,从而将脚本与特定值以及它们在输入数据中出现的顺序紧密耦合,而这些值已经按照需要打印的顺序出现在输入中。
    • 你的三元组而不是 var = var ? var s1 $X: s1 $X 应该是 var = (var == "" ? "" : var s1) $X 以避免重复代码,不会导致某些 awk 中的语法错误,因此当 var 的第一个值以数字方式计算为 0 时它们不会失败. wrt $1 分配 - 我真的不确定你要做什么,但我认为可能只是从每一行中删除标题值,这只是 sub(/^[^[:space:]]+[[:space:]]+/,"") 而不是 $1=""; gsub(/^ +|^,/,"");
    • @EdMorton,Ed 确实做到了,像往常一样非常感谢您在这里指导,真的很感谢您,周末快乐。
    猜你喜欢
    • 1970-01-01
    • 2022-10-15
    • 2021-10-10
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多