3

我有一个脚本可以搜索大量文件,并使用 sed 替换多行模式。该脚本是迭代的,它在某些迭代中运行良好,但有时会导致分段错误。

这就是脚本正在做的事情:

  1. 搜索不包含字符串 X 的文件
  2. 在这些文件中,搜索包含字符串 Y 的文件
  3. 使用 for 循环迭代返回的文件列表
  4. 如果文件内容与模式 A 匹配,则将模式 A 替换为 A_TAG
  5. 模式 B、C、D 相同(文件只能包含 A、B、C、D 之一)

模式 A、B、C、D 是多行的,它们被替换为两行。X 和 Y 是单行。

这是脚本。我为长线道歉,但我决定不编辑它们,因为它们是正则表达式。然而,我确实通过用“模式”替换字符串来缩短正则表达式 - 每个正则表达式中的替换内容都不相同,但它们没有任何特殊字符,所以我认为实际内容与这个问题无关。此外,正则表达式已被证明可以工作,因此您可能不需要完全理解它。

#!/bin/sh
STRING_A="Pattern(\n|.)*Pattern\.\""
A_TAG="\$STRING:A$"

STRING_B="(Pattern(\n|.)*)?(Pattern(\n|.)*)?Pattern(\n|.)*Pattern(\n|.)*Pattern\.((\n|.)*will be met\: http\:\/\/www.foo\.org\/example\/temp\.html\.\n)?"
B_TAG="\$STRING:B$"

STRING_C="(Pattern(\n|.)*)?Pattern(\n|.)*http\:\/\/www\.foo\.org\/bar\/old-foobar\/file\-2\.1\.html\.((\n|.)*Pattern.*Pattern)?"
C_TAG="\$STRING:C$"

STRING_D="(Pattern(\n|.)*)?(Pattern(\n|.)*http\:\/\/www\.foo\.org\/bar\/old-foobar\/file\-2\.1\.html.*|Pattern(\n|.)*Pattern)((\n|.)*http\:\/\/www\.some-site\.org/\.)?"
D_TAG="\$STRING:D$"

## params: #1 file, #2 PATTERN, #3 TAG
multil_sed()
{
 echo "In multil_sed"
 # -n = silent, -r = extended regex, -i = inline changes
 sed -nr '
  # Sed has a hold buffer that we can use to "keep text in memory". 
  # Here we copy the line to the buffer if it is the first line of the file, 
  # or append it if it is not
  1h
  1!H
  # We must first save all lines until the nth line to the hold buffer,
  # then we can search for our pattern
  60 {
    # Then we must use the pattern buffer. Pattern buffer holds text that
    # is up for modification. With g we can hopy the hold buffer into the pattern space
    g
    # Now we can just use the substitution command as we normally would. Use @ as a     delimiter
    s@([ \t:#*;/".\\-]*)'"$2"'@\1'"$3"'\
\1$QT_END_LICENSE$@Ig
    # Finally print what we did
    p
  }
 ' $1 > $1.foo;
 echo "Done"
}

for p in $(find . -type f -not -iwholename '*.git*' -exec grep -iL '.*STRING_X.*' {} \; | xargs grep -il -E '.*STRING_Y.*')
do
 echo
 echo "####################"
 echo "Working on file" $p
 #Find A
 if pcregrep -qiM "$STRING_A" "$p";
 then
  echo "A"
  multil_sed "$p" "$STRING_A" "$A_TAG"
 #Find B
 elif pcregrep -qiM "$STRING_B" "$p";
 then
  echo "B"
  multil_sed "$p" "$STRING_B" "$B_TAG"
 #Find C
 elif pcregrep -qiM "$STRING_C" "$p";
 then
  echo "C"
  multil_sed "$p" "$STRING_C" "$C_TAG"
 #Find D
 elif pcregrep -qiM "$STRING_D" "$p";
 then
  echo "D"
  multil_sed "$p" "$STRING_D" "$D_TAG"
 else
  echo "No match found"
 fi
 echo "####################"
done

我可能应该注意到 C 本质上是 D 的更长版本,在公共部分之前有一些额外的内容。

发生的情况是,对于某些迭代,这可以正常工作..

####################
Working on file ./src/listing.txt
A
In multil_sed
Done
####################

有时它不会。

####################
Working on file ./src/web/page.html
/home/tekaukor/code/project/tag_adder.sh: line 54: 16904 Segmentation fault      (core dumped) pcregrep -qiM "$STRING_A" "$p"
No match found
####################

它不依赖于正在搜索的模式。

####################
Working on file ./src/test/formatter_test.cpp
/home/tekaukor/code/project/tag_adder.sh: line 54: 18051 Segmentation fault      (core dumped) pcregrep -qiM "$STRING_B" "$p"
/home/tekaukor/code/project/tag_adder.sh: line 54: 18053 Segmentation fault      (core dumped) pcregrep -qiM "$STRING_C" "$p"
/home/tekaukor/code/project/tag_adder.sh: line 54: 18055 Segmentation fault      (core dumped) pcregrep -qiM "$STRING_D" "$p"
No match found
####################

第 54 行指向“for p in $(find . -type f -not -iwholename ' .git ' -exec grep...”行。

我的猜测是 sed 导致缓冲区溢出,但我还没有找到确定或解决此问题的方法。

4

2 回答 2

2

Bash 并不擅长在复合语句中定位错误的根源,所以

第 54 行指向行for p in $(find . -type f ...

具有误导性,因为错误可能出现在 for 语句块中的任何位置。错误信息

分段错误(核心转储) pcregrep -qiM "$STRING_D" "$p"

更准确。错误的原因可能是-M标志与无界模式相结合,(.|\n)*pcregrep 手册页所述:

-M, --multiline 允许模式匹配多于一行。当给出此选项时,模式可能有用地包含文字换行符和内部出现的 ^ 和 $ 字符。任何一个匹配的输出可能包含多于一行。设置此选项时,PCRE 库以“多行”模式调用。可以匹配的行数是有限制的,这是由 pcregrep 在扫描输入文件时缓冲输入文件的方式所施加的。但是,pcgrep 确保至少 8K 字符或文档的其余部分(以较短者为准)可用于前向匹配,并且类似地,前面的 8K 字符(或所有前面的字符,如果少于 8K)保证可用对于后向断言。

重点是我的。单个模式片段.*或者(.|\n)*可以从字面上匹配整个文件,所以是的,它不仅会填充它的前瞻缓冲区到下一个文字(例如http),而且直到它找到最后一个这样的文字,因为默认情况下正则表达式会寻找最长的符合匹配.

于 2013-05-31T12:52:34.713 回答
1

更新#2:显然 sed 不支持非贪婪匹配,这使得我的部分答案无效。有一些方法可以解决这个问题,但我不会在此处包含它们,因为它与原始问题相去甚远。这个问题的答案是使用 --disable-stack-for-recursion 标志,如下所述。


msw 的回答帮助我朝着正确的方向前进。

首先,我将正则表达式更改为懒惰而不是贪婪。默认情况下,正则表达式是贪婪的,这(如 msw 所述)意味着带有“PATTERN(.|\n)*TEXT”的多行表达式将搜索整个文件。通过添加 ”?” 在量词 (* -> *?) 之后,我使 regez 变得懒惰,这意味着 "(.|\n)*?" in "PATTERN(.|\n)*?TEXT" 将在第一个 TEXT 处停止扩展。

我还使可选部分变得懒惰(?->??),尽管我不确定这是否有必要。

然而这还不够。我还必须将 pcregrep 配置为使用堆而不是堆栈内存。我下载了 pcre 并使用标志 --disable-stack-for-recursion 进行了配置。请注意,使用堆要慢得多,因此如果您不必这样做,则不应这样做。

我将逐步介绍,以防有人在这里遇到同样的问题。请注意,我仍然是一个 linux 新手,并且很有可能我做了一些不必要和/或愚蠢的事情。说明基于http://www.mail-archive.com/pcre-dev@exim.org/msg00817.htmlhttp://www.linuxfromscratch.org/blfs/view/svn/general/pcre.html

  1. 从http://downloads.sourceforge.net/pcre/pcre-8.33.tar.bz2下载 pcre
  2. tar jxf pre-8.33.tar.bz2
  3. cd pcre-8.33
  4. ./configure --prefix=/usr --docdir=/usr/share/doc/pcre-8.33 --enable-utf --enable-unicode-properties --enable-pcregrep-libz2 --disable-static --disable -stack-for-recursion
  5. 制作
  6. 须藤使安装

提供的指南中有一些额外的步骤,但我不必这样做。

更新:使可选元素变得惰性 (? -> ??) 是一个错误,因为如果可能的话,它们将不会包含在匹配的模式中。

于 2013-06-03T08:49:51.813 回答