脚本匹配多行文字模式?

脚本匹配多行文字模式?

我的变量中有一个多行字符串$PAT$PAT必须在文件内搜索$FILE。如果$PAT$FILE,则需要打印删除后的文件$PAT。如果$PAT没有找到,则不打印任何内容。未知是否$PAT包含特殊字符,必须按字面匹配。例如,如果$PAT//\/\\|*,则应在 中搜索完全相同的 8 个字符字符串$FILE

实际用途是在现有文件/脚本中安装和删除文本。如果你想追加到$PAT$FILE,你想知道它之前是否已经被追加过。如果$PAT已经在 中$FILE,则输出 without$PAT允许您轻松卸载它。

我需要这样一个脚本的系统(Android 设备)上只有 BusyBox。没有 Perl 或其他脚本语言。

答案1

如果你想匹配$PAT完整的行,我有一个解决方案。通过完整的行,我的意思是,在匹配的情况下,您可以拆分$FILE为三个子文件(f1、f2 和 f3),其中:

  • cat f1 f2 f3$FILE
  • f2 是$PAT.

请注意,f1 和/或 f3 可以为空。

首先,创建 f2 文件:

cat << EOF > f2
$PAT
EOF

然后,比较 $FILE 和 f2,保存结果:

diff $FILE f2 > diff_res
res=$?

如果$res为零,则 f1 和 f3 为空,$FILE 等于 $PAT。我假设在这种情况下您需要一个空文件。

如果diff_res包含以“”开头的行>,则 f2 至少包含不在 $FILE 中的行。测试一下:

grep -q '^> ' diff_res
test $? -eq 0 && echo "PAT not found"

如果diff_res不包含以“ >”开头的行,则 f2 的所有行都在 $FILE 中,但可能不连续。如果是连续的,diff_res将包含:

  • 不以“”开头的单行<(如果 f1 或 f3 为空),
  • 两行不以“ <”开头,第一行始终以“ 1d”或“1,”开头。

为了测试这一点,我们有:

nb=$(grep -v "^< " diff_res | wc -l)
if test $nb -gt 2; then
  pat_found=0
elif test $nb -eq 1; then
  pat_found=1
else
  pat_found=$(sed -n -e '1{/^1d/p;/^1,/p}' diff_res | wc -l)
fi

然后,如果 pat_found 为 1,则不带 $PAT 的文件是 diff 结果,其中仅包含以“ <”开头且不带这 2 个字符的行:

grep '^< ' diff_res | cut -c 3-

完整且重新组织的脚本如下所示:

# Output the desired result on stdin.

f2=/tmp/f2              # Use of PID or mktmp would be better'
diff_res=/tmp/diff_res  # Use of PID or mktmp would be better'

cat << EOF > $f2
$PAT
EOF

diff $FILE $f2 > $diff_res
if test $? -ne 0; then
  grep -q '^> ' $diff_res
  if test $? -ne 0; then
    nb=$(grep -v "^< " $diff_res | wc -l)
    if test $nb -eq 1; then
      grep '^< ' $diff_res | cut -c 3-
    elif test $nb -eq 2; then
      pat_found=$(sed -n -e '1{/^1d/p;/^1,/p}' $diff_res | wc -l)
      test $pat_found -eq 1 && grep '^< ' $diff_res | cut -c 3-
    fi
  fi
fi

rm -f $f2 $diff_res

答案2

我假设您正在重写一个适合内存的文本文件(看起来您正在重写一个配置文件)。

以下脚本仅使用 shell 内置功能和cat.它应该可以在 Android 的 shell 上运行,至少从 Gingerbread 开始,肯定从 Ice Cream Sandwich 开始。它打印文件内容减去第一次出现的$PATif there is one;如果$PAT没有发生,则不打印任何内容。

contents=$(cat "$FILE")
case $contents in
  *"$PAT"*)
    echo "${contents%%$PAT*}${contents#*$PAT}";;
esac

此代码片段假定文件不包含任何空字节,以单个换行符结尾,并且不以破折号开头。此外,如果模式以换行符结尾,则不会在文件末尾找到它。以下更复杂的代码片段可处理任意文本文件:

contents=$(cat "$FILE"; echo a)
contents=${contents%a}
case $contents in
  *"$PAT"*)
    contents="${contents%%$PAT*}${contents#*$PAT}"
    dashes=${contents%%[!-]*}
    echo -n "$dashes"
    echo -n "${contents#$dashes}";;
esac

(请注意,您提出的行为使得无法区分完全包含模式的文件和空文件。)

实际上,直接实现追加/删除脚本比使用建议的中间函数更容易。

contents=$(cat "$FILE"; echo a)
contents=${contents%a}
append=
case $contents in
  *"$PAT"*) contents="${contents%%$PAT*}${contents#*$PAT}";;
  *) contents="$contents$PAT"
esac
dashes=${contents%%[!-]*}
{ echo -n "$dashes"; echo -n "${contents#$dashes}"; } >"$FILE.new"
mv -- "$FILE.new" "$FILE"

答案3

逐字符读取文件。如果该字符与变量的第一个字符匹配,则比较下一个字符,依此类推。如果整个变量不匹配,则返回。您甚至可以实施更先进的算法为了让它运行得更快,但是由于你的语言恰好是外壳,所以无论如何它都会非常慢。

相关内容