目录
五.ECHO_OUTPUT_VARIABLE, ECHO_ERROR_VARIABLE
execute_process
在 CMake 配置项目时(即在生成构建系统之前)运行命令。使用 add_custom_target()
和 add_custom_command()
命令来创建在构建时运行的自定义命令。
一.execute_process()
1.1.简要介绍
我们可以去官网看看是怎么说这个命令的:execute_process — CMake 4.1.1 Documentation
execute_process
执行一个或多个子进程。
语法:
execute_process(COMMAND <cmd1> [<arguments>]
[COMMAND <cmd2> [<arguments>]]...
[WORKING_DIRECTORY <directory>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <variable>]
[RESULTS_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>]
[ERROR_VARIABLE <variable>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[COMMAND_ECHO <where>]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[ENCODING <name>]
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
[COMMAND_ERROR_IS_FATAL <ANY|LAST|NONE>])
运行给定的一系列一个或多个命令。
命令作为管道并发执行,每个进程的标准输出会管道传输到下一个进程的标准输入。所有进程共享一个标准错误管道。
execute_process
在 CMake 配置项目时(即在生成构建系统之前)运行命令。使用 add_custom_target()
和 add_custom_command()
命令来创建在构建时运行的自定义命令。
我们将选项分组进行讲解,以便理解。
1. 定义要执行的命令
-
COMMAND <cmd1> [<arguments>] [COMMAND <cmd2> [<arguments>]]...
-
这是唯一必需的参数。指定要运行的一个或多个命令及其参数。
-
多个命令:如果指定了多个
COMMAND
,它们会以管道(pipe) 的形式并发启动。即第一个命令的标准输出(stdout)会自动成为第二个命令的标准输入(stdin),以此类推。所有命令共享同一个标准错误(stderr)管道。 -
Shell 操作符:重要! 该命令直接调用可执行文件,不通过系统 Shell(如
bash
或cmd.exe
)。这意味着你不能直接使用 Shell 的操作符,如>
(重定向)、|
(管道)、&&
(逻辑与)、&
(后台执行)等。这些字符会被当作普通的参数传递给命令。 -
Windows 脚本 (.bat, .cmd):在 Windows 上,为了正确执行批处理脚本,CMake 会自动在命令前加上
cmd /c call
。这是为了确保脚本执行后能正确返回到 CMake 进程。
-
2. 控制执行环境
-
WORKING_DIRECTORY <directory>
-
设置子进程的当前工作目录。命令将在指定的
<directory>
中执行。
-
-
TIMEOUT <seconds>
-
设置超时时间(秒,可为小数)。如果所有子进程在指定时间内未完成,它们将被强制终止。
-
执行结果(
RESULT_VARIABLE
)将被设置为字符串"timeout"
。
-
3. 捕获执行结果
-
RESULT_VARIABLE <variable>
-
将最后一个子进程的退出码或错误信息存储到指定的变量
<variable>
中。 -
通常,
0
表示成功,非0
表示错误。也可能是描述错误的字符串(如"timeout"
,"no such file"
)。
-
-
RESULTS_VARIABLE <variable>
(CMake 3.10+)-
存储所有子进程的结果到一个列表中(分号分隔)。列表中的顺序与
COMMAND
参数的顺序一致。 -
例如,有两个命令,
RESULTS_VARIABLE results
,那么results
可能是"0;1"
,表示第一个命令成功,第二个命令失败。
-
4. 重定向输入、输出和错误
这部分选项提供了类似 Shell 的重定向功能。
-
INPUT_FILE <file>
-
将指定文件的内容作为第一个命令的标准输入。
-
-
OUTPUT_FILE <file>
-
将最后一个命令的标准输出重定向到指定文件。
-
-
ERROR_FILE <file>
-
将所有命令的标准错误重定向到指定文件。
-
-
OUTPUT_VARIABLE <variable>
-
将最后一个命令的标准输出内容捕获到指定的 CMake 变量中。
-
-
ERROR_VARIABLE <variable>
-
将所有命令的标准错误内容捕获到指定的 CMake 变量中。
-
注意:如果
OUTPUT_VARIABLE
和ERROR_VARIABLE
使用同一个变量名,那么输出和错误将会被合并到该变量中。
-
-
OUTPUT_QUIET,
ERROR_QUIET
-
静默模式。分别禁止将输出或错误捕获到
OUTPUT_VARIABLE
/ERROR_VARIABLE
。文件重定向(*_FILE
)不受影响。
-
-
ECHO_OUTPUT_VARIABLE,
ECHO_ERROR_VARIABLE
(CMake 3.18+)-
类似于 Unix 的
tee
命令。既将输出/错误内容捕获到变量中,同时也将其打印到 CMake 的标准输出/错误(即终端上你会看到输出)。非常利于调试。
-
-
OUTPUT_STRIP_TRAILING_WHITESPACE,
ERROR_STRIP_TRAILING_WHITESPACE
-
自动移除捕获到的输出/错误字符串末尾的空白字符(如换行符、空格)。这在捕获单行输出(如版本号、路径)时非常有用。
-
5. 调试与高级控制
-
COMMAND_ECHO <where>
(CMake 3.15+)-
将正在执行的命令回显到指定的位置。
<where>
可以是STDERR
,STDOUT
, 或NONE
。 -
这极大地便利了调试,让你清楚地看到 CMake 实际执行了什么命令。
-
-
ENCODING <name>
(CMake 3.8+)-
仅限 Windows。指定解码命令输出时使用的字符编码。
-
选项:
NONE
(默认旧行为),AUTO
(默认 3.15-3.30),ANSI
,OEM
,UTF-8
(默认 3.31+). -
主要用于处理非 ASCII 字符(如中文路径),防止输出乱码。
-
-
COMMAND_ERROR_IS_FATAL <ANY|LAST|NONE>
(CMake 3.19+)-
控制命令失败时 CMake 配置过程的行为。
-
ANY
: 只要任何一个命令失败,整个execute_process
调用就立即失败,导致 CMake 配置停止。 -
LAST
: 只有最后一个命令失败时才导致失败。 -
NONE
(CMake 4.0+ 的默认行为): 即使命令失败,CMake 配置也会继续。你必须通过检查RESULT_VARIABLE
来手动处理错误。 -
这个选项提供了更灵活的错误处理策略。
-
一.标准输出,标准错误,退出码
1.1.执行单条命令的情况
我们在这个小节里面呢,会讲讲下面这些选项
COMMAND
CMake 直接使用操作系统 API 来执行子进程,这意味着其行为与在 Shell 中直接运行命令有所不同:
-
在 POSIX 平台(如 Linux, macOS)上:
命令行会以argv[]
风格的数组传递给子进程。不会执行中间层的 Shell(如 bash 或 zsh)。因此,Shell 的操作符(例如>
(重定向)、|
(管道)、&&
(与)、&
(后台执行)等)会被当作普通的参数文本传递给命令,而不会起到它们原有的作用。
OUTPUT_VARIABLE, ERROR_VARIABLE
-
OUTPUT_VARIABLE
:指定的变量将被设置为标准输出管道(stdout) 中的内容。这通常是命令正常打印的信息。 -
ERROR_VARIABLE
:指定的变量将被设置为标准错误管道(stderr) 中的内容。这通常是命令打印的错误、警告信息。
特殊情况的处理:
如果同一个变量名被同时用于 OUTPUT_VARIABLE
和 ERROR_VARIABLE
(例如,都设置为 MY_VAR
),那么这两个管道的内容将会被合并到该变量中。合并的顺序是按照信息产生的先后顺序。
OUTPUT_STRIP_TRAILING_WHITESPACE,ERROR_STRIP_TRAILING_WHITESPACE
在 CMake 中,当使用 execute_process
或类似命令执行外部程序时,常会使用 OUTPUT_VARIABLE
和 ERROR_VARIABLE
选项来捕获命令的标准输出和标准错误。这些输出内容会以字符串形式存入相应的 CMake 变量中。然而,这类输出往往在末尾包含一些不必要的“空白字符”。
-
什么是“空白字符”?
主要是我们看不见的字符:空格(Space)、制表符(Tab),尤其是换行符(Newline)。命令输出通常以换行符结束,所以如果你捕获echo "hello"
的输出,你得到的很可能是"hello\n"
。 -
这有什么问题?
当你之后想用这个变量去做字符串比较时,问题就来了。比如你想检查输出是不是"success"
,但变量里实际是"success\n"
,这两者是不相等的,会导致你的判断失败。
CMake 提供了 OUTPUT_STRIP_TRAILING_WHITESPACE
和 ERROR_STRIP_TRAILING_WHITESPACE
选项,它们的作用是在将输出或错误内容写入变量之前,自动去除其末尾的所有空白字符。例如:
-
"hello\n"
会被修剪为"hello"
-
"error: something went wrong \t \n"
会被修剪为"error: something went wrong"
好处
使用这两个选项后,所得到的变量内容更为“干净”,避免了不可见字符带来的干扰,使得变量更适用于后续的字符串比较、条件判断或其他处理逻辑,提高代码的可靠性和可维护性。
1.1.1.标准输出
在这个例子中,我们将会讲解COMMAND,OUTPUT_VARIABLE,OUTPUT_STRIP_TRAILING_WHITESPACE的用法
话不多说,我们先看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ExecProcDemo)
# 在配置阶段调用 pwd 命令
execute_process(
COMMAND pwd
OUTPUT_VARIABLE CUR_DIR
)
message(STATUS "当前目录是: ${CUR_DIR}")
大家可能对这个execute_process部分非常好奇,那么我们来讲讲
1. execute_process
这是 CMake 的命令,在 配置阶段(configure 阶段) 立即执行一个外部程序,并且可以捕获输出、错误、返回码。
2. COMMAND pwd
-
COMMAND
指定要运行的外部命令。 -
这里的命令是
pwd
,即 打印当前工作目录(print working directory)。 -
CMake 会在配置时直接调用系统的
pwd
程序。
👉 例如,如果你在 build
目录里执行 cmake ..
,那么运行时 pwd
的输出一般是 .../build
。
3. OUTPUT_VARIABLE CUR_DIR
-
把命令的 标准输出(stdout) 保存到一个 CMake 变量
CUR_DIR
中。 -
这样你就可以在后面用
${CUR_DIR}
来引用这个目录。
我们可以通过下面这一条连着的bash语句来搭建这个目录结构和文件
mkdir -p demo && \
cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.18)
project(ExecProcDemo)
# 在配置阶段调用 pwd 命令
execute_process(
COMMAND pwd
OUTPUT_VARIABLE CUR_DIR
)
message(STATUS "当前目录是: ${CUR_DIR}")
EOF
接下来我们就来构建这个项目
mkdir build && cd build && cmake ..
很好,路径是对上了,但是我们细心的人就会发现,怎么中间有一行空行啊?
事实上
命令的本质:您在终端(Shell)中直接输入 pwd
命令时,它会在打印出当前目录路径后,自动添加一个换行符(\n
)。这是 POSIX 标准下命令行工具的标准行为,目的是让每个命令的输出都从一个新行开始,使结果清晰可读。
$ pwd
/home/user/my_project <-- 这后面有一个看不见的 '\n',所以你的提示符才到了下一行
$
CMake 的捕获机制:execute_process
命令的 OUTPUT_VARIABLE
选项会原封不动地捕获命令写入标准输出(stdout)的所有内容。这意味着它不仅捕获了您想要的路径字符串(例如 /root/cmake/demo/build
),也捕获了命令末尾自动添加的那个换行符。
所以,变量 CUR_DIR
的内容实际上是 "/root/cmake/demo/build\n"
。
CMake 提供了非常方便的方法来处理这个问题:
使用 OUTPUT_STRIP_TRAILING_WHITESPACE
选项(推荐)
这是最简洁、最直接的方法。
该选项会自动移除捕获的输出字符串末尾的所有空白字符,包括换行符(\n
)、回车符(\r
)和空格()。
execute_process(
COMMAND pwd
OUTPUT_VARIABLE CUR_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE # <-- 关键选项
)
现在输出末尾就不会有换行符了。
我们现在去重新验证一下:
怎么样?空行是不是没有了!!!
1.1.2.标准错误
在这个例子中,我们着重讲解ERROR_VARIABLE,ERROR_STRIP_TRAILING_WHITESPACE
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ExecProcessError)
execute_process(
COMMAND ls nonexistent_file
OUTPUT_VARIABLE STDOUT
ERROR_VARIABLE STDERR
)
message(STATUS "STDOUT='${STDOUT}'")
message(STATUS "STDERR='${STDERR}'")
execute_process()这个命令的作用是:在配置阶段执行一个外部命令。
这里执行的是:
ls nonexistent_file
也就是列出一个不存在的文件。
-
OUTPUT_VARIABLE STDOUT
捕获命令的 标准输出 并存到变量STDOUT
中。
(比如ls
成功时会输出文件名) -
ERROR_VARIABLE STDERR
捕获命令的 标准错误输出 并存到变量STDERR
中。
(比如ls
失败时会报错信息)
我们构建一下这个项目
mkdir build && cd build && cmake ..
我们发现错误信息给了ERROR_VARIABLE。
但是我们发现ERROR_VARIABLE后面也是有一行空行的。这就需要请出ERROR_STRIP_TRAILING_WHITESPACE了
cmake_minimum_required(VERSION 3.18)
project(ExecProcessErrorStrip)
execute_process(
COMMAND ls nonexistent_file
OUTPUT_VARIABLE STDOUT
ERROR_VARIABLE STDERR
ERROR_STRIP_TRAILING_WHITESPACE
)
message(STATUS "STDOUT='${STDOUT}'")
message(STATUS "STDERR='${STDERR}'")
现在错误信息后面的空行是不是就没有了??
OUTPUT_VARIABLE 和 ERROR_VARIABLE 指向同一个变量
演示当 OUTPUT_VARIABLE 和 ERROR_VARIABLE 指向同一个变量 时,CMake 会把标准输出和标准错误合并在一起,顺序取决于进程实际产生输出的先后。
cmake_minimum_required(VERSION 3.18)
project(ExecProcessMerge)
# 这个命令会同时输出到 stdout 和 stderr
# - echo "hello" 会输出到 stdout
# - ls nonexistent_file 会把错误信息输出到 stderr
execute_process(
COMMAND bash -c "echo hello; ls nonexistent_file"
OUTPUT_VARIABLE MIXED
ERROR_VARIABLE MIXED
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
message("MIXED='${MIXED}'")
我们发现这个echo的输出先打印出来,这是因为echo先执行啊。
如果我们把示例内容修改成下面这样子,那么谁会先被打印出来呢?
cmake_minimum_required(VERSION 3.18)
project(ExecProcessMerge)
# 这个命令会同时输出到 stdout 和 stderr
# - echo "hello" 会输出到 stdout
# - ls nonexistent_file 会把错误信息输出到 stderr
execute_process(
COMMAND bash -c "ls nonexistent_file; echo hello"
OUTPUT_VARIABLE MIXED
ERROR_VARIABLE MIXED
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
message("MIXED='${MIXED}'")
我们发现错误信息先打印出来,因为ls先执行,所以错误信息先出来。
1.1.3.退出码
RESULT_VARIABLE
官网原文:
The variable will be set to contain the result of last child process. This will be an integer return code from the last child or a string describing an error condition.
翻译与解释:
该变量将被设置为包含最后一个子进程的运行结果。
这个结果可能是以下两种之一:
-
一个整数返回码:通常来自最后一个命令。返回码为
0
通常表示成功,非0
值表示错误(具体含义由被调用的命令决定)。 -
一个描述错误条件的字符串:这通常发生在进程根本无法启动的时候(例如,命令不存在)。此时,字符串会描述错误原因,如
"No such file or directory"
(找不到文件或目录)。
核心要点: RESULT_VARIABLE
用于获取命令执行的最终状态(成功、失败、还是根本没能运行)。
示例1——运行成功
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ExecSuccessDemo)
execute_process(
COMMAND echo "Hello World"
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
RESULT_VARIABLE RET
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
message(STATUS "返回码: ${RET}")
message(STATUS "标准输出: '${OUT}'")
message(STATUS "错误输出: '${ERR}'")
我们可以仔细观察这个命令啥意思
COMMAND echo "Hello World"
- 指定要执行的外部命令,这里是系统命令
echo "Hello World"
。 - 运行后会往 标准输出(stdout) 打印一行
Hello World
。
OUTPUT_VARIABLE OUT
- 把命令的 标准输出(stdout) 保存到 CMake 变量
OUT
。 - 如果命令有多行输出,会存成一个带换行符的字符串。
ERROR_VARIABLE ERR
- 把命令的 标准错误(stderr) 保存到 CMake 变量
ERR
。 - 如果命令没有报错,那
ERR
就是空字符串。
RESULT_VARIABLE RET
- 保存命令的 返回码(exit code)。
- 0 通常表示成功,非 0 表示失败。
- 例如:
echo
正常执行 →RET=0
。
OUTPUT_STRIP_TRAILING_WHITESPACE
- 去掉标准输出结尾的换行符。
- 否则
OUT
可能是"Hello World\n"
。
ERROR_STRIP_TRAILING_WHITESPACE
- 去掉标准错误结尾的换行符。
- 保证
ERR
内容更干净。
我们接下来来构建项目
mkdir build && cd build && cmake ..
怎么样?还是很好的吧!
❌ 示例2——命令不存在
接下来我们在修改那个CMakeLists.txt,修改成下面这个即可
cmake_minimum_required(VERSION 3.18)
project(ExecFailDemo)
# 情况 1:命令不存在
execute_process(
COMMAND nonexistent_command
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
RESULT_VARIABLE RET
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
message(STATUS "返回码: ${RET}")
message(STATUS "标准输出: '${OUT}'")
message(STATUS "错误输出: '${ERR}'")
运行结果
我们看,错误码怎么没有啊?
我们来仔细看看
我的命令是 COMMAND nonexistent_command
,系统里并没有这个名字的可执行文件。
-
返回码 (RET):
No such file or directory
-
这不是一个数字退出码(如
1
或2
),而是一个错误描述字符串。 -
原因:
RESULT_VARIABLE
被设置为一个字符串,通常发生在进程根本无法启动时。CMake 尝试调用nonexistent_command
,操作系统直接返回了一个错误:“找不到这个文件或目录”,CMake 捕获到这个系统错误信息并放入了RET
变量。这与命令存在但执行失败(返回非零退出码)的情况完全不同。
-
-
标准输出 (OUT):
''
(空字符串)-
这很符合逻辑。因为进程压根没能启动,所以自然没有任何内容被写入到标准输出管道。
OUTPUT_VARIABLE
也就捕获不到任何东西。
-
-
错误输出 (ERR):
''
(空字符串)-
这一点是最关键且最容易让人困惑的地方。
-
原因:
ERROR_VARIABLE
捕获的是子进程已经启动后写入标准错误(stderr)管道的内容。 -
在您这个例子中,
nonexistent_command
这个子进程根本没有被创建成功。因此,不存在一个“活的”进程往 stderr 管道里写诸如bash: nonexistent_command: command not found
这样的错误信息。 -
那个
command not found
的错误信息其实是来自于您父进程的 Shell(比如 bash)。是 Shell 在解析命令时发现它不存在而打印的。CMake 的execute_process
是直接通过操作系统 API(如fork
/exec
)来创建进程的,它绕过了 Shell,所以也捕获不到 Shell 才有的错误信息。 -
真正的错误(“No such file or directory”)是通过进程创建 API 的返回值传递回来的,CMake 将其设置给了
RESULT_VARIABLE
。
-
❌ 示例3——命令存在,但是执行失败,返回码!=0
我们只需将CMakeLists.txt修改成下面这个即可
cmake_minimum_required(VERSION 3.18)
project(ExecCompareDemo)
execute_process(
COMMAND ls nonexistent_file
OUTPUT_VARIABLE OUT1
ERROR_VARIABLE ERR1
RESULT_VARIABLE RET1
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
message(STATUS "返回码: ${RET1}")
message(STATUS "标准输出: '${OUT1}'")
message(STATUS "错误输出: '${ERR1}'")
怎么样?现在对退出码有更深刻的印象了吗。
1.2.执行多条命令的情况
1.2.1.标准输出
OUTPUT_VARIABLE
CMake 的规则是:OUTPUT_VARIABLE
只会捕获最后一条命令的标准输出。
我们看看例子即可
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(MultiOutputDemo)
execute_process(
COMMAND echo "First"
COMMAND echo "Second"
OUTPUT_VARIABLE OUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "输出: '${OUT}'")
可以看到啊,我这里可是执行了两条命令的。
我们接下来来构建项目
mkdir build && cd build && cmake ..
输出结果
我们看看这个输出,只打印了Second,也就是最后一个子进程的输出。
事实上呢!!
execute_process() 没有一个单一的、直接的选项(比如 ALL_OUTPUT_VARIABLE)来自动获取管道中所有命令的混合输出。
这是因为 execute_process 将一系列命令组织成一个管道链,每个命令的标准输出都连接到下一个命令的标准输入。它的设计初衷是模拟 Shell 中的管道行为(如 cmd1 | cmd2 | cmd3)。因此,默认情况下,你只能捕获到这个管道链的“最终”结果。
下面我来好好讲讲为什么这样设计以及如何实现获取所有输出。
核心概念:管道链的输入与输出
想象一下这个命令:
cmd1 | cmd2 | cmd3
-
cmd1
的 stdout 成为了cmd2
的 stdin。 -
cmd2
的 stdout 成为了cmd3
的 stdin。 -
最终我们只能在终端看到
cmd3
的 stdout。
execute_process
的工作方式完全相同:
execute_process(
COMMAND cmd1
COMMAND cmd2
COMMAND cmd3
OUTPUT_VARIABLE final_output # 这里只捕获 cmd3 的 stdout
)
这里,final_output
变量里只有 cmd3
的标准输出。cmd1
和 cmd2
的标准输出在管道中传递并被消耗了,无法直接单独访问。
那我想要看到所有子进程的标准输出怎么办呢?
我们只能像下面这样子做
cmake_minimum_required(VERSION 3.18)
project(MultiOutputDemo)
execute_process(
COMMAND echo "First"
OUTPUT_VARIABLE OUT1
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND echo "Second"
OUTPUT_VARIABLE OUT2
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "OUT1='${OUT1}'")
message(STATUS "OUT2='${OUT2}'")
这样子,就能获取到每个子进程的标准输出了。
1.2.2.标准错误
在 CMake 的 execute_process 命令中,ERROR_VARIABLE 的行为与 OUTPUT_VARIABLE 有一个重要的区别:ERROR_VARIABLE 会默认捕获并合并所有 COMMAND 子进程的标准错误输出。
我们直接看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ErrorVarSingleDemo)
execute_process(
COMMAND ls nonexistent1
COMMAND ls nonexistent2
ERROR_VARIABLE ERR
ERROR_STRIP_TRAILING_WHITESPACE
)
message(STATUS "捕获到的错误输出: '${ERR}'")
可以看到啊,我这里可是执行了两条命令的。
我们接下来来构建项目
mkdir build && cd build && cmake ..
输出结果
我们发现两条命令的错误都打印出来了。
怎么样,还是看得懂的吧!!!
1.1.3.退出码
首先我们需要知道RESULT_VARIABLE是最后一个子进程的运行结果。
那如果我们想要知道每个子进程的退出码应该怎么办?
其实下面这个就能解决
RESULTS_VARIABLE <variable>
(自3.10版起新增)
该变量将被设置为包含所有进程执行结果的列表,列表以分号分隔,排列顺序与给定的COMMAND参数顺序一致。每个条目将是对应子进程的整数返回码,或描述错误情况的字符串。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ResultsVariableDemo)
execute_process(
COMMAND echo Hello
COMMAND ls nonexistent_file
COMMAND false
RESULT_VARIABLE RET
RESULTS_VARIABLE RETS
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
)
message(STATUS "最后一个命令的返回码/错误: ${RET}")
message(STATUS "所有命令的返回码/错误列表: ${RETS}")
message(STATUS "标准输出: '${OUT}'")
message(STATUS "错误输出: '${ERR}'")
接下来我们来构建一下项目
mkdir build && cd build && cmake ..
怎么样?还是很符合我们的预期的吧!!
二.执行多条命令的机制
2.1.基本说明
3. 重要特性与细节
a. 独立性
每个 COMMAND
确实都是一个独立的子进程。它们不共享内存空间,唯一的通信方式就是通过 CMake 为它们设置的管道(stdout -> stdin)。
b. 错误处理
-
默认行为:如果管道中的任何一个子进程以非零状态退出(即发生错误),默认情况下,
execute_process
会立即停止执行,并且 CMake 配置阶段会因错误而中止。 -
忽略错误:你可以使用
ERROR_QUIET
选项来抑制错误信息,或者使用RESULTS_VARIABLE
来捕获每个进程的退出码,以便自行处理,而不是让 CMake 中止。
c. 输出捕获
整个管道链的最终输出(即最后一个命令的 stdout)可以被捕获到变量中,这是非常强大的功能。
-
OUTPUT_VARIABLE <var>
: 捕获整个管道链最终的标准输出,也就是最后一个进程的输出。 -
ERROR_VARIABLE <var>
: 捕获整个管道链每个进程的标准错误。注意:标准错误流通常不通过管道传递,所有命令的 stderr 默认都会混合到同一个错误流中。
d. 输入与输出分离
-
第一个进程的标准输入默认是空的(类似于从
/dev/null
读取),除非你使用INPUT_FILE
选项为其指定一个文件。 -
最后一个进程的标准输出和标准错误可以被捕获到变量或写入文件(使用
OUTPUT_FILE
和ERROR_FILE
选项)。
2.2.示例1——各个命令存在管道机制
1. 基本语法
execute_process(
COMMAND <cmd1> [<args>]
COMMAND <cmd2> [<args>]
COMMAND <cmd3> [<args>]
...
[OTHER_OPTIONS...]
)
2. 执行模型:管道链 (Pipeline)
当指定多个 COMMAND
参数时,execute_process
会创建一个进程管道:
-
并行启动:CMake 会(几乎)同时启动所有这些子进程。
-
管道连接:它会将第一个进程 (
cmd1
) 的标准输出(stdout) 连接到第二个进程 (cmd2
) 的标准输入(stdin)。 -
将
cmd2
的 stdout 连接到cmd3
的 stdin,以此类推。 -
上一个
COMMAND
的 标准输出 会作为下一个COMMAND
的 标准输入; -
数据流动:数据像水流过管道一样,从第一个命令流向下一个命令,每个命令都对流经的数据进行一次处理。
这个过程非常类似于你在 Shell 中这样写:
cmd1 | cmd2 | cmd3
话不多说,我们直接看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(PipelineDemo)
# 写一个示例文件
file(WRITE "${CMAKE_BINARY_DIR}/input.txt" "
Hello World
Hi there
Hello CMake
Bye Bye
Hello again
")
# 执行三个命令:
# 1. cat input.txt -> 输出文件内容
# 2. grep Hello -> 过滤出包含 "Hello" 的行
# 3. wc -l -> 统计行数
execute_process(
COMMAND cat "${CMAKE_BINARY_DIR}/input.txt"
COMMAND grep Hello
COMMAND wc -l
OUTPUT_VARIABLE OUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "包含 'Hello' 的行数: ${OUT}")
其实这个execute_process()所等价的 Shell 命令就是:
cat "${CMAKE_BINARY_DIR}/input.txt" | grep Hello | wc -l
我们现在来构建项目
mkdir build && cd build && cmake ..
我们去验证一下是不是这么一回事
事实证明,确实是这样子的。我们现在来讲讲这个例子。
这是整个示例的核心:
execute_process(
COMMAND cat "${CMAKE_BINARY_DIR}/input.txt" # 命令 1: 读取文件内容
COMMAND grep Hello # 命令 2: 过滤包含"Hello"的行
COMMAND wc -l # 命令 3: 统计行数
OUTPUT_VARIABLE OUT # 捕获最终输出
OUTPUT_STRIP_TRAILING_WHITESPACE # 清理输出
)
让我们一步步分析这个管道是如何工作的:
阶段 1: 数据读取 (cat
)
-
进程启动:CMake 启动
cat
子进程,并告诉它要读取input.txt
文件。 -
输出:
cat
进程将文件的内容逐行写入到它的标准输出 (stdout)。 -
数据流:此时,
cat
的 标准输出 (stdout) 并没有直接打印到终端,而是被 CMake 重定向到了一个管道中。
阶段 2: 数据过滤 (grep
)
-
进程启动:几乎同时,CMake 启动了
grep
子进程。 -
输入:CMake 将上一个管道(来自
cat
的标准输出 (stdout))连接到grep
的标准输入 (stdin)。grep
从这个输入流中读取数据。 -
处理:
grep
逐行检查输入,只保留那些包含字符串 "Hello" 的行。 -
输出:过滤后的结果(只有包含 "Hello" 的行)被
grep
写入到它自己的标准输出 (stdout)。 -
数据流:
grep
的 标准输出 (stdout)又被 CMake 重定向到另一个管道中。
阶段 3: 数据统计 (wc
)
-
进程启动:几乎同时,CMake 启动了
wc
子进程(带有-l
参数,表示统计行数)。 -
输入:CMake 将上一个管道(来自
grep
的标准输出 (stdout) )连接到wc
的标准输入 (stdin) 。wc
从这个输入流中读取数据。 -
处理:
wc -l
计算它从 stdin 读取到的行数。 -
输出:统计结果(一个数字)被
wc
写入到它自己的标准输出 (stdout) 。这是整个管道的最终输出。
阶段 4: 输出捕获与处理
-
OUTPUT_VARIABLE OUT
:CMake 捕获最后一个命令 (wc
) 的标准输出 (stdout) ,并将其内容存储到变量OUT
中。如果没有这个选项,最终结果将会丢失。 -
OUTPUT_STRIP_TRAILING_WHITESPACE
:这是一个非常重要的步骤。wc -l
命令的输出通常是数字后面跟着一个换行符,例如"3\n"
。这个选项会智能地移除这些尾部的空白字符(如换行符、空格),使变量OUT
的值变成干净的"3"
。如果不这样做,后续使用${OUT}
时可能会带来意想不到的格式问题。
2.3.示例2——各个命令独立执行
在 CMake 的 execute_process
中,当您指定多个 COMMAND
参数时,它们总是会被连接成一个管道(Pipe),无论它们之间在逻辑上是否存在“管道关系”。
核心机制:强制性的管道连接
CMake 会严格按照您提供的 COMMAND
顺序,无条件地创建管道连接:
-
物理管道必然存在:
-
从实现机制上来看,这种管道连接是物理必然存在的。无论您是否希望如此,CMake 的内部机制都会强制建立操作系统级别的管道连接。
-
具体来说,第一个命令(COMMAND A)的标准输出(stdout)一定会被连接到第二个命令(COMMAND B)的标准输入(stdin)。同样地,第二个命令的标准输出又一定会被连接到第三个命令的标准输入,以此类推。
-
这是一个由 CMake 强制建立的、底层的、操作系统级别的管道,与命令本身的逻辑无关。
-
那么,当我们说某些命令之间"不存在管道关系"时,这里的准确含义是什么呢?
-
这实际上是指后一个命令在功能上并不需要、也不期望前一个命令的输出作为其输入。
-
换句话说,虽然物理管道已经建立,数据也在流动,但由于命令自身的特性,这种数据流动并没有被实际利用。
我们看个例子即可
例子:execute_process(COMMAND date COMMAND ls -l)
-
date
命令会输出当前时间到它的 stdout。 -
由于管道存在,这个时间字符串会被强行塞进
ls -l
命令的 stdin。 -
ls -l
命令的本意是列出目录内容,它并不期望从 stdin 读取任何输入。它会忽略这些输入,照样从命令行参数(当前目录)获取信息并执行。 -
结果:管道建立了,数据流动了,但第二个命令
ls
选择忽略这些输入,所以最终效果看起来像是两个独立命令都执行了。date
的输出(时间字符串)在管道中被丢弃了,没有被ls
利用。 -
从最终效果来看,这两个命令似乎只是简单地先后执行,彼此独立,但需要注意的是,底层的管道机制确实已经发生,只是没有产生实际效果。
这种机制设计意味着在使用 execute_process 时,开发者需要特别留意命令之间的组合方式。即使某些命令在逻辑上不需要前一个命令的输出,CMake 仍然会在它们之间建立管道连接,这有时可能会带来不必要的性能开销,或者在极少数情况下导致意料之外的行为。
因此,如果确实只需要顺序执行多个独立的命令,而不是让它们通过管道传递数据,更好的做法是分别调用多个 execute_process 命令,或者通过调用 shell 来解释执行一系列命令。
那么到现在我们也就能理解一件事情:
事实上呢,我们是不推荐使用execute_process()来执行多条完全独立的命令的,因为我们只能获取最后一条command的标准输出,其他command的标准输出都会被忽略掉。
我们可以看看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(PipelineDemo)
execute_process(
COMMAND date
COMMAND ls -l
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
)
message(STATUS "结果: \n${OUT}")
我们现在来构建项目
mkdir build && cd build && cmake ..
我们发现只有最后一条命令ls -l有输出,而data被忽略掉了。
事实上呢,除了这个OUTPUT_VARIABLE是获取最后一个command标准输出的之外。
其实OUTPUT_FILE <file>也是获取最后一个command的标准输出,我们完全没有方法去获取到除了最后一个command之外的其他command的输出。
除了像下面这样子分开来写,我们没有任何办法获取除了最后一个command之外的其他command的输出。
cmake_minimum_required(VERSION 3.18)
project(SeparateDemo)
execute_process(
COMMAND date
OUTPUT_VARIABLE DATE_OUT
)
execute_process(
COMMAND ls -l
OUTPUT_VARIABLE LS_OUT
)
message(STATUS "时间输出: \n${DATE_OUT}")
message(STATUS "目录输出: \n${LS_OUT}")
2.4.示例3——错误机制(各个命令直接本就存在管道关系)
首先要理解 execute_process
中多个 COMMAND
参数的错误处理行为。
因为多个命令都会暗自被系统使用上管道(链式执行):
-
格式:
COMMAND cmd1 | cmd2 | cmd3
-
行为: 这些命令会形成一个管道。
cmd1
的标准输出作为cmd2
的标准输入,以此类推。这是真正的“命令链”。 -
错误处理: 如果链中任何一个命令失败(返回非零退出码),整个管道通常会立即失败,后续命令可能不会被执行。错误退出码通常是最后一个执行命令的退出码。
2.4.1.命令不存在
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(PipelineChainDemo)
# 写一个测试文件
file(WRITE "${CMAKE_BINARY_DIR}/input.txt" "
Hello World
Hi there
Hello CMake
Bye Bye
Hello again
")
# 执行命令链: cat -> nonexistent_command -> wc -l
execute_process(
COMMAND cat "${CMAKE_BINARY_DIR}/input.txt"
COMMAND nonexistent_command
COMMAND wc -l
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
RESULT_VARIABLE RET
RESULTS_VARIABLE RETS
)
message(STATUS "输出结果: '${OUT}'")
message(STATUS "错误输出: '${ERR}'")
message(STATUS "最后一个进程的返回码: ${RET}")
message(STATUS "各命令返回码: ${RETS}")
我们现在来构建项目
mkdir build && cd build && cmake ..
关键点分析
-
命令链行为:你使用了三个
COMMAND
参数,这意味着 CMake 会尝试将它们作为管道链执行,相当于在 shell 中运行cat input.txt | nonexistent_command | wc -l
。 -
中间命令失败:第二个命令
nonexistent_command
不存在,这是一个致命错误,会导致整个命令链在这一点上立即失败并中止。 -
输出结果为空 (
''
):-
因为命令链在
nonexistent_command
处就失败了,第三个命令wc -l
根本没有被执行。 -
OUTPUT_VARIABLE
捕获的是最后一个命令 (wc -l
) 的标准输出。既然wc -l
从未运行,自然没有任何输出被捕获。
-
-
错误输出为空 (
''
):-
这有点反直觉。你可能会期望看到
"bash: nonexistent_command: command not found"
这样的错误信息。 -
原因是:
ERROR_VARIABLE
捕获的是最后一个命令的标准错误。由于最后一个命令 (wc -l
) 根本没有运行,它没有产生任何标准错误。 -
而
nonexistent_command
产生的错误信息属于中间命令,在管道链的设置中,这些中间错误通常不会通过ERROR_VARIABLE
暴露给 CMake,而是直接导致进程失败。错误信息可能被打印到了终端(但被 CMake 的日志淹没),或者被系统丢弃了。
-
-
返回码分析:
-
RESULT_VARIABLE
(单数):存储最后一个命令的退出码。因为最后一个命令 (wc -l
) 未能执行,CMake 无法获取其退出码,所以它捕获到的是启动该进程时失败的系统错误信息——"No such file or directory"
。这通常意味着 Shell 找不到nonexistent_command
这个可执行文件。 -
RESULTS_VARIABLE
(复数):存储一个列表,包含所有命令的退出码或错误信息。-
在你的例子中,命令链包含三个命令。
-
第一个命令
cat
很可能成功执行了(退出码为 0),但因为它是管道的一部分,它的输出被传递给了下一个命令,而不是给 CMake。 -
第二个命令
nonexistent_command
启动失败,其“结果”就是错误信息"No such file or directory"
。 -
第三个命令
wc -l
由于上游失败而未能执行。 -
然而,从输出看,
RETS
变量只包含了一个值"No such file or directory"
,而不是一个包含三个元素的列表。这表明当管道链中的任何一个环节发生致命错误时,整个操作被视作一个单一的失败单元,RESULTS_VARIABLE
可能无法提供每个命令的独立状态,而是反映了整个链的失败原因。这种行为可能依赖于 CMake 的版本和底层的执行环境。
-
-
2.4.2.命令存在,但是退出码!=0
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(PipelineChainDemo)
# 写一个测试文件
file(WRITE "${CMAKE_BINARY_DIR}/input.txt" "
Hello World
Hi there
Hello CMake
Bye Bye
Hello again
")
# 执行命令链: cat -> ls -> wc -l
execute_process(
COMMAND cat "${CMAKE_BINARY_DIR}/input.txt"
COMMAND ls no_such_file
COMMAND wc -l
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
RESULT_VARIABLE RET
RESULTS_VARIABLE RETS
)
message(STATUS "输出结果: '${OUT}'")
message(STATUS "错误输出: '${ERR}'")
message(STATUS "最后一个进程的返回码: ${RET}")
message(STATUS "各命令返回码: ${RETS}")
我们现在来构建项目
mkdir build && cd build && cmake ..
可以看到虽然退出码!=0,但是所有命令都执行成功了。
2.5.示例4——错误机制(各个命令都可独立执行)
2.3.1.命令不存在
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(MultiIndependentCommands)
execute_process(
COMMAND echo "第一条命令"
COMMAND nonexistent_command # 故意失败
COMMAND echo "第三条命令"
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
RESULT_VARIABLE RET
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULTS_VARIABLE RETS # 捕获每个命令的退出码
)
message(STATUS "最后一个进程的返回码: ${RET}")
message(STATUS "所有子进程的返回码: ${RETS}")
message(STATUS "最后一个进程的标准输出: '${OUT}'")
message(STATUS "所有进程的错误输出: '${ERR}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
这个例子揭示了一个关键点:即使命令之间没有数据依赖(没有使用 |
管道符),execute_process()
中的多个 COMMAND
参数仍然会形成管道链,而不是独立执行。
这与许多开发者的直觉相悖。他们可能期望:
-
三个命令独立执行
-
分别捕获每个命令的输出和错误
-
分别获取每个命令的返回码
但实际上,execute_process()
的设计始终将这些命令视为管道链的一部分。
也就是说
-
命令链而非独立执行:尽管这三个命令看起来是独立的(它们之间没有数据依赖),但
execute_process()
仍然将它们作为管道链处理,相当于在 shell 中执行echo "第一条命令" | nonexistent_command | echo "第三条命令"
。 -
中间命令失败导致链中断:第二个命令
nonexistent_command
不存在,这是一个致命错误。与管道操作一致,整个命令链在此处立即中止,第三个命令echo "第三条命令"
根本没有被执行。 -
输出结果分析:
-
标准输出为空 (
''
):OUTPUT_VARIABLE
捕获的是最后一个命令 (echo "第三条命令"
) 的输出。由于该命令未执行,输出为空。 -
错误输出为空 (
''
):ERROR_VARIABLE
捕获的是最后一个命令的错误输出。由于最后一个命令未执行,错误输出为空。中间命令nonexistent_command
的错误信息没有被捕获到。
-
-
返回码分析:
-
RESULT_VARIABLE
(单数):返回的是最后一个命令的状态。由于最后一个命令未能执行,它返回了系统错误信息"No such file or directory"
。 -
RESULTS_VARIABLE
(复数):理论上应该返回所有命令的状态列表,但由于整个链在第二个命令处就完全失败,它也只返回了相同的错误信息"No such file or directory"
,而不是一个包含三个状态的列表。
-
2.3.2.命令存在,但是退出码!=0
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(MultiIndependentCommands)
execute_process(
COMMAND echo "第一条命令"
COMMAND ls a.txt
COMMAND echo "第三条命令"
OUTPUT_VARIABLE OUT
ERROR_VARIABLE ERR
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULTS_VARIABLE RETS # 捕获每个命令的退出码
)
message(STATUS "所有子进程的返回码: ${RETS}")
message(STATUS "最后一个进程的标准输出: '${OUT}'")
message(STATUS "所有进程的错误输出: '${ERR}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
我们发现这好像是正常运行了。
2.6.错误机制总结
因为多个命令都会暗自被系统使用上管道(链式执行):
-
格式:
COMMAND cmd1 | cmd2 | cmd3
-
行为: 这些命令会形成一个管道。
cmd1
的标准输出作为cmd2
的标准输入,以此类推。这是真正的“命令链”。 -
错误处理: 如果链中任何一个命令失败(返回非零退出码),整个管道通常会立即失败,后续命令可能不会被执行。错误退出码通常是最后一个执行命令的退出码。
三. 输入,输出,错误重定向
接下来我们需要讲的就是下面这些
INPUT_FILE <file>
<file>
将被附加到第一个 COMMAND
进程的标准输入管道。
OUTPUT_FILE <file>
<file>
将被附加到最后一个 COMMAND
进程的标准输出管道。
ERROR_FILE <file>
<file>
将被附加到所有 COMMAND
进程的标准错误管道。
自 3.3 版本新增: 如果为 OUTPUT_FILE
和 ERROR_FILE
指定了相同的 <file>
,则该文件将同时用于标准输出和标准错误管道。
3.1.输入重定向——INPUT_FILE <file>
首先我们先看看怎么理解
输入重定向是一种常见的 Shell 操作方式,它允许将文件的内容作为标准输入(stdin)传递给某个命令。
在 Shell 中,我们使用 <
操作符来实现这一功能。
例如,以下命令会将 input.txt
文件中的内容作为 my_tool
的输入:
my_tool < input.txt
在 CMake 中,类似的功能可以通过 execute_process
命令中的 INPUT_FILE
选项来实现。
该选项指定一个文件,子进程将直接从这个文件中读取内容作为标准输入,而不是由 CMake 先读取文件内容再进行传递。
这种方式更高效,也更符合底层输入重定向的行为。
例如,在 CMakeLists.txt 中可以这样编写,同样以下命令会将 input.txt
文件中的内容作为 my_tool
的输入
execute_process(
COMMAND my_tool
INPUT_FILE input.txt
...
)
值得注意的是,INPUT_FILE
的作用是让子进程直接从指定的文件中读取输入,而非通过 CMake 进行中间处理。
这在功能上等同于 Shell 中的输入重定向操作,提供了一种跨平台且与 CMake 构建系统集成良好的输入重定向机制。
3.1.1.执行单条命令
INPUT_FILE
会把指定文件的内容作为 第一个 COMMAND 的标准输入。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(InputFileDemo)
# 创建一个输入文件
file(WRITE "${CMAKE_BINARY_DIR}/input.txt" "Hello from file!")
execute_process(
COMMAND cat
INPUT_FILE "${CMAKE_BINARY_DIR}/input.txt"
OUTPUT_VARIABLE OUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message("输出内容: '${OUT}'")
上面这个
execute_process(
COMMAND cat
INPUT_FILE "${CMAKE_BINARY_DIR}/input.txt"
OUTPUT_VARIABLE OUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
完全等价于下面这个命令
cat < ${CMAKE_BINARY_DIR}/input.txt
解释一下
-
INPUT_FILE <file>
的作用是:
把<file>
的内容 重定向到第一个 COMMAND 进程的标准输入。 -
所以
cat
并没有传任何参数,它只是从 标准输入 (stdin) 里读取内容。 -
而
INPUT_FILE
把文件的内容送到了cat
的 stdin,相当于<
重定向。
👉 换句话说:
-
COMMAND cat INPUT_FILE file.txt
≈cat < file.txt
-
COMMAND cat file.txt
≈cat file.txt
两者效果一样,但机制不同:
-
前者:走的是 stdin 重定向。
-
后者:
cat
直接从命令行参数指定的文件读取。
我们现在来构建项目
mkdir build && cd build && cmake ..
还是在预期之内的吧!!
我们再去验证一下
是不是对上了啊!!!
3.1.2.执行多条命令
execute_process()
确实可以一次执行多个 COMMAND
,它们是串联执行的子进程:
-
每个
COMMAND
是独立的子进程; -
上一个
COMMAND
的 标准输出 会作为下一个COMMAND
的 标准输入; -
如果指定了
INPUT_FILE
,它只会作用在第一个COMMAND
上。
话不多说,直接看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ExecPipeDemo)
# 创建输入文件
file(WRITE "${CMAKE_BINARY_DIR}/input.txt" "Hello World\nBye World\nHello CMake")
execute_process(
COMMAND cat
COMMAND grep Hello
INPUT_FILE "${CMAKE_BINARY_DIR}/input.txt"
OUTPUT_VARIABLE OUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "结果: '${OUT}'")
那么这个execute_process()
啥意思呢?我们看看
1. execute_process()
的基本规则
-
不会通过 Shell 来执行命令,而是直接调用操作系统 API。
-
多个
COMMAND
会依次执行,并且 前一个命令的标准输出会自动作为下一个命令的标准输入,就像 Unix 管道一样(不过没有真正用|
,是 CMake 内部传递的)。 -
INPUT_FILE
表示第一个命令的标准输入内容来自一个文件。
2. 执行过程逐步展开
-
INPUT_FILE
指定了输入文件${CMAKE_BINARY_DIR}/input.txt
。
→ 相当于给第一个命令cat
执行时做了重定向:cat < "${CMAKE_BINARY_DIR}/input.txt"
-
第一个命令:
cat
-
由于
cat
默认从标准输入读取,而这里的标准输入就是input.txt
文件内容,
所以这一步就是 输出文件内容。
-
-
第二个命令:
grep Hello
-
它会接收上一步
cat
的输出作为输入,并过滤出包含"Hello"
的行。
-
-
OUTPUT_VARIABLE OUT
-
把最后一个命令(这里是
grep Hello
)的标准输出捕获到 CMake 变量OUT
中。
-
-
OUTPUT_STRIP_TRAILING_WHITESPACE
-
去掉
OUT
变量末尾的换行符。
-
整体效果等价于在 Shell 中执行:
cat < "${CMAKE_BINARY_DIR}/input.txt" | grep Hello
并把结果保存到 OUT
里。
我们现在来构建项目
mkdir build && cd build && cmake ..
还是在预期之内的吧!!
3.2.输出重定向
OUTPUT_FILE <file>
<file>
将被附加到最后一个 COMMAND
进程的标准输出管道。
3.2.1.执行单条命令
话不多说,直接看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(OutputFileDemo)
# 执行命令,把输出写入 output.txt
execute_process(
COMMAND echo "hello OUTPUT_FILE"
OUTPUT_FILE "${CMAKE_BINARY_DIR}/output.txt"
)
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
怎么样?输出是不是就存到我们的构建目录里面的output.txt文件里面了。
3.2.2.执行多条命令
这里要注意:
-
OUTPUT_FILE <file>
捕获的是最后一个命令的 stdout。 -
如果有多个
COMMAND
,它们会被 强制管道连接,所以最终写到文件的内容 = 最后一个命令的 stdout。 -
注意是这个是先清除文件内容,再写入文件的。
OUTPUT_FILE <file>
捕获的是最后一个命令的 stdout。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(OutputFileDemo)
# 写一个测试文件
file(WRITE "${CMAKE_BINARY_DIR}/input.txt" "
Hello World
Hi there
Hello CMake
Bye Bye
Hello again
")
# 命令链: cat -> grep -> wc -l
# 最终输出会写到 output.txt
execute_process(
COMMAND cat "${CMAKE_BINARY_DIR}/input.txt"
COMMAND grep Hello
COMMAND wc -l
OUTPUT_FILE "${CMAKE_BINARY_DIR}/output.txt"
)
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
怎么样?输出是不是就存到我们的构建目录里面的output.txt文件里面了。
注意:OUTFILE是先清空再写入文件的
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(OutputFileAppendDemo)
# 第一次执行: 输出 "Hello" 写入 output.txt
execute_process(
COMMAND echo "Hello"
OUTPUT_FILE "${CMAKE_BINARY_DIR}/output.txt"
)
# 第二次执行: 输出 "World" 追加到 output.txt
execute_process(
COMMAND echo "World"
OUTPUT_FILE "${CMAKE_BINARY_DIR}/output.txt"
)
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
怎么样?输出是不是就存到我们的构建目录里面的output.txt文件里面了。
结果 output.txt
里面只有 World
,说明 OUTPUT_FILE
实际行为是:
-
如果文件不存在 → 创建文件,再写入
-
如果文件存在 → 先清空文件,再写入
所以你看到的结果只有 World
,而不是 Hello\nWorld
。
3.3.错误重定向
ERROR_FILE <file>
<file>
将被附加到所有 COMMAND
进程的标准错误管道。
3.3.1.执行不存在的命令
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ErrorFileDemo)
# 执行一个不存在的命令,必然报错
execute_process(
COMMAND nonexistent_command
ERROR_FILE "${CMAKE_BINARY_DIR}/error.log"
)
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
我们看看里面是什么东西?
什么都没有。
-
CMake 在解析
COMMAND nonexistent_command
时,如果找不到这个可执行文件,会直接报错并停止配置(而不是把错误写入ERROR_FILE
)。 -
所以不会进入子进程执行,自然也不会有 stderr 写入
error.log
。
3.3.2.执行存在的命令
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ErrorFileDemo)
# 故意访问不存在的文件,让命令写入 stderr
execute_process(
COMMAND ls not_exist_file
ERROR_FILE "${CMAKE_BINARY_DIR}/error.log"
)
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
我们看看里面是什么东西?
怎么样。错误信息是不是就出来了???
3.3.3.执行多条命令
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(MultiErrorDemo)
execute_process(
COMMAND ls not_exist_file1
COMMAND cat not_exist_file2
COMMAND grep "hello" not_exist_file3
ERROR_FILE "${CMAKE_BINARY_DIR}/error.log"
)
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
我们看看里面是什么东西?
我们发现所有命令的错误信息都存在这里。很符合我们的预期了吧!
四. OUTPUT_QUIET, ERROR_QUIET
标准输出(对应OUTPUT_VARIABLE)或标准错误(对应ERROR_VARIABLE)将不会连接至变量(变量内容为空)。*_FILE 和 ECHO_*_VARIABLE 选项不受此影响。
4.1.OUTPUT_QUIET
这个选项的意思很简单:如果设置了OUTPUT_QUIET,那么意味着👉 标准输出不会保存到变量,但如果你用了 OUTPUT_FILE
,还是会写到文件。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(OutputQuietDemo)
execute_process(
COMMAND echo "hello stdout"
OUTPUT_VARIABLE OUT
OUTPUT_QUIET
OUTPUT_FILE "${CMAKE_BINARY_DIR}/out.log"
)
message(STATUS "OUT变量: '${OUT}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
我们看看里面是什么东西?
很符合我们的预期了吧!
4.2.ERROR_QUIET
这个和上面的OUTPUT_QUIET很像,ERROR_QUIET 指的是:标准错误不会保存到变量,但如果你用了 ERROR_FILE
,还是会写到文件。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ErrorQuietDemo)
execute_process(
COMMAND ls not_exist_file
ERROR_VARIABLE ERR
ERROR_QUIET
ERROR_FILE "${CMAKE_BINARY_DIR}/err.log"
)
message(STATUS "ERR变量: '${ERR}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
接下来我们看看build目录里面有什么
我们看看里面是什么东西?
很符合我们的预期了吧!
五.ECHO_OUTPUT_VARIABLE, ECHO_ERROR_VARIABLE
(3.18版本新增功能)
ECHO_OUTPUT_VARIABLE
和 ECHO_ERROR_VARIABLE
是在 配置阶段 把子进程的输出直接打印到 CMake 自己的终端里(就像 message(STATUS ...)
那样)。
它们不会影响变量本身是否保存内容。
也就是说
它们的作用正是在配置阶段(即 cmake
命令运行期间),将子进程的标准输出(stdout)或标准错误(stderr)同时打印到 CMake 自己的终端上,并且同时捕获到指定的变量中。
这就像是 Unix 命令中的 tee
,一份数据,两个目的地:屏幕和变量。
5.1.ECHO_OUTPUT_VARIABLE
直接看例子吧
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(EchoOutputDemo)
execute_process(
COMMAND echo "Hello from stdout"
OUTPUT_VARIABLE OUT
ECHO_OUTPUT_VARIABLE
)
message("message打印的OUT变量: '${OUT}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
效果很明显了吧。
5.2.ECHO_ERROR_VARIABLE
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(EchoErrorDemo)
execute_process(
COMMAND ls not_exist_file
ERROR_VARIABLE ERR
ECHO_ERROR_VARIABLE
)
message(STATUS "ERR变量: '${ERR}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
效果还是很明显的。
六.WORKING_DIRECTORY
将指定目录设为子进程的当前工作目录。
我们直接看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(WorkingDirCompareDemo)
# 在 build 目录里创建两个子目录,并写入不同的文件
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/dir1")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/dir2")
file(WRITE "${CMAKE_BINARY_DIR}/dir1/file1.txt" "This is file1\n")
file(WRITE "${CMAKE_BINARY_DIR}/dir2/file2.txt" "This is file2\n")
# 在 dir1 下执行 ls
execute_process(
COMMAND ls
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/dir1"
OUTPUT_VARIABLE OUT1
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 在 dir2 下执行 ls
execute_process(
COMMAND ls
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/dir2"
OUTPUT_VARIABLE OUT2
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 在 build 根目录下执行 ls
execute_process(
COMMAND ls
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
OUTPUT_VARIABLE OUT3
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "dir1 下的内容: '${OUT1}'")
message(STATUS "dir2 下的内容: '${OUT2}'")
message(STATUS "build 根目录下的内容: '${OUT3}'")
我们现在来构建项目
mkdir build && cd build && cmake ..
还是很符合我们的预期的吧!!
七.TIMEOUT
超过指定秒数(允许小数)后,所有未完成的子进程将被终止,且RESULT_VARIABLE将被设置为包含"timeout"提示信息的字符串。
话不多说,我们直接看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(TimeoutDemo)
execute_process(
COMMAND sleep 5
TIMEOUT 2 # 设置超时时间为 2 秒
RESULT_VARIABLE RET
)
message("RESULT_VARIABLE的值是: ${RET}")
我们现在来构建项目
mkdir build && cd build && cmake ..
还是很符合我们的预期的吧!!