使用sed进行不区分大小写的查找和替换

117
我想使用SED从日志文件中提取文本。我可以轻松地进行搜索和替换:
sed 's/foo/bar/' mylog.txt

然而,我想让搜索不区分大小写。从我谷歌到的结果来看,似乎在命令的末尾添加i应该可以解决:

sed 's/foo/bar/i' mylog.txt

然而,这会给我一个错误信息:

sed: 1: "s/foo/bar/i": bad flag in substitute command: 'i'

这里出了什么问题,我该如何修复?


2
你可以尝试更新sed的副本吗?I是GNU扩展,可能在你的sed副本中不可用。 - Lazer
5
我划掉了关于OS X的资格要求,因为OP接受了一个在OS X上无法运行的答案。(正如另一个答案所指出的那样,在OS X上的sed不支持大小写不敏感匹配,与苹果文档相反。) - danorton
2
@danorton:谢谢你;如果你从我的回答中得出苹果文档承诺了一些实现没有提供的意义:man sed 与实现一致-没有提到(在实践中也没有支持)大小写不敏感匹配;如果你找到了声称另外情况的文档,请告诉我们。 - mklement0
2
@mklement0,是的,抱歉,我改口了。苹果文档没有声称sed支持大小写不敏感匹配。 - danorton
1
值得一提的是,那些在OS X中以BSD版本提供的GNU工具的版本可以从各种软件包管理器中获取。我通过Homebrew安装了完整的文本工具套件,并添加了g前缀,这样当我需要使用某些在原版中没有的功能时,我就可以使用gsedgdate等工具。 - Mark Reed
显示剩余3条评论
10个回答

90
更新:从macOS Big Sur(11.0)开始,sed现在支持I标志进行不区分大小写的匹配,所以问题中的命令现在应该可以工作了(BSD sed不会报告其版本,但您可以根据man页面底部的日期来判断,该日期应该是2017年3月27日或更近);以下是一个简单的示例:
# BSD sed on macOS Big Sur and above (and GNU sed, the default on Linux)
$ sed 's/ö/@/I' <<<'FÖO'
F@O   # `I` matched the uppercase Ö correctly against its lowercase counterpart

注意:大写字母I是旗帜的正式形式,但小写字母i也可以使用。
同样,在macOS Big Sur (11.0)开始,awk现在支持本地化awk --version应该报告20200816或更近的版本):
# BSD awk on macOS Big Sur and above (and GNU awk, the default on Linux)
$ awk 'tolower($0)' <<<'FÖO'
föo  # non-ASCII character Ö was properly lowercased

以下适用于 macOS Catalina(10.15)及以下版本:
明确一点:在 macOS 上,BSD 实现的 sed 不支持大小写不敏感的匹配 - 难以置信,但却是事实。之前被接受的答案(链接1),本身展示了一个 GNU sed 命令,之所以获得了这个地位,是因为评论中提到的基于 perl 的解决方案。
要使这个 Perl 解决方案也适用于包含外文字符的情况,使用类似以下的 UTF-8 方式:
perl -C -Mutf8 -pe 's/öœ/oo/i' <<< "FÖŒ" # -> "Foo"
  • -C 打开流和文件的 UTF-8 支持,假设当前区域设置是基于 UTF-8 的。
  • -Mutf8 告诉 Perl 将源代码解释为 UTF-8(在本例中是传递给 -pe 的字符串)- 这是更冗长的 -e 'use utf8;'. 的简短等效形式。感谢,Mark Reed

(请注意,也不能使用 awk,因为 macOS 上的 awk(即 BWK awkBSD awk)似乎完全不了解区域设置 - 它的 tolower()toupper() 函数会忽略外来字符(而 sub() / gsub() 本来就没有不区分大小写的标志)。)


关于sedawk与POSIX标准的关系的说明:
BSD的sedawk主要限制其功能在POSIX sedPOSIX awk规范所要求的范围内,而它们的GNU版本则实现了更多的扩展功能。

修复本地设置问题:http://blogs.agilefaqs.com/2014/01/12/fixing-perl-warning-setting-locale-failed-on-mac-osx/ - Eduardo Cuomo

85

编辑说明:该解决方案在 macOS 上(开箱即用)不起作用,因为它仅适用于 GNUsed,而 macOS 自带的是 BSDsed

将"I"大写。

sed 's/foo/bar/I' file

2
我也看到了,而且尝试过了...但我仍然收到相同的错误信息。 - Craig Walker
16
BSD版的sed似乎有很多限制。如果是这样的话,我会用Perl来做(即perl -pe 's/foo/bar/i')。 - Wesley Rice
3
OS X Lion的默认安装会出现错误:sed: 1: "s/foo/bar/I": bad flag in substitute command: 'I'。意思是,OS X Lion默认的安装会导致sed命令在替换时出现错误,具体错误原因是替换命令中的标志'I'无效。 - Ben Clayton
如果搜索和替换应该是全局的,而且不区分大小写,那么语法应该是 sed 's/foo/bar/Ig' file 吗? - amphibient
15
sed 中,后缀 I 的用法是不可移植的。POSIX sed 仅使用基本正则表达式(BREs),其功能非常有限。它们甚至不支持 +(必须使用 \{1,\} 替代),更不用说不区分大小写的匹配了。唯一可移植的方法是使用类似于 /[hH][eE][lL][lL][oO]/ 这样的方式进行匹配,但这在实践中往往是不切实际的。 - edam
6
必须使用/gI,否则它只会对第一个匹配项进行操作。 - Faheem Mitha

29
另一种在 Mac OS X 上解决 sed 的方法是从 MacPorts 或 HomeBrew 安装 gsed,然后创建别名 sed='gsed'

gsed "s/a/b/Ig" 可以使用,谢谢! 一个好的回答为什么要被踩呢? - Matthias M
3
这个回答很棒。我使用了brew install gnu-sed,然后进入我的~/.bash_profile并添加了别名。感谢@davmat。 - ThinkBonobo
8
最好执行brew install gnu-sed --with-default-names - 这将覆盖默认的sed - maroux
@Mar0ux --with-default-names 现已被弃用:brew.sh 我将 GNU sed 添加到了我的 PATH 中,但我相信现在有其他解决方法:SE question - FLonLon

8
如果您首先进行模式匹配,例如:
/pattern/s/xx/yy/g

然后您需要在模式后面放置 I

/pattern/Is/xx/yy/g

例子:

echo Fred | sed '/fred/Is//willma/g'

返回 willma; 不带 I, 返回原始字符串 (Fred)。


2
在MacOS上我得到了以下错误信息: sed: 1: "/fred/Is//willma/g": invalid command code I - Chris F Carroll
好的提示。这是我在复杂搜索中使用它的方法:sed -r '/'"$PATTERN"'/I,${s//'$YELLOW'&'$NO_COLOR'/g;b};$q3'。它会打印文本,如果找到模式(不区分大小写),则会用黄色(ansi颜色)突出显示文本。如果未找到,则返回退出代码3。 - Noam Manos

6

sed FAQ 解决了大小写不敏感的 搜索 相关问题。它指出,a)许多版本的 sed 支持标志,b)在 sed 中这样做很麻烦,你应该使用 awk 或 Perl。

但是,在 POSIX sed 中进行操作时,他们提供了三种选择(此处适用于替换):

  1. Convert to uppercase and store original line in hold space; this won't work for substitutions, though, as the original content will be restored before printing, so it's only good for insert or adding lines based on a case-insensitive match.

  2. Maybe the possibilities are limited to FOO, Foo and foo. These can be covered by

     s/FOO/bar/;s/[Ff]oo/bar/
    
  3. To search for all possible matches, one can use bracket expressions for each character:

     s/[Ff][Oo][Oo]/bar/
    

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html 是你可以在 sed 中进行可移植操作的内容。 - D.Shawley
@D.Shawley 这并不与答案中的任何内容相矛盾,对吧?或者你想通过链接到官方规范来添加上下文吗?我可以将其添加到答案中。 - Benjamin W.
这里没有任何矛盾之处。我很高兴看到有人引用POSIX并想要添加一个链接。这里的大多数回答都在忙于哀叹macOS实现sed的“非标准”,这让我感到不安。 - D.Shawley
@D.Shawley现在已经添加了规范的链接 :) - Benjamin W.

2

sed在Mac版本中似乎有一些限制。解决方法之一是使用一个带有可用版本sed的Linux容器(通过Docker):

cat your_file.txt | docker run -i busybox /bin/sed -r 's/[0-9]{4}/****/Ig'

19
这是一件特别恶劣的事情。如果有人真的在认真考虑这样做,只需在本地安装GNU sed即可。 - ocodo
2
过度但有用的通用方法,值得了解! - YvesgereY

2
请使用以下内容替换所有出现的内容:
sed 's/foo/bar/gI' mylog.txt

0

以下应该可以。

  sed -i 's/foo/bar/gi' mylog.txt

0
不是直接的答案,但在某些情况下,可以通过使用 tr A-Z a-z 将整个流转换为小写来处理整个内容。
当然,你会失去大写字母,但这种损失可能会因简化管道的其他部分而得到弥补。数字和日期/时间也不受影响,输出流也会更好地进行压缩。电子邮件地址不区分大小写,所以这并不重要。
一个缺点是大小写敏感的标识符可能会变得笨拙。以这种方式发送的 Sendmail 日志将变得不太有用。

0
我有类似的需求,并想出了以下解决方案:
使用以下命令来查找所有文件:
grep -i -l -r foo ./* 

为了排除 this_shell.sh(假设你将命令放在名为 this_shell.sh 的脚本中),请将输出重定向到控制台以查看发生了什么,然后对找到的每个文件名使用 sed 将文本 foo 替换为 bar:

grep -i -l -r --exclude "this_shell.sh" foo ./* | tee  /dev/fd/2 | while read -r x; do sed -b -i 's/foo/bar/gi' "$x"; done 

我选择了这种方法,因为我不喜欢所有时间戳都被更改,而文件并没有被修改。提供grep结果只允许查看具有目标文本的文件(因此可能会提高性能/速度)

在使用之前,请务必备份您的文件并进行测试。对于带有嵌入空格的文件,在某些环境中可能无法正常工作。(?)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接