Contents

Linux中的/bin/true和/bin/false命令

1. 概述

在 Linux 中,有一些简单的命令。通常,我们知道命令的作用。但是,当我们更进一步时,有时我们无法真正说出这些命令的用例。

/bin/true/bin/false 命令是两个示例。

在本教程中,让我们仔细看看这两个非常简单的命令。

2. /bin/true和*/bin/false*介绍

/bin/true和*/bin/false都是Coreutil 包的成员,它在几乎所有 Linux 发行版上都可用。值得一提的是,*某些发行版可能会在/usr/bin而不是*/bin*下安装命令**。

如果我们查看这两个命令的手册页,描述非常简单:

  • /bin/true - “什么都不做,成功”
  • /bin/false – “什么都不做,不成功”

如果我们执行这些命令,它们实际上“什么都不做”,除了*/bin/true命令返回 0,而*/bin/false命令返回 1:

$ /bin/false
$ echo $?
0
$ /bin/false
$ echo $?
1

好吧,我们已经学会了这两个简单的命令。但是,我们可能会问,既然它们什么都不做,为什么我们需要这两个命令呢?他们的用例是什么?

接下来,让我们看一些使用*/bin/true*和 /bin/false命令的实际示例。

3. /bin/false的用法示例

首先,让我们看一下*/bin/false*命令。

我们已经了解到*/bin/false*命令除了返回一个失败代码之外什么都不做。它最典型的用法是拒绝对特定用户帐户的 shell 访问

接下来,让我们检查一下这台机器上哪些用户的 shell 设置为*/bin/false*:

$ grep 'false' /etc/passwd
bin:x:1:1:bin:/bin:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false
mail:x:8:12:mail:/var/spool/mail:/bin/false
ftp:x:14:11:ftp:/srv/ftp:/bin/false
http:x:33:33:http:/srv/http:/bin/false
nobody:x:99:99:nobody:/:/bin/false
rtkit:x:133:133:RealtimeKit:/proc:/bin/false
ntp:x:87:87:Network Time Protocol:/var/lib/ntp:/bin/false

当上面列表中的用户尝试通过 shell 登录时,/bin/false返回 1。换句话说,它拒绝用户的 shell 访问。

现在,我们以用户nobody 为例进行测试:

root# su nobody
root# echo $?
1

由于我们不知道用户nobody的密码,所以我们以root用户身份登录,然后尝试更改为nobody 用户。

如上面的输出所示,执行“ *su nobody”*命令后,当前用户仍然是 root。此外,“ *su nobody”*命令失败,返回码为 1。

也就是说,“切换用户”操作失败。

接下来,让我们看看*/bin/true*的一些示例用法。

4. 使用*/bin/true*构建无限循环

由于*/bin/true*命令总是返回 0,我们可以用它来写一个无限循环:

while /bin/true
do
   some-operations
   ...
   # We need to break out of the loop, otherwise the script will hang.
done

5. 使操作始终成功

有时,当某些命令的执行给出非零返回码时,/bin/true命令允许我们将其视为成功

嗯,听起来有点奇怪。那么接下来,让我们看一个例子。它可以快速解释这种情况。

5.1. executor.sh脚本

首先,让我们看一个简单的 shell 脚本:

$ cat executor.sh
#!/bin/bash
#Arguments
OPERATION="$1"
INPUT="$2"
execute() {
    echo "Start executing operation: [$1]"
    $1 && echo "Success! Logging status in the Execution-Database ..." \
        || echo "Failed! Logging and sending alert messages ..."
}
# predefined operations
findErrors() {
    grep -i 'error' "$INPUT"
}
# other opertions...
execute "$OPERATION"

如上面的代码所示,executor.sh脚本允许我们使用输入执行预定义的操作。在这个例子中,我们只定义了一个操作—— findErrors

executor函数负责执行给定的操作并在之后做一些进一步的常见工作,例如记录执行状态、错误处理以及可能的许多其他工作。

值得一提的是,为简单起见,我们在脚本中跳过了参数验证。

5.2. 在两个输入文件上执行findErrors

假设我们有两个日志文件:

$ head *.log
==> log1.log <==
2021-11-22 11:23:22 [INFO] application starts successfully
2021-11-22 11:24:22 [INFO] clean-up job starts..
2021-11-22 11:24:52 [INFO] clean-up job done!
==> log2.log <==
2021-11-28 18:23:00 [ERROR] IO: File "foo/bar" not found!
2021-11-28 18:24:00 [INFO] User has changed profile.
2021-11-28 18:25:00 [ERROR] DB: Cannot exec SQL Statement "insert into ..."

首先,我们 对log2.log文件执行findErrors操作 :

$ ./executor.sh findErrors log2.log
Start executing operation: [findErrors]
2021-11-28 18:23:00 [ERROR] IO: File "foo/bar" not found!
2021-11-28 18:25:00 [ERROR] DB: Cannot exec SQL Statement "insert into ..."
Success! Logging status in the Execution-Database ...

上面的输出表明执行器已经成功执行了findErrors操作。 接下来,让我们对另一个日志文件做同样的事情:

$ ./executor.sh findErrors log1.log
Start executing operation: [findErrors]
Failed! Logging and sending alert messages ...

这次执行器报告操作失败,程序进入错误处理流程。

这是因为log1.log文件不包含任何“ ERROR ”条目。因此,当 grep没有找到结果时,  grep命令返回代码 1 而不是 0

因此,当执行器收到返回码1时,就认为操作失败了。

实际上,这并不是我们真正想要的。即使findErrors操作找不到匹配项,该操作的执行也应该是成功的。 那么,让我们看看如何修复它。

5.3. 解决问题

现在,让我们使用*/bin/true*命令来修复它:

#!/bin/bash
...
 
findErrors() {
    grep -i 'error' "$INPUT"; /bin/true
}
...

我们可以 在grep命令之后用分号连接 /bin/true命令。这样,无论grep是否在输入中找到匹配项,我们都要求findErrors操作始终返回 0 。 让我们再次使用log1.log文件测试findErrors操作:

$ ./executor.sh findErrors log1.log
Start executing operation: [findErrors]
Success! Logging status in the Execution-Database ...

可以看到,仍然没有找到“ ERROR ”,但是操作执行成功了。

现在,程序按预期运行。

6. Shell 内置程序和 Coreutils 包

6.1. Shell内置真假

我们通过示例学习了*/bin/true/bin/false命令。*

敏锐的眼睛可能已经发现到目前为止我们一直在使用*/bin/true而不是true*,尽管*/bin*通常位于 $PATH 系统变量中。

这是因为Coreutils 中的*/bin/true/bin/false在 Linux 上有兄弟。他们的兄弟是shell内置的真假*命令。

我们可以使用 type -a命令来列出它们,例如:

$ type -a true
true is a shell builtin
true is /bin/true

所以,正如我们在上面的输出中看到的,我们在这个系统上有两个真正的命令。一个是 shell (Bash) 内置命令,另一个是 Coreutils 包中的*/bin/true*命令。

在本教程的这一部分,我们将重点关注Coreutils包中的真假命令。因此,在前面的部分中,我们使用*/bin/true/bin/false*。

如果我们只是在 shell 中执行true,我们将执行 shell 内置的true

内置的truefalse具有与其对应的 Coreutils 命令相同的功能。

6.2. Bash 内置true与*/bin/true*

我们已经了解到,*真假 *基本上什么都不做。换句话说,它们应该非常快。

接下来,让我们做一个有趣的测试:对 Bash 内置true和 Coreutils true进行基准测试。

我们将运行每个真正的命令 10,000 次,并使用time 命令来测量执行所需的时间。

首先,让我们测量内置的 Bash true

$ time for i in {1..10000}; do true; done
real	0m0.015s
user	0m0.015s
sys	0m0.000s

一下子就结束了!接下来,让我们测试一下*/bin/true*命令:

$ time for i in {1..10000}; do /bin/true; done
real	0m12.552s
user	0m10.863s
sys	0m10.013s

这一次,我们等待了大约 13 秒才能看到输出。

所以,内置的 Bash true比 Coreutils 版本快得多

例如,在这台机器上,内置的 Bash true比 Coreutils 版本快大约 836 (12.552/0.015) 倍。多么惊喜!

6.3. 为什么我们需要Coreutils 包中的*/bin/true/bin/false* ?

我们了解到 Bash 内置true和 false和 Coreutils 版本具有相同的功能。此外,内置的 shell 命令比它们的 Coreutils 版本快得多。

那么,我们可能会问,为什么我们还需要外部命令*/bin/true/bin/false*?

答案是因为内置的 shell 命令只存在于 shell 中。因此,只有当我们从 shell 调用它们时它们才可用。

在前面的示例中,我们展示了在*/etc/passwd文件中使用/bin/false来防止用户访问 shell。这也是不从 shell调用/bin/false*的一个很好的例子。

现在,让我们将用户guest的 shell 更改为 false  ,看看当我们切换到guest时会发生什么:

$ grep '^guest' /etc/passwd
guest:x:1001:1001::/home/guest:false
$ su guest
Password: 
su: failed to execute false: No such file or directory

上面的输出显示 su命令失败,因为它找不到文件或目录“false”。这是因为它调用了false命令,而不是从 shell 调用。

因此,此时所有内置的 shell 命令都不可用。