匹配第一个双括号而不是最后一个

Match first double parenthesis instead of last

我尝试了很长时间来替换:

(a (b ((c) (d)) (e) :hello ((f (g) h)))))

来自

(a (b ((c) (d)) (e)))

hello 没有出现在字符串的其他任何地方。如果尝试了很多不同的东西但认为它应该像这样工作:

 sed -i 's/\s:hello\s.*))//g'

然而,它似乎与前两个括号不匹配,即

(a (b ((c) (d)) (e) :你好 ((f (g) h ))))

但最后两个

(a (b ((c) (d)) (e) :你好 ((f (g) h)))))

从而删除 :hello.

之后的所有内容

我也试过使用 [^)]* 但只能让它带一个括号而不是两个并且因为在 g 之后有一个右括号所以它停在那里。

perl 更适合这里,因为它支持非贪婪匹配。下面的命令将匹配 hello 之后第一次出现的 )):

$ s='(a (b ((c) (d)) (e) :hello ((f (g) h))))'
$ echo "$s" | perl -pe 's/\s:hello\s.*?\)\)//'
(a (b ((c) (d)) (e)))

# you can also recursively match balanced parentheses
$ cat ip.txt
(a (b ((c) (d)) (e) :hello ((f (g) h))))
(a (b ((c) (d)) (e) :hello (f (g) h)))
(a (b ((c) (d)) (e) :hello (f h)))
(a (b ((c) (d)) (e) :hello ((f ((c) (d)) h))))
$ perl -pe 's/\s:hello\s(\((?:[^()]++|(?1))++\))//' ip.txt
(a (b ((c) (d)) (e)))
(a (b ((c) (d)) (e)))
(a (b ((c) (d)) (e)))
(a (b ((c) (d)) (e)))

您可以使用一些技巧让它与 sed 一起工作。在下面的解决方案中,所有出现的 )) 首先替换为换行符(因为在默认用法中该字符不能成为输入行的一部分)。 [^\n] 现在只能用于匹配第一次出现。之后,将所有换行符改回 )).

$ s='(a (b ((c) (d)) (e) :hello ((f (g) h))))'
$ echo "$s" | sed 's/))/\n/g; s/\s:hello\s[^\n]*\n//; s/\n/))/g'
(a (b ((c) (d)) (e)))

如果您的数据与您的样本相似,那么您可以匹配从冒号到最后一个字母后的最后 3 个括号的所有内容,并用任何内容替换它。

使用sed

sed 's/ :.*[a-z])))//' input_file
(a (b ((c) (d)) (e)))

.* 表示“尽可能跳过”。如果你不是这个意思,请不要使用它。

就像您已经发现的那样,“不是右括号”的正则表达式是 [^)]。但是,您希望允许使用一个括号,只要它后面没有紧跟另一个括号即可。这变得有点丑陋,因为您需要 \(...\|...\) 替代方案。 (切换到 sed -rsed -E 并不会真正改善这种情况,因为虽然你可以在这个结构中避免使用反斜杠,但你必须使用反斜杠或以其他方式转义文字括号,字符 类.)

sed 's/\s:hello\s\([^)]\|)[^)]\)*))//g'

-i 选项在这里没有意义(如果你确实有一个文件要处理并且你想就地处理它,也许把它放回去)并且 \s 不是便携(切换到 [[:space:]] 相当于 POSIX)。

如另一个答案所述,更现代的正则表达式工具提供了尽可能少跳过的非贪婪量词。考虑清楚地表达一个精确的需求仍然是件好事;非贪婪匹配只是实现精确的另一种工具。太多的初学者感到困惑,并将其用作“按我的意思去做”的锤子,当然它根本不是。