我想生成一个 csv 文件来告诉哪些服务器具有特定文件和所有权。这是我得到的原始输出:
server01,server02,server03,owner,/etc/file1
server04,owner,/etc/file2
server05,server06,owner,/etc/file3
我想用 Windows 格式换行符 (CF+LR) 替换服务器名称之间的逗号,并在它们之间添加引号,以便 CSV 将在同一个框中显示所有服务器。
期望的输出:
"server01
server02
server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05
server06",owner,/etc/file3
如何用sed实现呢?
答案1
,owner,
如果它是您想要包装在一个字段中的文字的剩余所有内容:
使用 GNU sed
:
sed -E ':1; s/,(.*,owner,)/\r\n\1/; t1; s/(.*)(,owner,)/"\1"\2/' file
和perl
:
perl -pe 's{.*?(?=,owner,)}{q(") . ($& =~ s/,/\r\n/gr) . q(")}e' file
如果除了最后两个字段之外的所有内容:
使用 GNU sed
:
sed -E ':1; s/(.*),(.*,.*,)/\1\r\n\2/; t1; s/(.*),/"\1",/' file
和perl
:
perl -pe 's{(.*)(?=,.*,)}{q(") . ($& =~ s/,/\r\n/gr) . q(")}e' file
或者使用Text::CSV
perl 模块正确进行 CSV 解析和格式化:
perl -MText::CSV -e '
$csv = Text::CSV->new({binary => 1, decode_utf8 => 0, eol => $" = "\r\n"});
while ($row = $csv->getline(STDIN)) {
if (($last = $#{$row}) > 1) {
$csv->print(STDOUT, ["@$row[0..$last-2]", @$row[$last-1..$last]]);
} else {
$csv->print(STDOUT, $row);
}
}' < file
如果文件采用带有 BOM 的 UTF-16 或 UTF-8 编码(这对于 Microsoft 文件来说并非闻所未闻),您可能必须进行调整(请参阅 参考资料 中的该方法),或者您可能更喜欢以某种方式perldoc Text::CSV
重新格式化<file dos2unix | ... | unix2dos
这样你就可以处理理智的输入。
答案2
我不会用 这样做sed
,我会使用perl
(或者也许awk
- 但然后我必须编写自己的pop()
函数join()
而不是使用 perl 内置函数):
$ perl -F, -lane '$file = pop @F; $owner = pop @F;
print join(",", "\"" . join("\r\n", @F) . "\"", $owner, $file)' input.csv
"server01
server02
server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05
server06",owner,/etc/file3
首先,它从数组中删除最后两个元素(所有者和文件名)@F
(@F
由于-a
选项指定使用逗号作为字段分隔符,因此为每个输入行自动创建-F,
- 类似于 awk 自动分割其输入的方式)和将它们存储在变量$file
和中$owner
。
"\"" . join("\r\n", @F) . "\""
构造一个字符串,其中 @F 的每个元素由 CR+LF 字符分隔,并且整个字符串用双引号引起来。
$owner
该字符串用and 和,连接(用逗号)$file
并打印。
答案3
您希望通过引用第一个逗号分隔字段与以下所有字段(直到(但不包括)每条记录中的最后两个字段)组合起来,生成一个带引号的 CSV 字段。然后,您需要用 CR+LF 替换该组合字段中嵌入的逗号。
您可以通过反转文件中每一行的内容、在第二个逗号后插入双引号、再次反转该行并在开头插入双引号来轻松完成此操作:
$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/'
"server01,server02,server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05,server06",owner,/etc/file3
由于我们现在有一个正确引用的无标头 CSV 文件,我们可以使用磨坊主(mlr
; 一个工具具体来说用于处理结构化数据(例如 CSV),用 CR+LF 替换第一个字段中的所有逗号:
$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/' | mlr --csv -N put '$1 = gsub($1, ",", "\r\f")'
server01
server02
server03,owner,/etc/file1
server04,owner,/etc/file2
server05
server06,owner,/etc/file3
请注意,无需引用字段,因为在 Unix 系统上,字段和记录分隔符未嵌入此数据集中的字段中。我们可以通过单独调用从每条记录中提取第二个字段来显示这一点mlr
:
$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/' | mlr --csv -N put '$1 = gsub($1, ",", "\r\f")' | mlr --csv -N cut -f 2
owner
owner
owner
您是否想保留最后一个sed
命令输出的原始引用,然后使用--quote-original
:
$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/' | mlr --csv -N --quote-original put '$1 = gsub($1, ",", "\r\f")'
"server01
server02
server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05
server06",owner,/etc/file3
不过,理想情况下,您应该更正产生此损坏的 CSV 输出的任何代码,以便该字段从一开始就是正确的,而不是在后处理步骤中修复它。
仅使用米勒:
mlr --nidx --fs comma put '
for (var i=2; NF > 3; i=i+1) {
$1 .= "\r\f" . $[i];
unset $[i]
}
$1 = "\"" . $1 . "\""' file
这会将文件读取为简单的逗号分隔、隐式索引的文本文件。然后,它将以 CR+LF 作为分隔符将第二个及后续字段附加到第一个字段的末尾,删除每个附加字段,直到只剩下三个字段。然后它显式引用第一个字段。