用bash封装C工具链 | Wrapping C Toolchains with Bash
章节概述
本章讲解如何用 Bash 脚本封装 C 开发工具链,涵盖自动编译脚本、测试框架封装、性能对比脚本、批量编译管理,以及 CMake/Makefile 的 Bash 封装方案。Bash 封装能大幅简化 C 项目的构建、测试和部署流程。
核心理念:C 负责性能,Bash 负责流程。让 Bash 成为 C 工具链的”指挥官”,统一管理编译、测试、部署的全流程。
第1节:自动编译脚本
基础自动编译
#!/usr/bin/env bash
set -euo pipefail
# 自动编译脚本
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SRC_DIR="${SCRIPT_DIR}/src"
BIN_DIR="${SCRIPT_DIR}/bin"
BUILD_DIR="${SCRIPT_DIR}/build"
# 创建目录
mkdir -p "${BIN_DIR}" "${BUILD_DIR}"
# 编译单个文件
compile_file() {
local src="${1}"
local obj="${2}"
if [ "${src}" -ot "${obj}" ] 2>/dev/null; then
return 0
fi
echo "Compiling: ${src}"
gcc -Wall -O2 -c -o "${obj}" "${src}"
}
# 链接程序
link_program() {
local objs=("$@")
local output="${BIN_DIR}/$(basename "${objs[0]}" .o)"
echo "Linking: ${output}"
gcc -o "${output}" "${objs[@]}"
echo "Built: ${output}"
}
# 编译所有源文件
build_all() {
local sources=("${SRC_DIR}"/*.c)
local objs=()
for src in "${sources[@]}"; do
local obj="${BUILD_DIR}/$(basename "${src}" .c).o"
compile_file "${src}" "${obj}"
objs+=("${obj}")
done
link_program "${objs[@]}"
}
build_all增量编译脚本
#!/usr/bin/env bash
set -euo pipefail
# 智能增量编译
needs_rebuild() {
local src="${1}"
local bin="${2}"
[ ! -f "${bin}" ] && return 0
[ "${src}" -nt "${bin}" ] && return 0
return 1
}
# 自动检测依赖变化
check_header_deps() {
local src="${1}"
local deps=()
# 提取 #include 的头文件
while IFS= read -r header; do
local dep_file
dep_file=$(find /usr/include "${SRC_DIR}" -name "${header}" 2>/dev/null | head -1)
[ -n "${dep_file}" ] && deps+=("${dep_file}")
done < <(grep -oE '#include [<"](.+)[>"]' "${src}" | cut -d'"' -f2 | cut -d'>' -f1)
echo "${deps[@]}"
}
# 智能编译
smart_build() {
local src="${1}"
local bin="${2}"
# 检查源文件是否需要重新编译
if needs_rebuild "${src}" "${bin}"; then
echo "Rebuilding: ${src}"
gcc -Wall -O2 -o "${bin}" "${src}"
return 0
fi
# 检查头文件依赖
local headers
headers=$(check_header_deps "${src}")
for header in ${headers}; do
if [ "${header}" -nt "${bin}" ] 2>/dev/null; then
echo "Rebuilding (header changed): ${src}"
gcc -Wall -O2 -o "${bin}" "${src}"
return 0
fi
done
echo "Up to date: ${bin}"
}第2节:测试框架封装
单元测试框架
#!/usr/bin/env bash
set -euo pipefail
# 测试框架
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/tests"
BIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/bin"
# 测试计数器
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
# 测试函数
run_test() {
local name="${1}"
local command="${2}"
local expected="${3}"
TESTS_RUN=$((TESTS_RUN + 1))
local actual
actual=$(eval "${command}" 2>&1)
local exit_code=$?
if [ "${exit_code}" -eq 0 ] && [ "${actual}" = "${expected}" ]; then
TESTS_PASSED=$((TESTS_PASSED + 1))
echo "PASS: ${name}"
else
TESTS_FAILED=$((TESTS_FAILED + 1))
echo "FAIL: ${name}"
echo " Expected: ${expected}"
echo " Actual: ${actual}"
echo " Exit: ${exit_code}"
fi
}
# 测试退出码
run_test_exit() {
local name="${1}"
local command="${2}"
local expected_exit="${3}"
TESTS_RUN=$((TESTS_RUN + 1))
eval "${command}" >/dev/null 2>&1
local actual_exit=$?
if [ "${actual_exit}" -eq "${expected_exit}" ]; then
TESTS_PASSED=$((TESTS_PASSED + 1))
echo "PASS: ${name}"
else
TESTS_FAILED=$((TESTS_FAILED + 1))
echo "FAIL: ${name}"
echo " Expected exit: ${expected_exit}"
echo " Actual exit: ${actual_exit}"
fi
}
# 测试结果汇总
print_results() {
echo ""
echo "========================================="
echo "Tests run: ${TESTS_RUN}"
echo "Passed: ${TESTS_PASSED}"
echo "Failed: ${TESTS_FAILED}"
echo "========================================="
if [ "${TESTS_FAILED}" -gt 0 ]; then
return 1
fi
return 0
}
# 运行测试
test_basic() {
echo "Running basic tests..."
run_test "echo works" "echo hello" "hello"
run_test "math works" "echo \$((2 + 2))" "4"
run_test_exit "exit code 0" "true" 0
run_test_exit "exit code 1" "false" 1
}
test_program() {
echo "Running program tests..."
run_test "program help" "${BIN_DIR}/myprogram --help" "Usage: myprogram"
run_test "program version" "${BIN_DIR}/myprogram --version" "1.0.0"
}
# 主函数
main() {
test_basic
test_program
print_results
}
main "$@"测试文件组织
# tests/test_math.c
#include <assert.h>
#include "math.h"
void test_add() {
assert(add(2, 3) == 5);
}
void test_multiply() {
assert(multiply(2, 3) == 6);
}
int main() {
test_add();
test_multiply();
printf("All tests passed!\n");
return 0;
}# 编译并运行测试
gcc -o test_math tests/test_math.c src/math.c
./test_math第3节:性能对比脚本
基准测试框架
#!/usr/bin/env bash
set -euo pipefail
# 性能测试框架
benchmark() {
local name="${1}"
local command="${2}"
local iterations="${3:-100}"
echo "Benchmarking: ${name}"
echo "Iterations: ${iterations}"
local start_time end_time elapsed
start_time=$(date +%s%N)
for ((i = 0; i < iterations; i++)); do
eval "${command}" >/dev/null
done
end_time=$(date +%s%N)
elapsed=$(( (end_time - start_time) / 1000000 ))
echo "Total time: ${elapsed}ms"
echo "Average: $((elapsed / iterations))ms"
echo ""
}
# 对比测试
compare() {
local name1="${1}"
local cmd1="${2}"
local name2="${3}"
local cmd2="${4}"
local iterations="${5:-100}"
echo "=== Performance Comparison ==="
echo ""
local time1 time2
# 测试第一个
local start=$(date +%s%N)
for ((i = 0; i < iterations; i++)); do
eval "${cmd1}" >/dev/null
done
local end=$(date +%s%N)
time1=$(( (end - start) / 1000000 ))
# 测试第二个
start=$(date +%s%N)
for ((i = 0; i < iterations; i++)); do
eval "${cmd2}" >/dev/null
done
end=$(date +%s%N)
time2=$(( (end - start) / 1000000 ))
# 输出结果
printf "%-20s %8d ms\n" "${name1}" "${time1}"
printf "%-20s %8d ms\n" "${name2}" "${time2}"
if [ "${time1}" -lt "${time2}" ]; then
local faster=$(( (time2 - time1) * 100 / time2 ))
printf "%-20s %d%% faster\n" "${name1}" "${faster}"
else
local faster=$(( (time1 - time2) * 100 / time1 ))
printf "%-20s %d%% faster\n" "${name2}" "${faster}"
fi
}
# 使用示例
compare "Bash sort" "echo -e '3\n1\n2' | sort" "C qsort" "./benchmark_qsort"第4节:批量编译管理
多平台批量编译
#!/usr/bin/env bash
set -euo pipefail
# 批量编译脚本
BUILD_DIR="build"
SRC_FILE="main.c"
# 目标平台
PLATFORMS=(
"linux:x86_64-linux-gnu-gcc"
"linux:aarch64-linux-gnu-gcc"
"macos:clang"
"windows:x86_64-w64-mingw32-gcc"
)
# 创建输出目录
mkdir -p "${BUILD_DIR}"
# 批量编译
for platform in "${PLATFORMS[@]}"; do
IFS=':' read -r os compiler <<< "${platform}"
echo "Building for ${os}..."
output="${BUILD_DIR}/program-${os}"
if [ "${os}" = "windows" ]; then
output="${output}.exe"
fi
${compiler} -O2 -Wall -o "${output}" "${SRC_FILE}"
if [ $? -eq 0 ]; then
echo " Success: ${output}"
else
echo " Failed: ${output}" >&2
fi
done
echo "Build complete!"
ls -la "${BUILD_DIR}/"版本管理编译
#!/usr/bin/env bash
set -euo pipefail
# 从 Git 标签获取版本
get_version() {
local version
version=$(git describe --tags --always 2>/dev/null || echo "unknown")
echo "${version}"
}
# 编译时注入版本信息
compile_with_version() {
local version
version=$(get_version)
echo "Compiling version: ${version}"
gcc -o myprogram -DVERSION="${version}" -DBUILD_DATE="$(date +%Y-%m-%d)" main.c
}
# 生成版本文件
generate_version_info() {
local version
version=$(get_version)
cat > version.h << EOF
#ifndef VERSION_H
#define VERSION_H
#define VERSION "${version}"
#define BUILD_DATE "$(date +%Y-%m-%d)"
#define BUILD_TIME "$(date +%H:%M:%S)"
#endif
EOF
}
generate_version_info
compile_with_version第5节:CMake/Makefile的Bash封装
CMake 封装脚本
#!/usr/bin/env bash
set -euo pipefail
# CMake 构建封装
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${PROJECT_DIR}/build"
INSTALL_DIR="${PROJECT_DIR}/install"
# 清理
clean() {
echo "Cleaning build directory..."
rm -rf "${BUILD_DIR}"
mkdir -p "${BUILD_DIR}"
}
# 配置
configure() {
local build_type="${1:-Release}"
echo "Configuring CMake (type: ${build_type})..."
cmake -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE="${build_type}" -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" -DBUILD_TESTING=ON "${PROJECT_DIR}"
}
# 编译
build() {
local jobs="${1:-$(nproc)}"
echo "Building with ${jobs} jobs..."
cmake --build "${BUILD_DIR}" --parallel "${jobs}"
}
# 安装
install() {
echo "Installing..."
cmake --install "${BUILD_DIR}"
}
# 运行测试
test() {
echo "Running tests..."
cmake --build "${BUILD_DIR}" --target test
}
# 打包
package() {
echo "Creating package..."
cd "${BUILD_DIR}"
cpack -G TGZ
}
# 主函数
main() {
local action="${1:-build}"
case "${action}" in
clean) clean ;;
config) configure "${2:-Release}" ;;
build) build "${2:-$(nproc)}" ;;
install) install ;;
test) test ;;
package) package ;;
all)
clean
configure
build
test
;;
*)
echo "Usage: $0 {clean|config|build|install|test|package|all}"
exit 1
;;
esac
}
main "$@"Makefile 封装
#!/usr/bin/env bash
set -euo pipefail
# Makefile 封装
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 检查 Makefile 是否存在
if [ ! -f "${PROJECT_DIR}/Makefile" ]; then
echo "Error: Makefile not found" >&2
exit 1
fi
# 封装 make 命令
make_target() {
local target="${1:-all}"
local jobs="${2:-$(nproc)}"
echo "Running make ${target} with ${jobs} jobs..."
make -C "${PROJECT_DIR}" -j "${jobs}" "${target}"
}
# 自动检测最优 job 数
optimal_jobs() {
local cpu_count
cpu_count=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
echo $((cpu_count + 1))
}
# 主函数
main() {
local action="${1:-build}"
case "${action}" in
build) make_target "all" "$(optimal_jobs)" ;;
clean) make_target "clean" ;;
test) make_target "test" ;;
install) make_target "install" ;;
reinstall) make_target "clean" && make_target "install" ;;
*)
echo "Usage: $0 {build|clean|test|install|reinstall}"
exit 1
;;
esac
}
main "$@"第6节:完整的C项目Bash工具链
项目脚手架
#!/usr/bin/env bash
set -euo pipefail
# C 项目脚手架
create_project() {
local name="${1}"
mkdir -p "${name}"/{src,include,tests,build,bin,docs}
# 创建基础文件
cat > "${name}/Makefile" << 'EOF'
CC = gcc
CFLAGS = -Wall -Wextra -O2
SRC = $(wildcard src/*.c)
OBJ = $(SRC:.c=.o)
BIN = bin/program
all: $(BIN)
$(BIN): $(OBJ) | bin
$(CC) $(CFLAGS) -o $@ $^
src/%.o: src/%.c | build
$(CC) $(CFLAGS) -c -o $@ $<
bin build:
mkdir -p $@
clean:
rm -rf build bin src/*.o
.PHONY: all clean
EOF
cat > "${name}/src/main.c" << 'EOF'
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Hello from %s!\n", argv[0]);
return 0;
}
EOF
echo "Project '${name}' created!"
echo " cd ${name}"
echo " make"
echo " ./bin/program"
}
# 使用
create_project "my_project"自动化工作流
#!/usr/bin/env bash
set -euo pipefail
# 开发工作流
dev_workflow() {
echo "=== Development Workflow ==="
# 1. 代码检查
echo "Step 1: Code linting..."
if command -v cppcheck &>/dev/null; then
cppcheck --enable=all src/
fi
# 2. 编译
echo "Step 2: Building..."
make clean && make
# 3. 测试
echo "Step 3: Testing..."
make test
# 4. 性能检查
echo "Step 4: Performance..."
if [ -f benchmarks/run.sh ]; then
benchmarks/run.sh
fi
echo "=== Workflow Complete ==="
}
dev_workflow本节帮助你构建完整的 Bash 封装 C 工具链,实现自动化、标准化的 C 项目开发流程。