检查命令的输出是不是包含 shell 脚本中的某个字符串

Posted

技术标签:

【中文标题】检查命令的输出是不是包含 shell 脚本中的某个字符串【英文标题】:Checking if output of a command contains a certain string in a shell script检查命令的输出是否包含 shell 脚本中的某个字符串 【发布时间】:2013-05-31 15:22:32 【问题描述】:

我正在编写一个 shell 脚本,我正在尝试检查命令的输出是否包含某个字符串。我想我可能必须使用 grep,但我不确定如何。有人知道吗?

【问题讨论】:

生成你要找的输出字符串后,命令是否需要继续运行,或者可以在那个时候立即关闭? (您的两个答案在这方面的语义不同)。 【参考方案1】:

测试grep的返回值:

./somecommand | grep 'string' &> /dev/null
if [ $? == 0 ]; then
   echo "matched"
fi

惯用的做法是这样的:

if ./somecommand | grep -q 'string'; then
   echo "matched"
fi

还有:

./somecommand | grep -q 'string' && echo 'matched'

【讨论】:

此代码不适用于所有 POSIX shell:POSIX 标准只要求 = 是比较运算符,而不是 ==;见pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html 另外,grep 'string' &>/dev/null 既不符合 POSIX 标准,而且执行起来比grep -q string 慢得多(如果string 出现在长输出流的早期)。 [需要注意的是,如果您想确保somecommand 即使在发出string 之后也能继续运行,在这种情况下使用grep -q——通过关闭其标准输入并在看到string 的第一个实例后退出——可能适得其反]。 (回复:“非 POSIX 兼容”,&> 是一个扩展 - 请参阅描述 POSIX 强制重定向支持的 pubs.opengroup.org/onlinepubs/009695399/utilities/…)。 如果能解释为什么会起作用/每个参数的作用会有所帮助,以鼓励对语法的充分理解 另见Why is testing “$?” to see if a command succeeded or not, an anti-pattern? 它可以打印结果而不是matched吗?【参考方案2】:

测试$? 是an anti-pattern.

if ./somecommand | grep -q 'string'; then
  echo "matched"
fi

【讨论】:

如果您只想测试固定字符串,请添加 Fx 选项:grep -Fxq F 代表固定(未解释),x 代表整行 为什么测试$? 是反模式? @VitalyZdanevich 我认为是因为它对并发性不强。 @VitalyZdanevich,例如,为了 set -eERR 陷阱,测试 $? 不会将前面的命令设置为“已检查”,因此您的程序可以在以下情况下退出您希望它稍后简单地返回故意错误的路径。另一方面,$? 是易变的全局状态——很容易意外丢弃它的值。比如添加echo "Exit status is $?"这样的一行日志,$?中的新值变成echo的退出状态。【参考方案3】:

另一个选项是检查命令输出中的正则表达式匹配。

例如:

[[ "$(./somecommand)" =~ "sub string" ]] && echo "Output includes 'sub string'"

【讨论】:

【参考方案4】:

一个干净的 if/else 条件 shell 脚本:

if ./somecommand | grep -q 'some_string'; then
  echo "exists"
else
  echo "doesn't exist"
fi

【讨论】:

【参考方案5】:

简短回答

以上所有(非常优秀的)答案都假设grep 可以“看到”命令的输出,这并不总是正确的:

SUCCESS 可以发送到 STDOUTFAILURE 可以发送到 STDERR.

因此,根据您测试的方向,您的grep 可能会失败。也就是说,如果您正在测试 FAILURE 的情况,您必须在这种情况下使用2>&1 将命令的输出重定向到 STDOUT。

带证明的更长答案

我使用grep 在 bash 脚本中进行了我认为非常简单的测试,但它一直失败。随之而来的是许多挠头。在我的脚本中使用set -x 表明该变量为空!所以我创建了以下测试来了解事情是如何破坏的。

注意iscsiadm 是来自“open-iscsi”软件包的 Linux 工具,用于将主机连接/断开连接到 SAN贮存。命令iscsiadm -m session 用于显示是否建立了任何LUN 连接):

#!/bin/bash

set -x

TEST1=$(iscsiadm -m session)
TEST2=$(iscsiadm -m session 2>&1)
echo
echo 'Print TEST1'
echo $TEST1
echo
echo 'Print TEST2'
echo $TEST2
echo

如果 LUN WAS 已连接,则这两个变量都已成功填充值:

Print TEST1
tcp: [25] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:ipdisk.Target-LUN1 (non-flash) tcp: [26] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:storagehost.Target-LUN1 (non-flash)

Print TEST2
tcp: [25] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:ipdisk.Target-LUN1 (non-flash) tcp: [26] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:storagehost.Target-LUN1 (non-flash)

但是,如果 LUNWASN'T 连接,iscsiadm 将输出发送到 STDERR,并且只有“TEST2”变量填充到我们重定向到的位置标准输出使用2>&1;没有重定向到 STDOUT 的“TEST1”变量为空:

iscsiadm: No active sessions.

Print TEST1


Print TEST2
iscsiadm: No active sessions.

结论

如果你有一个时髦的、半断的——在一个方向工作而不是另一个——这样的情况,试试上面的测试用你自己的命令替换iscsiadm,你应该得到重写测试以使其正常工作的适当可见性。

【讨论】:

那是我需要的缺失部分。像 resize2fs 这样的命令需要它 (2>&1)。感谢您的澄清! @FalloutBoy 没有问题;很高兴能帮助你。这个网站为我节省了大量浪费的周期,所以我们都花点时间回馈一些东西似乎是对的-

以上是关于检查命令的输出是不是包含 shell 脚本中的某个字符串的主要内容,如果未能解决你的问题,请参考以下文章

使用shell脚本检查“diff”命令的输出

如何在脚本本身内重定向整个 shell 脚本的输出?

shell脚本判断字符串是不是包含某个字符

Shell脚本判断文件是不是存在

如何从 shell/shell 脚本获取 sqlplus 命令的输出状态?

Shell脚本的调试方法