用正则表达式替换字符时忽略乳胶宏

时间:2019-04-07 04:48:42

标签: regex perl latex

我有一个文件需要转换为自定义代码页。该文件具有以下内容:

foo bar baz \bazfoo \barfoo foo bar \foobar

我想用bar替换foo,除非foo作为LaTeX宏的一部分出现,例如\ bazfoo,\ barfoo和\ foobar

换句话说,s/foo/bar/,但是\ bazfoo必须保留\ bazfoo。有没有办法使用lookead运算符来做到这一点?

4 个答案:

答案 0 :(得分:4)

可以使用否定的字符类,要求单词的模式不能以\开头

s{(?: ^|\s ) (?: [^\\\s]\S* )? \K foo}{XXX}gx

foo也可能出现在字符串或单词的开头,因此交替使用^|\s[^\\\s]\S*是可选的。 \需要在字符类中转义,否则它本身也会转义]

\K会丢弃所有匹配点,因此我们不必捕获它们并将它们放回原处。

后面的负数不允许使用可变长度的模式,这是一个问题。

测试,除了测试字符串之外

perl -wE'$_=q(foo bar somefoo \bazfoo \barfoo foo bar \foobar); say; 
    s{(?: ^|\s ) (?: [^\\\s]\S* )? \K foo}{XXX}gx; say'

打印

foo bar somefoo \bazfoo \barfoo foo bar \foobar
XXX bar someXXX \bazfoo \barfoo XXX bar \foobar

请注意,测试字符串不包含foo在单词中但仍需要替换的情况,例如somefoo。我在上面加了

答案 1 :(得分:2)

尽管zdim已经有一个引人入胜的解决方案,但我仍然想分享我的版本。

由于后面的长度可变,我也遇到了问题。

所以我的解决方案是对字符串含义进行“标记”:选择每个“单词”,仅替换那些不是以\开头的单词。

perl -e '
  $_=q(foo bar baz \bazfoo \barfoo foo bar \foobar);
  s/(\S+)/                  # pick the word
    $word=$1;               # save it
    if ($word!~m#^\\#) {    # test for LaTeX
      $word=~s#foo#bar#g;   # otherwise replace
    }
    $word                   # the result
  /gex;                     # globally, execute and eXtended for comments
  print $_;
'

不幸的是,这要求使用“ e”(-xecute)标志。

更新:根据@Alex的说法(请参见下面的评论)»此解决方案找不到{\ foo},这是有效的LaTeX语法。«。

因此,如果需要,将上面的if语句行更改为if ($word!~m#^\\|^\{\\.*\}$#) {

答案 2 :(得分:2)

如果每个单词只需要处理一个foo

s/ (?: ^ | \s++ ) (?: [^\\\s]\S* )? \K foo /bar/gx

如果您只需要每个单词处理多个foo

s{ (?: ^ | \s++ ) \K ( [^\\\s]\S* ) }{ $1 =~ s/foo/bar/rg }egx

这些是先前解答中解决方案的固定版本和优化版本。 (也对先前的答案进行了修正,但并非优化。)

答案 3 :(得分:0)

如果我们确定任何Latex令牌字符都不会成为字符char。并将您的数据存储在“ d”中,只需:

sed -E 's/(^|\s)(\w*)foo/\1\2bar/g' d
perl -pe 's/(^|\s)(?:\w*)foo/$1bar/g' d