如何将两个单词之间的某些字符替换为 CR+LF

如何将两个单词之间的某些字符替换为 CR+LF

我想生成一个 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::CSVperl 模块正确进行 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 作为分隔符将第二个及后续字段附加到第一个字段的末尾,删除每个附加字段,直到只剩下三个字段。然后它显式引用第一个字段。

相关内容