Bash 使用分隔符读取过去的换行符

Bash 使用分隔符读取过去的换行符

我有一个 csv,我必须为其提取特定字段。 csv 以竖线分隔 ( |),并用双引号 ( ") 来保护文本字段和某些文本中的换行符(这是问题所在)。

例如。

"aaa"|"111"|"!!!"|""
"bbb"|"222"|"@@
@"|""
"ccc"|"333"|"###"|""

我想提取每条记录的第二个字段:

111
222
333

我正在使用 bash 脚本来读取文件,但即使指定了read换行符,命令似乎也会在到达换行符时停止。-d这意味着,在上面的示例中,我的脚本正确处理记录 1(我使用read -d \| varname),但不能正确处理记录 2,因为它无法将换行符识别为第三个字段的一部分。现在它被@"|""视为一个新记录,一切都变得混乱。

是否有可能用于read此目的,或者我应该考虑替代方案?

我花了尝试使用read的设置并在网络上进行搜索。有趣的是,我遇到有人在与我完全相同的输入文件上遇到问题,但问题出在 Excel 上。

答案1

read对于具有可以处理 CSV 的内置命令的 shell ,您可以使用ksh93以下命令代替bash

$ while IFS='|' read -rS a b c; do printf '%s\n' "$b"; done < file
111
222
333

要将格式转换为可以处理的格式bashread您可以执行以下操作:

< file ksh93 -c 'while IFS="|" read -rSA a; do
                   printf "%s|" "${a[@]//[\|]/\\\0}"
                   printf "\0"
                 done' |
       bash -c 'while IFS="|" read -d "" a b c; do
                  printf "%s\n" "$b"
                done'

答案2

您确实应该使用合适的 CSV 解析器。例如,使用 ruby​​ 附带的:

ruby -rcsv -e 'CSV.foreach("file", :col_sep => "|") {|row| p row; puts row[1]}'

我们得到

["aaa", "111", "!!!", ""]
111
["bbb", "222", "@@\n@", ""]
222
["ccc", "333", "###", ""]
333

您可以看到第二行有嵌入的换行符。删除p row以摆脱那些“调试”行。

答案3

好的,所以对我来说最好的解决方案(但我想这是一个品味问题),是使用 PHP 的fgetcsv,因为我已经在该服务器上安装了 PHP。遗憾的是 bashread命令不能像 PHP 函数那样处理换行符。它会自动识别附加分隔符(例如“”)。

样本:

<?php
$row = 1;
if (($handle = fopen("test.csv", "r")) !== FALSE) {
    while (($data = fgetcsv($handle, 10000, "|")) !== FALSE) {
        $num = count($data);
        echo "$num fields in line $row:\n";
        $row++;
        for ($c=0; $c < $num; $c++) {
            echo $c + 1 . ": " . $data[$c] . "\n";
        }
    }
    fclose($handle);
}
?>

输出(例如在我原来的问题中):

4 fields in line 1:
1: aaa
2: 111
3: !!!
4: 
4 fields in line 2:
1: bbb
2: 222
3: @@
@
4: 
4 fields in line 3:
1: ccc
2: 333
3: ###
4: 

相关内容