防止grep在输入不匹配时返回错误


73

我想在bash脚本中编写一段代码,检查程序是否已在运行。我有以下内容以便搜索栏是否正在运行

 foo=`ps -ef | grep bar | grep -v grep`

 grep -v grep

部分是确保在ps结果中不考虑“ grep bar”

如果bar没有运行,则foo正确为空。但是我的问题在于该脚本具有

 set -e

如果某个命令返回错误,则该标志是终止脚本的标志。事实证明,当bar不运行时,“ grep -v grep”与任何内容都不匹配,并且grep返回错误。我尝试使用-q或-s但无济于事。

有什么解决办法吗?谢谢


请注意,这set -e不是bash特定的,而是适用于任何POSIX兼容的外壳(sh等)
myrdd '18

Answers:


72

当然:

ps -ef | grep bar | { grep -v grep || true; }

甚至:

ps -ef | grep bar | grep -v grep | cat

8
第一个更好的地方是它还可以与“ -o pipefail”一起使用,这是bash的另一个很好的快速失败设置,类似于“ -e”。
JakaJančar'16

5
如果grep由于没有匹配项而返回1,则效果很好,但是如果返回2(例如错误),会发生什么呢?
安德烈FRATELLI

3
正如AndréFratelli所说,使用会grep $options || true忽略实际错误!我提出了一个新的答案:stackoverflow.com/a/49627999/307637
myrdd

观察:使用“ ... | grep -v grep | cat”的方法不会阻止脚本(如果有的话)失败set -o pipefail,尽管它可以满足要求set -e。“ ... | { grep -v grep || true; }”方法适用于set -o pipefail,但要获得更清洁的解决方案(专门测试是否缺少grep匹配项),请参阅@myrdd的答案。
Trutane

19

简短答案

ps -ef | grep bar | { grep -v grep || test $? = 1; }

如果您正在使用set -e

如果使用bash的pipefail选项(set -o pipefail),请记住将异常处理(||test)应用于grep管道中的每个对象:

ps -ef | { grep bar || test $? = 1; } | { grep -v grep || test $? = 1; }

外壳程序脚本中,我建议您使用“ catch-1-grep”(c1grep)实用程序功能:

c1grep() { grep "$@" || test $? = 1; }

解释

grep的退出状态为0、1或2:[1]

  • 0 表示选择了一条线
  • 1 表示未选择任何行
  • 2 表示发生错误

grep如果被信号中断(例如130SIGINT),它也可以返回其他代码。

由于我们只想忽略退出状态1,因此我们使用test了抑制特定退出状态的功能。

  • 如果grep返回0test则不运行。
  • 如果grep返回1test则运行并返回0
  • 如果grep返回任何其他值,test则运行并返回1

在最后一种情况下,脚本将由于set -e或立即退出set -o pipefail。但是,如果您根本不关心grep错误,那么您当然可以编写

ps -ef | grep bar | { grep -v grep || true; }

正如肖恩建议的。


Shell脚本中的[additional]用法

在shell脚本中,如果您使用grep很多,建议您定义一个实用程序函数:

# "catch exit status 1" grep wrapper
c1grep() { grep "$@" || test $? = 1; }

这样,您的管道将再次变得简短而简单,而不会丢失set -eand的功能set -o pipefail

ps -ef | c1grep bar | c1grep -v grep

仅供参考:

  • 我称其c1grep为强调仅是捕获退出状态1,仅此而已。
  • 我本可以调用函数grepgrep() { env grep "$@" ...; }),但我更喜欢使用一个不太混乱和更明确的名称c1grep

[其他] ps+grep

所以,如果你想知道如何避免grep -v grep甚至| grep部分ps|grep可言,看看一些其他的答案; 但这有点离题了。


[1]grep联机帮助页


15

一个好的避免方法grep -v grep是:

ps -ef | grep '[b]ar'

该正则表达式仅匹配字符串“ bar”。但是,在ps输出中,字符串“ bar”不会出现在grep进程中。


在了解之前pgrep,我编写了此函数来自动执行上述命令:

psg () { 
    local -a patterns=()
    (( $# == 0 )) && set -- $USER
    for arg do
        patterns+=("-e" "[${arg:0:1}]${arg:1}")
    done
    ps -ef | grep "${patterns[@]}"
}

然后,

psg foo bar

变成

ps -ef | grep -e '[f]oo' -e '[b]ar'

嘿,我确实做到了。它起作用是因为表达式确实匹配bar,匹配b.ar
DigitalRoss

5
@ grok12,将在ps输出中显示,grep [b]ar并且正则表达式 [b]ar无法与字符串 匹配[b]ar-正则表达式将恰好匹配3个字符,而字符串包含5个字符
glenn jackman 2011年

10

如果只丢弃99%的ps输出,为什么要提供大量输出-ef呢?ps特别是GNU版本是功能强大的瑞士军刀。尝试这个:

ps -C bar -o pid= 1>/dev/null

-o pid=在这里指定的原因仅在于,但实际上,由于我们将所有stdout都丢弃了,所以这毫无意义。但是,如果您想知道实际运行的PID,这将很有用。

ps如果-C无法匹配任何内容,则自动返回状态为非零;如果匹配失败,则返回零。所以你可以简单地说

ps -C bar 1>/dev/null && echo bar running || echo bar not running

要么

if ps -C bar 1>/dev/null ; then
    echo bar running
else
    echo bar not running
fi

那不是更简单吗?不需要grep,不需要两次甚至一次。


您在这里回答的问题略有不同。最主要的一点是,grep它可以与任何输入一起使用,不仅限于ps
维克多·亚雷玛


1

尝试这样做:

ps auxw | grep -v grep | 猫

cat始终返回0,并忽略grep的退出代码


1
忽略所有退出代码不是一个好主意,因为除了not found一个错误外,还会有其他错误。在由set -eset -o pipefail命令引起的模式的情况下,该解决方案也将失败。
维克多·亚雷玛
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.