6、数学与字符串:math命令、string命令(TOLOWER、TOUPPER、REPLACE、REGEX等)、文件路径操作
说实话,CMake 里处理数学和字符串,很多人一开始都觉得「这有啥好学的?不就是算个数、拼个串吗?」。嗯,我当年也是这么想的。直到有一次,我需要根据版本号动态生成库文件名,还要处理不同平台下的路径分隔符……那一周我几乎把 CMake 文档翻烂了。
今天咱们就把 math()、string() 和文件路径操作这几个硬骨头啃下来。你学完之后,处理版本号、拼接路径、做条件判断,都会顺手很多。
6.1 数学运算:math 命令
math() 命令在 CMake 里做整数运算。别指望它做浮点——CMake 的数学就是整数世界。我个人习惯用它来计算循环次数、版本号递增、或者做位掩码。
基本语法:
math(EXPR <output_variable> <expression>)
举个例子:
set(A 10)
set(B 3)
math(EXPR RESULT "${A} + ${B} * 2")
message("结果: ${RESULT}") # 输出: 16
支持的运算符:+ - * / % | & ^ ~ << >>
实际项目里,我常用它来做版本号递增:
set(VERSION_MAJOR 2)
set(VERSION_MINOR 1)
set(VERSION_PATCH 5)
# 打补丁版本 +1
math(EXPR VERSION_PATCH "${VERSION_PATCH} + 1")
message("新版本: ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
6.2 字符串处理:string 命令全家桶
string() 是 CMake 里最常用的命令之一。我敢说,你写的每个 CMakeLists.txt 里几乎都会用到它。咱们挑几个最实用的来讲。
6.2.1 大小写转换:TOLOWER / TOUPPER
这个太常用了。比如用户输入的选项可能是 ON、on、On,你统一转成大写再比较,省心很多。
set(INPUT "Hello World")
string(TOUPPER "${INPUT}" UPPER_STR)
string(TOLOWER "${INPUT}" LOWER_STR)
message("大写: ${UPPER_STR}") # HELLO WORLD
message("小写: ${LOWER_STR}") # hello world
6.2.2 字符串替换:REPLACE
替换子串,支持直接替换和正则替换。我经常用它来清理路径或者修改配置。
set(TEXT "Hello, World! Hello, CMake!")
string(REPLACE "Hello" "Hi" NEW_TEXT "${TEXT}")
message("替换后: ${NEW_TEXT}") # Hi, World! Hi, CMake!
只替换第一个?可以用正则:
string(REGEX REPLACE "^Hello" "Hi" NEW_TEXT "${TEXT}")
message("正则替换: ${NEW_TEXT}") # Hi, World! Hello, CMake!
6.2.3 正则匹配:REGEX MATCH / REGEX MATCHALL
这个功能我太爱了。比如从版本号里提取主版本、次版本:
set(VERSION "3.25.1-rc2")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)" MATCHED "${VERSION}")
message("匹配结果: ${MATCHED}") # 3.25
message("CMAKE_MATCH_1: ${CMAKE_MATCH_1}") # 3
message("CMAKE_MATCH_2: ${CMAKE_MATCH_2}") # 25
用 MATCHALL 可以匹配所有出现:
set(DATA "a1b2c3d4")
string(REGEX MATCHALL "[0-9]+" NUMBERS "${DATA}")
message("所有数字: ${NUMBERS}") # 1;2;3;4
\\\\ 或者直接用正斜杠。
6.2.4 其他常用操作
| 命令 | 作用 | 示例 |
|---|---|---|
| LENGTH | 获取字符串长度 | string(LENGTH "abc" LEN) → 3 |
| SUBSTRING | 截取子串 | string(SUBSTRING "abcdef" 1 3 SUB) → bcd |
| FIND | 查找子串位置 | string(FIND "hello" "ll" POS) → 2 |
| APPEND | 追加字符串 | string(APPEND STR " world") |
| PREPEND | 前置字符串 | string(PREPEND STR "hello ") |
| STRIP | 去除首尾空白 | string(STRIP " abc " S) → abc |
| COMPARE | 比较字符串 | string(COMPARE EQUAL "a" "b" RES) |
6.3 文件路径操作
跨平台项目里,路径处理是最容易出问题的。Windows 用反斜杠,Linux/macOS 用正斜杠。CMake 提供了一套路径操作命令,帮你统一处理。
6.3.1 路径拼接与规范化
set(BASE_DIR "/home/user/project")
set(SUB_DIR "src/core")
# 拼接路径
cmake_path(APPEND FULL_PATH "${BASE_DIR}" "${SUB_DIR}" "main.cpp")
message("完整路径: ${FULL_PATH}")
# 规范化(去掉多余的 . 和 ..)
cmake_path(NORMAL_PATH NORM_PATH "${FULL_PATH}/../include/../src/main.cpp")
message("规范化后: ${NORM_PATH}")
6.3.2 获取路径各部分
set(FILE_PATH "/home/user/project/src/main.cpp")
cmake_path(GET FILE_PATH PARENT_PATH DIR)
message("目录: ${DIR}") # /home/user/project/src
cmake_path(GET FILE_PATH FILENAME NAME)
message("文件名: ${NAME}") # main.cpp
cmake_path(GET FILE_PATH STEM STEM_NAME)
message("无扩展名: ${STEM_NAME}") # main
cmake_path(GET FILE_PATH EXTENSION EXT)
message("扩展名: ${EXT}") # .cpp
cmake_path(GET ... LAST_EXTENSION) 可以获取最后一个扩展名(比如 .tar.gz 会得到 .gz),而 EXTENSION 只取第一个点后面的。
6.3.3 相对路径与绝对路径
set(BASE "/home/user/project")
set(TARGET "/home/user/project/build/output/lib")
# 计算相对路径
cmake_path(RELATIVE_PATH TARGET BASE_DIR "${BASE}" OUTPUT_VAR REL)
message("相对路径: ${REL}") # build/output/lib
# 判断是否为绝对路径
cmake_path(IS_ABSOLUTE TARGET IS_ABS)
message("是绝对路径吗? ${IS_ABS}") # TRUE
6.3.4 老式路径命令(了解即可)
在 cmake_path() 出现之前,大家用 get_filename_component() 和 file(RELATIVE_PATH ...)。现在新项目建议直接用 cmake_path(),更直观。
# 老式写法(仍然可用)
get_filename_component(DIR "/home/user/file.txt" DIRECTORY)
message("目录: ${DIR}") # /home/user
6.4 知识体系总览
下面这张图帮你理清本章的知识结构:
6.5 实战:版本号处理与路径拼接
咱们来个综合例子。假设你要根据版本号生成动态库文件名,并放到指定输出目录:
# 版本号
set(VER_MAJOR 1)
set(VER_MINOR 2)
set(VER_PATCH 3)
# 生成版本字符串
string(CONCAT VERSION_STR "${VER_MAJOR}.${VER_MINOR}.${VER_PATCH}")
message("版本号: ${VERSION_STR}")
# 生成库文件名
set(LIB_NAME "mylib")
set(LIB_FILE_NAME "${LIB_NAME}.so.${VERSION_STR}")
message("库文件名: ${LIB_FILE_NAME}")
# 输出目录
set(OUTPUT_DIR "/opt/myapp/lib")
# 拼接完整路径
cmake_path(APPEND FULL_PATH "${OUTPUT_DIR}" "${LIB_FILE_NAME}")
message("完整路径: ${FULL_PATH}")
# 检查路径是否合法
cmake_path(IS_ABSOLUTE FULL_PATH IS_ABS)
if(NOT IS_ABS)
message(WARNING "路径不是绝对路径,请检查!")
endif()
这个例子涵盖了数学运算、字符串拼接、路径操作三个核心知识点。你在实际项目中,类似的场景会非常多。
math()只做整数运算,别拿它算浮点string()的 REGEX 功能很强大,但要注意转义- 路径操作优先用
cmake_path(),跨平台更省心 - 所有用户输入的字符串,先 TOUPPER 再比较,避免大小写问题
好了,这一章的内容就到这里。数学和字符串处理是 CMake 里的基本功,你多用几次就会形成肌肉记忆。下次遇到需要动态生成路径或者处理版本号的情况,直接翻出这章的例子来用就行。