我将直接回答问题的标题,而不是考虑问题本身的细节:
"grep 模式并输出不匹配的部分行"
这个问题的标题对我很重要,因为我正在搜索的模式包含 sed 将赋予特殊含义的字符。我想使用 grep 因为我可以使用 -F 或 --fixed-strings 使 grep 从字面上解释模式。不幸的是,sed 没有文字选项,但是 grep 和 bash 都能够在不考虑任何特殊字符的情况下解释模式。
注意:在我看来,尝试对模式中的特殊字符进行反斜杠或转义在代码中显得很复杂并且不可靠,因为它很难测试。使用旨在搜索文字文本的工具让我有一种舒适的“可行”的感觉,而无需考虑 POSIX。
我同时使用了 grep 和 bash 来生成结果,因为 bash 很慢,而我使用快速 grep 会从大量输入中产生少量输出。此代码搜索文字两次,一次在 grep 期间快速提取匹配行,一次在 =~ 期间从每行中删除匹配项。
while IFS= read -r || [[ -n "$RESULT" ]]; do
if [[ "$REPLY" =~ (.*)("$LITERAL_PATTERN")(.*) ]]; then
printf '%s\n' "${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
else
printf "NOT-REFOUND" # should never happen
exit 1
fi
done < <(grep -F "$LITERAL_PATTERN" < "$INPUT_FILE")
解释:
IFS= 重新分配输入字段分隔符是读取语句的特殊前缀。将 IFS 分配给空字符串会导致 read 接受所有空格和制表符的每一行,直到行尾(假设 IFS 是默认的空格制表符换行符)。
-r 告诉 read 从字面上接受输入流中的反斜杠,而不是将它们视为转义序列的开始。
$REPLY 由read 创建,用于存储输入流中的字符。每行末尾的换行符不会出现在 $REPLY 中。
|| [[ -n "$REPLY" ]] 逻辑 or 导致 while 循环接受不是换行符终止的输入。这不需要存在,因为 grep 总是为每个匹配项提供一个尾随换行符。但是,我习惯性地在我的 read 循环中使用它,因为没有它,最后一个换行符和文件末尾之间的字符将被忽略,因为这会导致 read 即使内容失败已成功读取。
=~ (.*)("$LITERAL_PATTERN")(.*) ]] 是标准的 bash 正则表达式测试,但引号中的任何内容都被视为文字。如果我想让 =~ 考虑 $PATTERN 中包含的正则表达式字符,那么我需要消除双引号。
"${BASH_REMATCH[@]}" 由 [[ =~ ]] 创建,其中 [0] 是整个匹配项,[N] 是第 N 组匹配项的内容括号。
注意:我不喜欢将 stdin 重新分配给 while 循环,因为它很容易出错并且很难看到以后发生的情况。我通常为这种类型的操作创建一个函数,该函数通常会在调用期间执行 file_name 参数或重新分配标准输入。