当我们在编写 Shell 脚本时,有时会遇到类似这样的错误和提示:

1
2
3
4
5
6
a=1
if$a == 1 】; then
echo "yes"
fi

>>> bash: 【: 未找到命令...

由此可知在 Shell 脚本中,用作判断的语句是属于“命令”的。我们回忆一下上例的正确形式:

1
2
3
4
a=1
if [ $a == 1 ]; then
echo "yes"
fi

那么[是命令吗?我们可以测试一下:

1
2
3
4
[user@localhost ~]$ which [
/usr/bin/[
[user@localhost ~]$ type [
[ 是 shell 内建

可见[的确是个命令(而且它还拥有自己的 man 手册)。于是bash语法要求方括号和括号中的语句必须用空格隔开等奇怪的特性就都有了解释。

它和条件分支语句配合生效的方式,我猜测是通过程序返回值实现的。于是可以设计如下程序进行测试:

1
2
3
4
5
if $@; then
echo "success"
else
echo "failed"
fi

运行脚本,得到:

1
2
3
4
5
[user@localhost ~]$ bash ifTest.sh ls /abc
ls: 无法访问 '/abc': 没有那个文件或目录
failed
[user@localhost ~]$ bash ifTest.sh ls /opt
successful

由此可以猜想:

  1. bash 的条件分支语句确实是通过命令的返回值运行的
  2. 条件分支运行时会将命令执行一遍
  3. 当命令返回 0 时判断为真,返回其他值时判断为假

为了验证猜想,可以编写类似这样的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getTrue() {
return 0
}
function getFalse() {
return 1
}

if getTrue; then
echo "getTrue OK"
fi

if ! getFalse; then
echo "getFalse OK"
fi

通过运行此脚本,可见我们的猜想大体是正确的。

事实上在Linux操作系统中存在类似我们刚刚定义的“getTrue()”和“getFalse()”方法的命令,那就是truefalse

1
2
3
4
[user@localhost ~]$ which true
/usr/bin/true
[user@localhost ~]$ which false
/usr/bin/false

而且它们也拥有自己的 man 手册。这两个命令的功能非常简单,所以我猜测它们的源代码也不复杂。于是我决定试试找到它们的源代码读一读。

首先搜索它们所属的软件包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[user@localhost ~]$ yum whatprovides 'true'
上次元数据过期检查:1:00:35 前,执行于 2024年03月23日 星期六 15时52分33秒。
coreutils-8.32-31.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-8.32-32.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-8.32-33.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-8.32-34.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-8.32-35.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
仓库 :@System
匹配来源:
文件名 :/usr/bin/true

coreutils-8.32-35.el9.x86_64 : A set of basic GNU tools commonly used in shell scripts
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-single-8.32-31.el9.x86_64 : coreutils multicall binary
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-single-8.32-32.el9.x86_64 : coreutils multicall binary
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-single-8.32-33.el9.x86_64 : coreutils multicall binary
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-single-8.32-34.el9.x86_64 : coreutils multicall binary
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

coreutils-single-8.32-35.el9.x86_64 : coreutils multicall binary
仓库 :baseos
匹配来源:
文件名 :/usr/bin/true

然后在 github 中搜索 coreutils 仓库,然后我在 https://github.com/coreutils/coreutils/blob/master/src/true.c 看到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* Exit with a status code indicating success.
Copyright (C) 1999-2024 Free Software Foundation, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */

#include <config.h>
#include <stdio.h>
#include <sys/types.h>
#include "system.h"

/* Act like "true" by default; false.c overrides this. */
#ifndef EXIT_STATUS
# define EXIT_STATUS EXIT_SUCCESS
#endif

#if EXIT_STATUS == EXIT_SUCCESS
# define PROGRAM_NAME "true"
#else
# define PROGRAM_NAME "false"
#endif

#define AUTHORS proper_name ("Jim Meyering")

void
usage (int status)
{
printf (_("\
Usage: %s [ignored command line arguments]\n\
or: %s OPTION\n\
"),
program_name, program_name);
printf ("%s\n\n",
_(EXIT_STATUS == EXIT_SUCCESS
? N_("Exit with a status code indicating success.")
: N_("Exit with a status code indicating failure.")));
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
emit_ancillary_info (PROGRAM_NAME);
exit (status);
}

int
main (int argc, char **argv)
{
/* Recognize --help or --version only if it's the only command-line
argument. */
if (argc == 2)
{
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);

/* Note true(1) will return EXIT_FAILURE in the
edge case where writes fail with GNU specific options. */
atexit (close_stdout);

if (STREQ (argv[1], "--help"))
usage (EXIT_STATUS);

if (STREQ (argv[1], "--version"))
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
(char *) nullptr);
}

return EXIT_STATUS;
}

和我想的有些出入,看起来它甚至支持像--help--version这样的选项(使用绝对位置调用这个命令后就能使用这两个选项)。有些难以想象bash脚本每判断一次true都得走一遍这百八十行的程序。

但我仍不死心,于是找到这个 true.c 最初的版本,发现在1999年的8月1号,Jim Meyering 提交了这样的代码:

1
2
3
4
5
int
main ()
{
exit (0);
}

这就和我想的差不多了。