如何将查找和替换限制为仅 CSV 中的一列?

Posted

技术标签:

【中文标题】如何将查找和替换限制为仅 CSV 中的一列?【英文标题】:How to restrict a find and replace to only one column within a CSV? 【发布时间】:2012-04-20 12:00:48 【问题描述】:

我有一个 4 列的 CSV 文件,例如:

0001 @ fish @ animal @ eats worms

我使用sed 对文件进行查找和替换,但我需要将此查找和替换限制为仅在第 3 列中找到的文本。

我怎样才能让查找和替换只发生在这一列上?

【问题讨论】:

这不是 CSV,但我认为您让我们更容易阅读。是否保证字段之间的分隔符(在您的示例中显示为@)可以从不出现在其他任何地方?那么您是在问如何在第二个和第三个“@”之间查找一段文字,还是更复杂,即模式?请举例说明需要找到什么,以及将用什么代替。 它是一个 CSV,但使用@ 来划分列,周围有空格。我只显示了一行来显示格式。逗号在整个文件中经常出现,但@ 从不出现,所以我使用@ 作为分隔符。替换示例是sed -i "s/a/b/g" ./file.csv(将所有出现的“a”替换为“b”,但我只希望此替换发生在第 3 列中的条目内,并且不影响@ 两侧的空格。 【参考方案1】:

您确定要使用sed 吗? csvfix 呢?您的 CSV 是否美观且简单,没有引号或嵌入的逗号或其他使正则表达式变得不那么令人满意的处理通用 CSV 文件的方式?我假设@ 是您格式中的“逗号”。

考虑使用awk 而不是sed

awk -F@ '$3 ~ /pattern/  OFS= "@"; $3 = "replace"; '

可以说,您应该有一个设置 OFS 一次的 BEGIN 块。对于一行输入,它没有任何优势(而且您可能也很难衡量一百万行输入的差异):

$ echo "pattern @ pattern @ pattern @ pattern" | 
> awk -F@ '$3 ~ /pattern/  OFS= "@"; $3 = "replace"; '
pattern @ pattern @replace@ pattern
$

如果sed 看起来仍然很吸引人,那么:

sed '/^\([^@]*@[^@]*\)@pattern@\(.*\)/ s//\1@replace@\2/'

例如(并注意输入和输出略有不同 - 如果需要,您可以将其修复为与 awk 相同的处理相当):

$ echo "pattern@pattern@pattern@pattern" |
> sed '/^\([^@]*@[^@]*\)@pattern@\(.*\)/ s//\1@replace@\2/'
pattern@pattern@replace@pattern
$

第一个正则表达式查找一行的开头、一个非符号字段、一个符号字段、另一个非符号字段并记住批次;它查找一个 at 符号、模式(它必须在第三个字段中,因为前两个字段已经匹配)、另一个 at 符号,然后是该行的剩余部分。当该行匹配时,它会用前两个字段(未更改,根据需要)替换该行,然后添加替换的第三个字段,以及该行的剩余部分(未更改,根据需要)。

如果您需要编辑而不是简单地替换第三个字段,那么您可以考虑使用awk 或 Perl 或 Python。如果您仍然受限于sed,那么您可以探索使用保持空间来保持部分行,同时在模式空间中操作另一部分,并最终从保持空间和模式重新集成所需的输出行打印线之前的空间。这几乎和听起来一样混乱。实际上,可能比听起来更混乱。我会选择 Perl(因为我很久以前就学会了它,而且它很容易做到这一点),但你可以使用任何你喜欢的非sed 工具。


Perl 编辑第三个字段。请注意,默认输出是 $_,它必须从数组 @F 中的自动拆分字段重新组合。

$ echo "pattern@pattern@pattern@pattern" | sh -x xxx.pl
> perl -pa -F@ -e '$F[2] =~ s/\s*pat(\w\w)rn\s*/ prefix-$1-suffix /; $_ = join "@", @F; ' "$@"
pattern@pattern@ prefix-te-suffix @pattern
$

解释。-p 表示“循环,将行读入$_ 并在每次迭代结束时打印$_”。 -a 表示“自动将 $_ 拆分为数组 @F”。 -F@ 表示字段分隔符为 @-e 后面是 Perl 程序。数组在 Perl 中从 0 开始索引,因此第三个字段被拆分为 $F[2](印记 — @$ — 根据您使用数组中的值还是将数组中的值作为=~ 是一个匹配运算符;它将 RHS 上的正则表达式应用于 LHS 上的值。替代模式识别零个或多个空格 \s* 后跟 pat 然后是记住的两个“单词”字符进入$1,然后rn和零个或多个空格;也许那里应该有一个^$来绑定到字段的开头和结尾。替换是一个空格,'prefix- ',记住的一对字母,'-suffix' 和一个空格。$_ = join "@", @F; 从可能修改的单独字段中重新组合输入行$_,然后-p 将其打印出来。不像我想要(所以可能有更好的方法来做到这一点),但它有效。你可以在 ar 上进行任意变换Perl 中的二进制字段没有太大困难。 Perl 也有一个模块 Text::CSV(和一个高速 C 版本,Text::CSV_XS),它可以处理非常复杂的 CSV 文件。

【讨论】:

【参考方案2】:

基本上将线条分成三部分,中间是您要寻找的图案。然后保留外片,更换中间片。

/\([^@]*@[^@]*@\[^@]*\)pattern\([^@]*@.*\)/s//\1replacement\2/

\([^@]*@[^@]*@\[^@]*\) - 收集模式之前的所有内容,包括第三个 @ 和数学之前的任何文本 - 这变成 \1

pattern - 你要找的东西

\([^@]*@.*\) - 在模式之后收集所有内容 - 这变成 \2

然后将该行更改为\1,然后是replacement,然后是pattern之后的所有内容,即\2

【讨论】:

【参考方案3】:

这可能对你有用:

echo 0001 @ fish @ animal @ eats worms|
sed 's/@/&\n/2;s/@/\n&/3;h;s/\n@.*//;s/.*\n//;y/a/b/;G;s/\([^\n]*\)\n\([^\n]*\).*\n/\2\1/'
0001 @ fish @ bnimbl @ eats worms

解释:

    定义要处理的字段(在本例中为第 3 个)并在它之前和之后直接插入一个换行符 (\n)。 s/@/&\n/2;s/@/\n&/3 将该行保存在保留空间中。 h 删除s/\n@.*//;s/.*\n//两侧的字段 现在处理该字段,即将所有 a's 更改为 b'sy/a/b/ 现在追加原始行。 G 用新字段替换旧字段(同时删除所有换行符)。 s/\([^\n]*\)\n\([^\n]*\).*\n/\2\1/

注意在第 4 步中,模式空间仅包含定义的字段,因此可以在此处执行任意数量的命令,结果不会影响该行的其余部分。

【讨论】:

以上是关于如何将查找和替换限制为仅 CSV 中的一列?的主要内容,如果未能解决你的问题,请参考以下文章

如何将商店中的 iOS 应用程序限制为仅限 4S 和 iPad 3

如何将结果集限制为仅 JOIN 中的最新实例

将搜索和替换限制为类别

如何将单个字符的 natvis 限制为仅 Visual Studio 中的字符

Oracle Apex:如何将表单中的数字字段限制为仅允许数字?

如何使用包含所有映射的查找表替换多列中的值?