测试命令是否输出空字符串

377

我该如何测试一个命令是否输出空字符串?


10
一个命令不会返回一个字符串(它返回一个小的整数退出代码,通常情况下成功为0,失败时为1或2),但它可能会输出一些数据,以便放入一个字符串中。 - Basile Starynkevitch
@BasileStarynkevitch 这就是我需要的东西。我知道命令返回退出代码。但我的命令退出代码始终为0,所以我需要控制输出。 - barp
1
你应该阅读一些Bash脚本教程。http://tldp.org/LDP/abs/html/ - Basile Starynkevitch
2
Joey Hess为此编写了一个实用程序ifne。https://joeyh.name/code/moreutils/ - tripleee
13个回答

444

先前的问题是如何检查目录中是否有文件。下面的代码可以实现这一点,但请查看rsp的答案,以获得更好的解决方案。


空输出

命令不会返回值——它们会输出它们。您可以使用命令替换来捕获这个输出;例如$(ls -A)。你可以像这样在Bash中测试非空字符串:

if [[ $(ls -A) ]]; then
    echo "there are files"
else
    echo "no files found"
fi

请注意,我使用的是-A而不是-a,因为它省略了符号当前目录(.)和父级目录(..)的条目。

注意:正如评论中指出的那样,命令替换不会捕获尾随换行符。 因此,如果命令只输出换行符,则替换将捕获到空值,测试将返回false。 虽然这在上面的示例中非常不可能发生,因为单个换行符是有效的文件名! 更多信息请参见此答案


退出代码

如果您想检查命令是否成功完成,可以检查$?,其中包含最后一个命令的退出代码(成功为零,失败为非零)。 例如:

files=$(ls -A)
if [[ $? != 0 ]]; then
    echo "Command failed."
elif [[ $files ]]; then
    echo "Files found."
else
    echo "No files found."
fi

更多信息在这里


5
你可以省略 -n,这样就只需写成 if [[ $(ls -A) ]]; then - user
6
该方法对于只输出换行符的命令会返回错误结果。 - Ciro Santilli OurBigBook.com
如果在if语句下有$(ls -A)的文件,重新运行ls -A是否明智?或者在第一次调用时,我们可以保存其输出并打印它。 - alper

130

简述

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then ...; fi

感谢 netj 的建议改进我的原始代码:
if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi


这是一个老问题,但我看到至少有两个需要改进或至少需要澄清的地方。

第一个问题

我看到的第一个问题是大多数提供的示例根本不起作用。它们使用ls -alls -Al命令 - 这两个命令在空目录中输出非空字符串。这些示例总是报告存在文件,即使没有文件

因此,您应该只使用ls -A - 无论如何,为什么任何人都想使用“-l”开关(表示“使用长列表格式”),当您只想测试是否有输出还是没有呢?

因此,这里的大多数答案都是不正确的。

第二个问题

第二个问题是,虽然一些答案很好用(那些使用ls -alls -Al,而是使用ls -A),但它们都做了类似这样的事情:

  1. 运行一个命令
  2. 在RAM中缓冲其整个输出
  3. 将输出转换为一个巨大的单行字符串
  4. 将该字符串与空字符串进行比较

我建议改为:

  1. 运行一个命令
  2. 计算其输出中的字符数,而不存储它们
    • 或者更好的方法-使用head -c1计算最多1个字符的数量
      (感谢netj在下面的评论中发布此想法)
  3. 将该数字与零进行比较

因此,例如,代替:

if [[ $(ls -A) ]]

我会使用:

if [[ $(ls -A | wc -c) -ne 0 ]]
# or:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]

改为:

if [ -z "$(ls -lA)" ]

我会使用:

if [ $(ls -lA | wc -c) -eq 0 ]
# or:
if [ $(ls -lA | head -c1 | wc -c) -eq 0 ]

等等。

对于小输出,这可能不是问题,但对于大输出,差异可能很大:

$ time [ -z "$(seq 1 10000000)" ]

real    0m2.703s
user    0m2.485s
sys 0m0.347s

与之进行比较:

$ time [ $(seq 1 10000000 | wc -c) -eq 0 ]

real    0m0.128s
user    0m0.081s
sys 0m0.105s

更好的是:
$ time [ $(seq 1 10000000 | head -c1 | wc -c) -eq 0 ]

real    0m0.004s
user    0m0.000s
sys 0m0.007s

完整示例

这是基于Will Vousden的答案更新后的示例:

if [[ $(ls -A | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

在netj的建议下再次更新:

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi
jakeonfire 的额外更新:
如果没有匹配项,grep 将以失败退出。我们可以利用这一点稍微简化语法:
if ls -A | head -c1 | grep -E '.'; then
    echo "there are files"
fi

if ! ls -A | head -c1 | grep -E '.'; then
    echo "no files found"
fi

去除空格

如果你正在测试的命令可能会输出一些你想要将其视为空字符串的空格,那么可以使用以下方法代替:

| wc -c

您可以使用以下方法:

| tr -d ' \n\r\t ' | wc -c

或者使用 head -c1 命令:
| tr -d ' \n\r\t ' | head -c1 | wc -c

摘要

  1. 首先,使用一个可行的命令。

  2. 其次,避免在RAM中存储不必要的数据和处理可能非常庞大的数据。

答案没有指定输出总是很小,因此还需要考虑大量输出的可能性。


5
[[ $(... | head -c | wc -c) -gt 0 ]] 或者 [[ -n $(... | head -c1) ]] 更好,因为 wc -c 需要消耗命令的全部输出。 - netj
@netj 这是一个非常好的想法。请看我的更新答案 - 我加入了你的建议。非常感谢! - rsp
有没有关于如何获取输出的想法? - fahrradflucht
我认为在一般情况下原始文本(不含标题)更好。并不总是一个好主意一开始就终止命令,因为它正在输出! - stk
大多数程序在接收到终止信号后会安全退出。 - cambunctious

55
if [ -z "$(ls -lA)" ]; then
  echo "no files found"
else
  echo "There are files"
fi

这将运行命令并检查返回的输出(字符串)是否具有零长度。您可能想要查看'test'手册页面以获取其他标志。
请在要检查的参数周围使用"",否则空结果将导致语法错误,因为没有给出第二个参数(要检查的参数)!
注意:ls -la始终返回"."和"..",因此使用它将无效,请参见ls手册页面。此外,虽然这似乎很方便和容易,但我认为它很容易出问题。编写一个根据结果返回0或1的小脚本/应用程序会更可靠!

你可能更喜欢使用 $( 而不是反引号,因为 $( 嵌套更好。 - Basile Starynkevitch
你是对的,无论如何使用它们都是更好的实践,尽管在这里我们不需要嵌套。 - Veger

37

对于那些想要一个优雅的、独立于Bash版本的解决方案(实际上应该在其他现代shell中也适用),以及喜欢使用一行代码快速完成任务的人。我们来看看吧!

ls | grep . && echo 'files found' || echo 'files not found'

(正如其中一条评论所提到的,ls -al 和实际上只有 -l-a 都会返回内容,因此在我的回答中我使用简单的 ls


8
如果您使用 grep -q ^,换行符也将被匹配,grep 将不会向标准输出打印任何内容。此外,它会在收到任何输入后立即退出,而不是等待其输入流结束。 - mortehu
如果您想包括点文件(或目录),则应以“ls -A”开头。 - bill.lee

24

Bash参考手册

6.4 Bash条件表达式

-z string
     True if the length of string is zero.

-n string
string
     True if the length of string is non-zero.

您可以使用速记版:

if [[ $(ls -A) ]]; then
  echo "there are files"
else
  echo "no files found"
fi

1
括号内 [[ ... ]] 中缺少 -z 或 -n。 - 71GA
6
或许这很容易被忽略,但如果你仔细看的话,你会发现 string 只是 -n string 的同义词。 - user

17

正如Jon Lin所评论的那样,ls -al命令将始终输出(对于...)。你需要使用ls -Al来避免这两个目录。

例如,你可以将命令的输出放入一个shell变量中:

v=$(ls -Al)

一个较早的、不可嵌套的表示法是

v=`ls -Al`

但我更喜欢可嵌套的符号表示法 $( ... )

然后你就可以测试变量是否非空

if [ -n "$v" ]; then
    echo there are files
else
    echo no files
fi

你可以将两者结合起来,写成if [ -n "$(ls -Al)" ]; then

有时候,ls可能是一些shell 别名。你可能更喜欢使用$(/bin/ls -Al)。请参阅ls(1)hier(7)以及environ(7)和你的~/.bashrc(如果你的shell是GNU bash;我使用的互动式shell是zsh,在/etc/passwd中定义-请参阅passwd(5)chsh(1))。


8

我猜您想要ls -al命令的输出,所以在bash中,您可以使用以下命令:

LS=`ls -la`

if [ -n "$LS" ]; then
  echo "there are files"
else
  echo "no files found"
fi

这样不行:命令“ls”从未被执行。您只检查变量“LS”的扩展是否非空。 - gniourf_gniourf
2
@gniourf_gniourf - 你是怎么想的?反引号咒语确实不如$()那么美观或灵活,但它起作用... - tink
“-a”开关永远不会返回空内容(即使没有文件,它也会返回内容),因为隐式的“.”和“..”目录始终存在。 - bill.lee

4
有时候,"something"可能会输出到测试应用的stderr而不是stdout,因此这里提供的修复方法更为通用。
if [[ $(partprobe ${1} 2>&1 | wc -c) -ne 0 ]]; then
    echo "require fixing GPT parititioning"
else
    echo "no GPT fix necessary"
fi

3
以下是更极端情况的解决方案:
if [ `command | head -c1 | wc -c` -gt 0 ]; then ...; fi

这将适用于所有 Bourne shell;

  • 如果命令输出全是零;
  • 无论输出大小,都能高效运行;

然而,

  • 一旦有任何输出,该命令或其子进程将被终止。

1
这里有一种替代方法,它将某个命令的 std-out 和 std-err 写入临时文件,然后检查该文件是否为空。这种方法的好处是它捕获了两个输出,并且没有使用子 shell 或管道。这些方面很重要,因为它们可能会干扰捕获 bash 退出处理(例如这里)。
tmpfile=$(mktemp)
some-command  &> "$tmpfile"
if [[ $? != 0 ]]; then
    echo "Command failed"
elif [[ -s "$tmpfile" ]]; then
    echo "Command generated output"
else
    echo "Command has no output"
fi
rm -f "$tmpfile"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接