如何从 CSV 字段中删除嵌入的换行符

如何从 CSV 字段中删除嵌入的换行符

我正在努力处理文件。通常我应该为每个主机定义一行。但有时,有人会将某些字段拆分为不同的行。这是一个例子:

"host1","host1","linux
server",""
"host2","host2","linux server",""

现在我想找到一种方法(在 bash 中更好)来解决这个问题:

"host1","host1","linux server",""
"host2","host2","linux server",""

每个字段都应该用双引号引起来;如果不是这种情况,则意味着\n插入了 a,然后我想将其删除,以便每行始终有 4 个字段。

请注意,我可能会将描述分为几行,例如:

"host1","host1","linux
server
centos",""
"host2","host2","linux server",""

我尝试了几种awk方法,例如

awk 'BEGIN {ORS=""; RS="\"\n\""; FS="\",\""; OFS="\",\""} {if (NF == 3) print "\"" $1 "\"," $2 "\"," $3 "\"\n"; else printf "%s", $0} END {print ""}' /tmp/ngr4

但我没有成功,我开始用这个强大的工具达到我的极限。

答案1

使用米勒 ( mlr),一个支持 CSV 的多用途处理实用程序,适用于各种结构化文档格式,用于清理所有字段的空白:

$ cat file
"host1","host1","linux
server",""
"host2","host2","linux server",""
$ mlr --csv -N clean-whitespace file
host1,host1,linux server,
host2,host2,linux server,

这会将数据读取file为无标头 CSV 记录并应用操作clean-whitespace每一个。该clean-whitespace操作会修剪每个字段值中的侧翼空格,并将连续的空格字符组合成单个空格。

改为只用空格替换换行符,您可以使用简短的语句迭代字段put表达:

$ mlr --csv -N put 'for (k,v in $*) { $[k] = gssub(v, "\n", " ") }' file
host1,host1,linux server,
host2,host2,linux server,

功能gssub()其行为类似于gsub()Awk,但不会将其查询参数视为正则表达式(Miller 也有gsub())。

如果您觉得需要对字段进行引号,即使它不是严格需要的(如果字段的值需要,Miller 会自动添加引号),然后mlr与其--quote-all选项一起使用:

$ mlr --csv -N --quote-all clean-whitespace file
"host1","host1","linux server",""
"host2","host2","linux server",""
$ mlr --csv -N --quote-all put 'for (k,v in $*) { $[k] = gssub(v, "\n", " ") }' file
"host1","host1","linux server",""
"host2","host2","linux server",""

答案2

您最不想做的就是尝试在 bash 中执行此操作。看为什么使用 shell 循环处理文本被认为是不好的做法?

现在,如果您想要的可以表达为“删除任何换行符,除非它们紧接在字符之后"”,您可以执行以下操作:

perl -pe 's/(?<!")\n/ /g' file

匹配(?<!")\n任何前面没有 的换行符"。因此给出一个像这样的输入示例:

$ cat file
"host0","host0","linux
server",""
"host1","host1","linux
server
centos",""
"host2","host2","linux server",""

上面的命令给出:

$ perl -pe 's/(?<!")\n/ /g' file
"host0","host0","linux server",""
"host1","host1","linux server centos",""
"host2","host2","linux server",""

但实际上,mlr这是最好的方法。

答案3

使用(以前称为 Perl_6)

受到 @terdon 出色的 Perl 答案的启发:

~$ raku -ne '/ <!after \" > $/ ?? print "$_ " !! put $_;'  file

以下是用 Raku(又名 Perl6)编写的答案。 Raku 有一个新的 Unicode 感知正则表达式引擎,它试图清理一些众所周知的习语。因此(例如),“Y not-after X”负回顾习语<!after X > Y在 Raku 中变为,其中<?after … >表示正回顾,<!after … >表示负回顾。

\n由于 Raku 规范了行终止符处理(现在默认自动截断换行符),因此我们只需使用 Raku 的三元运算符即可检测模式测试 ?? 真的 !! 错误的,然后使用print(不添加\n换行符)或put\n在文本末尾添加换行符)输出。


输入示例:

"host0","host0","linux
server",""
"host1","host1","linux
server
centos",""
"host2","host2","linux server",""

示例输出:

"host0","host0","linux server",""
"host1","host1","linux server centos",""
"host2","host2","linux server",""


其他 Raku 解决方案

使用 Raku 的Text::CSV模块:

使用 Raku 生态系统中适当的 CSV 解析器(模块)相当容易(请参阅:https://raku.land/?q=CSV)。这些检查是否符合 RFC 4180 合规性,并可以为您提供标准化的 CSV 输出以及大量自定义内容。

下面,Raku 的Text::CSV模块很好地解析了 OP 的输入,并且在删除\n换行符后,默认情况下将仅输出那些带有内部空白的列作为双引号列(第一个答案)。第二个答案按行读取并产生与第一个答案相同的答案:

将整个文件读入内存,默认csv()输出:

~$ raku -MText::CSV -e 'my @a = csv(in => "/path/to/file", sep => ",");
                        @a = @a>>.map( *.trans: "\n" => " ");
                        csv(in => @a, out => $*OUT, sep => ",");'
host0,host0,"linux server",
host1,host1,"linux server centos",
host2,host2,"linux server",

逐行读取,然后“手动”引用输出:

~$ raku -MText::CSV -e 'my $fh = "/path/to/file";  my $io = open $fh, :r, :!chomp; 
                        my $csv = Text::CSV.new;  my @data;
                        while $csv.getline($io) -> $row {
                            @data.push: $row.map: *.trans: "\n" => " "; };
                        put $_.join(",") for @data>>.map({ / \s / ?? (q["] ~ $_ ~ q["]) !! $_ });'
host0,host0,"linux server",
host1,host1,"linux server centos",
host2,host2,"linux server",

https://docs.raku.org/language/operators#infix_??_!
https://github.com/Tux/CSV/blob/master/doc/Text-CSV.md#embedded-newlines
https://raku.org

答案4

我们假设尽管有许多不需要的换行符,但所有双引号都匹配并且所有字段分隔符都存在。在这种情况下你可以使用这个命令:

$ sed '/^"/! s/^/ /'  infile | tr -d '\n' | sed '-e s/"/"\n/'{8..1000..8}
"host1","host1","linux server centos",""
"host2","host2","linux server",""

在哪里:

$ cat infile
"host1","host1","linux
server
centos",""
"host2","host2","linux server",""

无论哪里可能发生不需要的换行符,这都应该有效。

大括号内的数字 1000 是任意大的数字,并且必须大于输入文件中的字符总数。

如果您怀疑输入行可能包含前导/尾随空格,请先将其删除。例如使用这个命令awk 'NF{$1=$1}1' infile

相关内容