【问题标题】:grep a pattern and output non-matching part of linegrep 模式并输出行的不匹配部分
【发布时间】:2011-09-19 02:16:24
【问题描述】:

我知道可以使用 -v 标志反转 grep 输出。有没有办法只输出匹配行的不匹配部分?我问是因为我想使用 grep 的返回码(sed 没有)。这是我所拥有的:

tags=$(grep "^$PAT" >/dev/null 2>&1)
[ "$?" -eq 0 ] && echo $tags 

【问题讨论】:

  • 就像-o 的反面一样?
  • @Steve Prentice 是的,类似的东西。出于我的目的,我只需要该行的其余部分。

标签: bash shell unix grep


【解决方案1】:

你可以使用sed:

$ sed -n "/$PAT/s/$PAT//p" $file

唯一的问题是,只要模式是好的,即使找不到模式,它也会返回退出代码 0。


说明

-n 参数告诉sed 不要打印任何行。 Sed 的默认设置是打印出文件的所有行。让我们看看斜线之间的sed 程序的每个部分。假设程序是/1/2/3/4/5

  1. /$PAT/:这表示要查找与模式 $PAT 匹配的所有行以运行您的 substitution 命令。否则,sed 将在所有行上运行,即使没有替换。
  2. /s/:这表示你将进行替换
  3. /$PAT/:这是您将要替换的模式。这是$PAT。因此,您正在搜索包含 $PAT 的行,然后您将用该模式替换某些内容。
  4. //:这就是您要替换 $PAT 的内容。它是空的。因此,您要从该行中删除 $PAT
  5. /p:这个最后的p 说要打印出这一行。

因此:

  • 您告诉sed 在处理文件时不要打印出文件的行。
  • 您正在搜索包含$PAT 的所有行。
  • 在这些行中,您使用s 命令(替换)来删除模式。
  • 从线条上移除图案后,您将打印出线条。

【讨论】:

  • 谢谢,但如果我能得到退出代码就更好了。我猜 sed 从来没有实现过退出代码,因为它真的没有意义。
  • @Dennis Hodapp:Sed 不会为您提供除零以外的退出状态,因为即使它没有编辑任何内容,它也可以工作。您可以简单地执行if [ -z $tags ] 来测试是否设置了$tag。或者,您可以通过sed 管道grep,然后通过$PIPESTATUS 环境变量获取grep 的退出状态,因为grep 可能会找到一些东西,但不会设置任何标记。如果我知道您需要做什么,我可以提供更多信息。在您的原始帖子中,grep 在命令行上没有文件名。 grep 的输入是否取自 STDOUT?
  • 我的问题不是很好,我抄错了。我忘了添加输入文件,它是一个多行文件。您的回答(附评论)也正是我的想法。不过,我不能将答案授予 2 个人。
  • 如果您能解释一下上述 sed 语法的一般形式,那就太好了。即,/$pat/s/$pat//p 在 sed-speak 中到底是什么意思?该语法的应用如何解决 OP 的问题?
  • 问,你就会收到。见上面的解释。
【解决方案2】:

如何使用grepsed$PIPESTATUS 的组合来获得正确的退出状态?

$ echo Humans are not proud of their ancestors, and rarely invite
  them round to dinner | grep dinner | sed -n "/dinner/s/dinner//p"
Humans are not proud of their ancestors, and rarely invite them round to 

$ echo $PIPESTATUS[1]
0[1]

$PIPESTATUS 数组的成员保存在管道中执行的每个相应命令的退出状态。 $PIPESTATUS[0] 保存管道中第一个命令的退出状态,$PIPESTATUS[1] 保存第二个命令的退出状态,依此类推。

【讨论】:

  • 这很酷。我不知道 $PIPESTATUS。以下所有答案都很好,但这最接近我想要的。
【解决方案3】:

您的 $tags 永远不会有值,因为您将它发送到 /dev/null。除了那个小问题之外,grep 没有任何输入。

echo hello |grep "^he" -q ; 
ret=$? ; 
if [ $ret -eq 0 ]; 
then 
echo there is he in hello; 
fi

成功返回码为0。

...这里是您的“问题”之一:

pat="most of "; 
data="The apples are ripe. I will use most of them for jam.";  
echo $data |grep "$pat" -q; 
ret=$?; 
[ $ret -eq 0 ] && echo $data |sed "s/$pat//"
The apples are ripe. I will use them for jam.

...完全一样的东西?:

echo The apples are ripe. I will use most of them for jam. | sed ' s/most\ of\ //'

在我看来,您混淆了基本概念。你到底想做什么?

【讨论】:

  • 是的,这对我来说很糟糕。我认为我错误地复制了它,但这无关紧要。重点是使用 grep 来查找模式。我想要返回码,然后是行中不匹配的部分。当我更多地查看它时,除了尝试编写更少的代码(一个 grep 命令)之外,真的没有任何意义。上面的 Fredrik 似乎以一种有趣的方式解决了我的问题,但你的也有效。
【解决方案4】:

我将直接回答问题的标题,而不是考虑问题本身的细节:

"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 从字面上接受输入流中的反斜杠,而不是将它们视为转义序列的开始。

$REPLYread 创建,用于存储输入流中的字符。每行末尾的换行符不会出现在 $REPLY 中。

|| [[ -n "$REPLY" ]] 逻辑 or 导致 while 循环接受不是换行符终止的输入。这不需要存在,因为 grep 总是为每个匹配项提供一个尾随换行符。但是,我习惯性地在我的 read 循环中使用它,因为没有它,最后一个换行符和文件末尾之间的字符将被忽略,因为这会导致 read 即使内容失败已成功读取。

=~ (.*)("$LITERAL_PATTERN")(.*) ]] 是标准的 bash 正则表达式测试,但引号中的任何内容都被视为文字。如果我想让 =~ 考虑 $PATTERN 中包含的正则表达式字符,那么我需要消除双引号。

"${BASH_REMATCH[@]}" 由 [[ =~ ]] 创建,其中 [0] 是整个匹配项,[N] 是第 N 组匹配项的内容括号。

注意:我不喜欢将 stdin 重新分配给 while 循环,因为它很容易出错并且很难看到以后发生的情况。我通常为这种类型的操作创建一个函数,该函数通常会在调用期间执行 file_name 参数或重新分配标准输入。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-07
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 2010-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多