使用Linux工具将多行CSV转换为单行

时间:2019-08-06 00:54:03

标签: bash csv awk sed

我有一个.csv文件,其中包含双引号的多行字段。我需要将多行单元格转换为单行。它没有显示在示例数据中,但是我不知道哪些字段可能是多行的,因此任何解决方案都需要检查每个字段。我知道我将有多少列。第一行也将需要跳过。我没有多少数据,因此性能不是一个考虑因素。

我需要一些可以从Linux上的bash脚本运行的东西。最好使用awk或sed之类的工具,而不是实际的编程语言。

将使用Logstash进一步处理数据,但是它不能处理双引号多行字段,因此需要进行一些预处理。

我尝试过类似的操作,但它可以在一行上工作,但在多行上失败。

sed -e :0 -e '/,.*,.*,.*,.*,/b' -e N -e '1n;N;N;N;s/\n/ /g' -e b0 file.csv

CSV示例

First name,Last name,Address,ZIP
John,Doe,"Country

City
Street",12345

我想要的输出是

First name,Last name,Address,ZIP
John,Doe,Country City Street,12345
Jane,Doe,Country City Street,67890
etc.
etc.

5 个答案:

答案 0 :(得分:1)

如果您选择Perl,请尝试以下操作:

perl -e '
while (<>) {
    $str .= $_;
}

while ($str =~ /("(("")|[^"])*")|((^|(?<=,))[^,]*((?=,)|$))/g) {
    if (($el = $&) =~ /^".*"$/s) {
        $el =~ s/^"//s; $el =~ s/"$//s;
        $el =~ s/""/"/g;
        $el =~ s/\s+(?!$)/ /g;
    }
    push(@ary, $el);
}

foreach (@ary) {
    print /\n$/ ? "$_" : "$_,";
}' sample.csv

sample.csv:

First name,Last name,Address,ZIP
John,Doe,"Country

City
Street",12345
John,Doe,"Country

City
Street",67890

结果:

First name,Last name,Address,ZIP
John,Doe,Country City Street,12345
John,Doe,Country City Street,67890

答案 1 :(得分:1)

这可能对您有用(GNU sed):

sed ':a;s/[^,]\+/&/4;tb;N;ba;:b;s/\n\+/ /g;s/"//g' file

测试每行以查看其包含正确数量的字段(在示例中为4)。如果没有足够的字段,请追加下一行并重复测试。否则,用空格替换换行符,最后删除"

这可能充满问题,例如,和引号"之间的"

答案 2 :(得分:1)

尝试cat -v file.csv。使用Excel制作文件时,您可能会遇到一些麻烦:当字段中的换行符是简单的\n并且末尾的换行符是\r\n(看起来像^ M)时,解析很简单。

# delete all newlines and replace the ^M with a new newline.
        tr -d "\n" < file.csv| tr "\r" "\n"

# Above two steps with one command
        tr "\n\r" " \n" < file.csv

如果要在连接的行之间留一个空格,则需要执行额外的步骤。

tr "\n\r" " \n" < file.csv | sed '2,$ s/^ //'

编辑:@sjaak评论说这没用是他的情况。

当虚线也有^M时,您仍然可以是个幸运的人。
如果断字段始终是双引号中的第一个字段,并且您具有GNU sed 4.2.2,则当第一行正好有一个双引号时,您可以连接两行。

 sed -rz ':a;s/(\n|^)([^"]*)"([^"]*)\n/\1\2"\3 /;ta' file.csv

说明:
-z请勿将\ n用作行尾
:a标签,用于在成功替换后重复该步骤
(\n|^)在换行符或第一行之后搜索
没有([^"]*)的{​​{1}}子字符串
"返回标签a并重复

答案 3 :(得分:1)

awk 模式匹配正在运行。 一行回答:

  awk '/,"/{ORS=" "};/",/{ORS="\n"}{print $0}' YourFile

如果您想删除引号,可以使用:

  awk '/,"/{ORS=" "};/",/{ORS="\n"}{print $0}' YourFile | sed 's/"//gw NewFile'

但是我更喜欢保留它。

解释代码:

  1. / 模式 /:在当前行中找到模式。

  2. ORS :指示 output 行记录。

  3. $ 0 :指示整个当前行。

  4. 的/ OldPattern / NewPattern /':用NewPattern取代第一个OldPattern

  5. / g:对所有OldPattern执行上一个操作

  6. / w:将结果写入Newfile

答案 4 :(得分:1)

首先我很抱歉迟到7个月...

我遇到了一个与您今天类似的问题,其中包含多个具有多行类型的字段。我很高兴找到您的问题,但至少对于我而言,我有一个复杂性,因为一个以上的字段相互冲突,引号可能会在同一行上打开,关闭并再次打开...无论如何,要阅读很多内容并合并答案从不同的帖子中,我想到了这样的东西:

首先,我计算一行中的引号,为此,我将除引号外的所有内容都取出来,然后使用wc:

quotes=`echo $line | tr -cd '"' | wc -c` # Counts the quotes

如果您想到单个多行字段,那么知道引号是1还是2就足够了。在像我这样的更通用的场景中,我必须知道引号的数量是否为奇数,甚至还必须知道行是否完成了记录或需要更多信息。

要检查偶数还是奇数,通常可以使用mod操作数(%):

even % 2 = 0
odd % 2 = 1

对于第一行:

  • 奇数表示该行期望下一行有更多信息。
  • 甚至表示该行已完成。

对于后面的几行,我必须知道上一行的状态。例如在您的示例文字中:

First name,Last name,Address,ZIP
John,Doe,"Country

City
Street",12345

您可以说第1行(John,Doe,"Country)有1个引号(奇数),这表示记录的状态不完整或处于打开状态。

转到第2行时,没有报价(偶数)。尽管如此,这并不意味着记录是完整的,您必须考虑先前的状态...因此对于第一条记录之后的行将是:

  • 奇数表示记录状态切换(不完整以完成)。
  • 甚至表示记录状态保持为前一行。

我所做的是一行一行地循环,同时将最后一行的状态带到下一行:

incomplete=0
cat file.csv | while read line; do
    quotes=`echo $line | tr -cd '"' | wc -c` # Counts the quotes
    incomplete=$((($quotes+$incomplete)%2))  # Check if Odd or Even to decide status
    if [ $incomplete -eq 1 ]; then
        echo -n "$line " >> new.csv          # If line is incomplete join with next
    else
        echo "$line" >> new.csv              # If line completes the record finish
    fi
done

执行此命令后,您格式的文件将生成一个new.csv,如下所示:

First name,Last name,Address,ZIP
John,Doe,"Country  City Street",12345

我和每个人一样都喜欢单行代码,为了清晰起见,我写了那个脚本,可以-可以说是这样写成一行:

i=0;cat file.csv|while read l;do i=$((($(echo $l|tr -cd '"'|wc -c)+$i)%2));[[ $i = 1 ]] && echo -n "$l " || echo "$l";done >new.csv

如果您可以返回示例并查看它是否适合您的情况(您很可能已经解决了),将不胜感激。希望这仍然可以帮助其他人...

恢复多行字段

每个需求都是不同的,在我的情况下,我希望一行中的记录进一步处理csv以添加一些bash提取的数据,但是我想保持csv不变。为此,我没有使用空格将行连接在一起,而是使用了一个代码(可能是唯一的),然后我可以搜索并替换:

i=0;cat file.csv|while read l;do i=$((($(echo $l|tr -cd '"'|wc -c)+$i)%2));[[ $i = 1 ]] && echo -n "$l ~newline~ " || echo "$l";done >new.csv

代码是〜newline〜,这当然是任意的。

然后,在完成处理后,我获取了csv文本文件,并将编码的换行符替换为实际的换行符:

sed -i 's/ ~newline~ /\n/g' new.csv

参考:

TL; DR

运行此:

i=0;cat file.csv|while read l;do i=$((($(echo $l|tr -cd '"'|wc -c)+$i)%2));[[ $i = 1 ]] && echo -n "$l " || echo "$l";done >new.csv

...并在new.csv中收集结果

希望对您有帮助!