【CMake】执行外部命令1——execute_process()

目录

一.标准输出,标准错误,退出码

1.1.执行单条命令的情况

1.1.1.标准输出

1.1.2.标准错误

1.1.3.退出码

1.2.执行多条命令的情况

1.2.1.标准输出

1.2.2.标准错误

1.1.3.退出码

二.执行多条命令的机制

2.1.基本说明

2.2.示例1——各个命令存在管道机制

2.3.示例2——各个命令独立执行

2.4.示例3——错误机制(各个命令直接本就存在管道关系)

2.4.1.命令不存在

2.4.2.命令存在,但是退出码!=0

2.5.示例4——错误机制(各个命令都可独立执行)

2.3.1.命令不存在

2.3.2.命令存在,但是退出码!=0

2.6.错误机制总结

三. 输入,输出,错误重定向

3.1.输入重定向——INPUT_FILE 

3.1.1.执行单条命令

3.1.2.执行多条命令

3.2.输出重定向

3.2.1.执行单条命令

3.2.2.执行多条命令

3.3.错误重定向

3.3.1.执行不存在的命令

3.3.2.执行存在的命令

3.3.3.执行多条命令

四. OUTPUT_QUIET, ERROR_QUIET  

4.1.OUTPUT_QUIET

4.2.ERROR_QUIET  

五.ECHO_OUTPUT_VARIABLE, ECHO_ERROR_VARIABLE  

5.1.ECHO_OUTPUT_VARIABLE

5.2.ECHO_ERROR_VARIABLE  

六.WORKING_DIRECTORY  

七.TIMEOUT  


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> 可以是 STDERRSTDOUT, 或 NONE

    • 这极大地便利了调试,让你清楚地看到 CMake 实际执行了什么命令。

  • ENCODING <name> (CMake 3.8+)

    • 仅限 Windows。指定解码命令输出时使用的字符编码。

    • 选项:NONE (默认旧行为), AUTO (默认 3.15-3.30), ANSIOEMUTF-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.

翻译与解释:

该变量将被设置为包含最后一个子进程的运行结果

这个结果可能是以下两种之一:

  1. 一个整数返回码:通常来自最后一个命令。返回码为 0 通常表示成功,非 0 值表示错误(具体含义由被调用的命令决定)。

  2. 一个描述错误条件的字符串:这通常发生在进程根本无法启动的时候(例如,命令不存在)。此时,字符串会描述错误原因,如 "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,系统里并没有这个名字的可执行文件。

  1. 返回码 (RET): No such file or directory

    • 不是一个数字退出码(如 1 或 2),而是一个错误描述字符串

    • 原因RESULT_VARIABLE 被设置为一个字符串,通常发生在进程根本无法启动时。CMake 尝试调用 nonexistent_command,操作系统直接返回了一个错误:“找不到这个文件或目录”,CMake 捕获到这个系统错误信息并放入了 RET 变量。这与命令存在但执行失败(返回非零退出码)的情况完全不同。

  2. 标准输出 (OUT): '' (空字符串)

    • 这很符合逻辑。因为进程压根没能启动,所以自然没有任何内容被写入到标准输出管道。OUTPUT_VARIABLE 也就捕获不到任何东西。

  3. 错误输出 (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 OUTCMake 捕获最后一个命令 (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 ..

关键点分析

  1. 命令链行为:你使用了三个 COMMAND 参数,这意味着 CMake 会尝试将它们作为管道链执行,相当于在 shell 中运行 cat input.txt | nonexistent_command | wc -l

  2. 中间命令失败:第二个命令 nonexistent_command 不存在,这是一个致命错误,会导致整个命令链在这一点上立即失败并中止

  3. 输出结果为空 ('')

    • 因为命令链在 nonexistent_command 处就失败了,第三个命令 wc -l 根本没有被执行

    • OUTPUT_VARIABLE 捕获的是最后一个命令 (wc -l) 的标准输出。既然 wc -l 从未运行,自然没有任何输出被捕获。

  4. 错误输出为空 ('')

    • 这有点反直觉。你可能会期望看到 "bash: nonexistent_command: command not found" 这样的错误信息。

    • 原因是:ERROR_VARIABLE 捕获的是最后一个命令的标准错误。由于最后一个命令 (wc -l) 根本没有运行,它没有产生任何标准错误。

    • 而 nonexistent_command 产生的错误信息属于中间命令,在管道链的设置中,这些中间错误通常不会通过 ERROR_VARIABLE 暴露给 CMake,而是直接导致进程失败。错误信息可能被打印到了终端(但被 CMake 的日志淹没),或者被系统丢弃了。

  5. 返回码分析

    • 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 参数仍然会形成管道链,而不是独立执行。

这与许多开发者的直觉相悖。他们可能期望:

  1. 三个命令独立执行

  2. 分别捕获每个命令的输出和错误

  3. 分别获取每个命令的返回码

但实际上,execute_process() 的设计始终将这些命令视为管道链的一部分。

也就是说

  1. 命令链而非独立执行:尽管这三个命令看起来是独立的(它们之间没有数据依赖),但 execute_process() 仍然将它们作为管道链处理,相当于在 shell 中执行 echo "第一条命令" | nonexistent_command | echo "第三条命令"

  2. 中间命令失败导致链中断:第二个命令 nonexistent_command 不存在,这是一个致命错误。与管道操作一致,整个命令链在此处立即中止,第三个命令 echo "第三条命令" 根本没有被执行。

  3. 输出结果分析

    • 标准输出为空 ('')OUTPUT_VARIABLE 捕获的是最后一个命令 (echo "第三条命令") 的输出。由于该命令未执行,输出为空。

    • 错误输出为空 ('')ERROR_VARIABLE 捕获的是最后一个命令的错误输出。由于最后一个命令未执行,错误输出为空。中间命令 nonexistent_command 的错误信息没有被捕获到。

  4. 返回码分析

    • 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.txtcat < file.txt

  • COMMAND cat file.txtcat 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. 执行过程逐步展开

  1. INPUT_FILE 指定了输入文件 ${CMAKE_BINARY_DIR}/input.txt
    相当于给第一个命令 cat 执行时做了重定向:

    cat < "${CMAKE_BINARY_DIR}/input.txt"
    
  2. 第一个命令:

    cat
    
    • 由于 cat 默认从标准输入读取,而这里的标准输入就是 input.txt 文件内容,
      所以这一步就是 输出文件内容

  3. 第二个命令:

    grep Hello
    
    • 它会接收上一步 cat 的输出作为输入,并过滤出包含 "Hello" 的行。

  4. OUTPUT_VARIABLE OUT

    • 把最后一个命令(这里是 grep Hello)的标准输出捕获到 CMake 变量 OUT 中。

  5. 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_VARIABLEECHO_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 ..

还是很符合我们的预期的吧!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值