Shell Programming Techs I
记录阅读 MAGMA 源码时,遇到的一些高级shell编程技巧
Set
设置环境变量
set -a # 激活 Shell 的 allexport 模式。当 allexport 模式启用时,所有定义的变量(包括后续加载的变量)都会自动导出为环境变量。
source "$1" # 加载配置文件,将其中定义的变量和命令导入当前 Shell 环境。
set +a # 关闭 allexport 模式。
设置shell变量
MAGMA=${MAGMA:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/../../" >/dev/null 2>&1 && pwd)"}
${MAGMA:-value}: 表示如果环境变量 MAGMA 已经被定义,则保留其原值。如果 MAGMA 未被定义,则设置为后面指定的值。
$(...): 用于命令替换,表示运行括号中的命令并将其输出结果赋值给变量。
dirname "${BASH_SOURCE[0]}": 返回当前脚本文件所在的目录。${BASH_SOURCE[0]} 是当前脚本文件的路径。
>/dev/null 2>&1: 将标准输出重定向到 /dev/null,抑制命令的标准输出和错误输出。2>表示重定向标准错误。&1表示将标准错误的输出流合并到标准输出流中。避免命令的输出污染终端显示。仅判断命令是否执行成功(通过 $? 查看退出状态),不关心输出内容。
相应的有,&> /dev/null 抑制输出,避免干扰。
Operation
获取CPU列表
WORKERS_ALL=($(lscpu -b -p | sed '/^#/d' | sort -u -t, -k ${WORKER_MODE}g | cut -d, -f1))
lscpu -b -p: 列出 CPU 的简洁信息,以逗号分隔。
sed '/^#/d': 删除以 # 开头的注释行。
sort -u -t, -k ${WORKER_MODE}g: 按第 WORKER_MODE 列对数据进行排序并去重。
cut -d, -f1: 提取第一个字段(CPU 核心编号)。
(...): 将命令的输出分割为多个元素(基于空白符)。
确保绝对路径
WORKDIR="$(realpath "$WORKDIR")"
双引号用于避免路径中可能包含的空格或特殊字符导致问题。
$() 用于捕获子命令的输出。
安全删除文件
shopt -s nullglob # shopt 是 Bash 的一个内建命令,用于启用或禁用 shell 选项。nullglob 是一个特定的 shell 选项,其作用是控制文件名通配符(如 *)在没有匹配文件时的行为。启用时,文件名通配符在没有匹配文件时返回空字符串。启用 nullglob 确保目录 $LOCKDIR 中没有匹配文件时,通配符 * 不会被当作字符串 *,而是返回空。
rm -f "$LOCKDIR"/* # 如果 $LOCKDIR 是空目录,启用了 nullglob 的情况下,* 返回空,rm 不会执行删除操作,也不会报错。
shopt -u nullglob # 关闭 nullglob 后恢复 Bash 的默认行为,即文件名通配符在没有匹配文件时不返回空,而是原样保留。
排序操作
cids=($(sort -n < <(basename -a "${campaigns[@]}")))
sort -n: 按数字排序这些基名。
< <(...): 将子命令的输出重定向为标准输入流。
导出函数
export -f get_next_cid # 将函数导出为当前 shell 的子进程可用(例如在 bash 脚本或其他子 shell 中调用)。
清理临时文件
trap 'rm -f "$LOCKDIR/$mux"' EXIT
trap: 设置退出时的清理动作。捕获 EXIT 信号,当脚本或函数退出时(无论正常还是异常退出),都会执行指定命令。
移除参数
shift # 使用 shift 移除第一个参数,将后续参数(命令及其参数)从 $2..N 调整为 $1..N。
创建子shell
(
flock -xF 200 &> /dev/null
"${@}"
) 200>"$LOCKDIR/$mux"
使用 () 启动一个子 shell,确保锁的范围仅限于当前子 shell。
flock 是一个用于管理文件锁的工具。-x: 获取排他锁(独占锁),确保同一时间只有一个进程持有该锁。-F: 强制刷新文件,如果文件描述符无法获取锁时立即返回(不会阻塞)。
200>"$LOCKDIR/$mux" 将文件描述符 200 定向到 $LOCKDIR/$mux,该文件用于作为锁标识。
"${@}" 运行给定命令,@ 是传递给 mutex 函数的命令及其参数。
失败处理
|| if [ $? -eq $errno_lock ]; then
continue
fi
$? 表示前一个命令的退出状态码。
文件操作:防止覆盖已有文件
set -o noclobber # 确保文件创建是原子的(即如果文件已存在则创建失败)。
cut: 一个用于从文本中按字段或字符位置提取数据的工具
cut -d',' -f2- <<< $WORKERSET
# example
cut -d',' -f2- <<< "a,b,c"
echo "a,b,c" | cut -d',' -f2- # equivalent command
# output: a b c
-d',': 指定逗号 , 为字段分隔符。
-f2-: 提取从第 2 个字段开始的所有字段。
后台任务处理
for job in `jobs -p`; do
if ! wait $job; then
continue
fi
done
jobs -p 列出当前 Shell 中的所有后台任务的进程 ID(PID)。
wait $job 等待后台任务完成。如果任务成功完成,wait 返回 0;否则返回非零状态码。
遍历目录下所有文件
find "$LOCKDIR" -type f | while read lock; do
if inotifywait -qq -e delete_self "$lock" &> /dev/null; then
continue
fi
done
find "$LOCKDIR" -type f 找出 $LOCKDIR 所有文件
while read lock 逐个遍历文件设为 lock
变量获取
set -- "DEFAULT" "${@:2}"
set -- "${@:2}" # 删除第一个参数(包括 DEFAULT),使用剩余参数。
"DEFAULT" 替换第一个参数,生成新的参数列表。"${@:2}" 代表原参数的第 2 个及之后的部分。
Array
数组切片
WORKER_POOL="${WORKERS_ALL[@]:0:WORKERS}"
[@] 表示操作数组的所有元素。如果只用 ${#WORKERS_ALL},则表示字符串长度,而不是数组元素数量。${#array[@]} 语法只能用于明确声明为数组的变量。
[@]:start:length 是 Bash 中的数组切片语法,用于提取数组的一部分。start 表示起始索引(从 0 开始),length 表示要提取的元素个数。
"${...}" 将数组切片的结果(即前 WORKERS 个元素)合并成一个字符串,元素之间用空格分隔。
使用 read 将字符串解析为数组
IFS=',' # 设置字段分隔符
read -a workers <<< "$AFFINITY"
read -a: 读取输入并将其存储为一个数组。
<<< 是 Here String 的语法,用于将一个字符串作为命令的标准输入。
数组定义与解引用
name="${name}[@]" # 将变量名作为数组处理
value="${!name}" # 间接引用变量 name 的值
Functions
将传入的参数用指定分隔符连接成一个字符串
function join_by { local IFS="$1"; shift; echo "$*"; } # 定义分隔符为"$1",移除第一个参数,打印每个参数
pattern=$(join_by _ "${@}") # ${@} 包含传入的所有参数