Shell脚本中$!返回‘not found’错误,如何正确传递PID?
解决Shell脚本监控进程时长、CPU及内存占用的PID错误问题
问题背景
想要监控编译命令的时长、CPU及内存占用,计划通过$!获取命令PID实现。用sleep测试脚本时,出现1209: not found、PID: not found等错误,尝试pgrep后问题依旧,需要解决变量传递和脚本错误。
测试脚本原代码:
#!/bin/sh FILE_EXTENSION="$(date +\"%d-%m-%Y-%T\")" TIME_FILE_NAME=outout/test_duration_$FILE_EXTENSION.txt RESOURCES_FILE_NAME=output/test_internal_resources$FILE_EXTENSION.csv monitorResources(){ PID=$1 echo "the pid i found is $($PID)" mkdir -p output echo "TIME_STAMP, Usage%, Memory Usage (MB)" > "$RESOURCES_FILE_NAME" TOTAL="$(free -m | grep Mem | tr -s ' ' | cut -d ' ' -f 2)" while ps -p "$PID" > /dev/null do logResources "$PID" "$TOTAL" done } logResources(){ PID=$1 TOTAL=$2 echo "logging procces $(PID)" DATE=$(date +\"%H:%M:%S:%s%:z\") echo "$DATE, " >> "$RESOURCES_FILE_NAME" top -b -n 1 -p "$PID" | tr -s ' ' | cut -d ' ' -f 10 >> "$RESOURCES_FILE_NAME" ##VAR="$(top -b -n 1 -p $PID | tr -s ' ' | cut -d ' ' -f 11)" ##echo "scale=3; ($VAR*$TOTAL/100)" | bc >> $RESOURCES_FILE_NAME sleep 1 } ##cd /php-src ##{ time make -j $(nproc) ; } > $TIME_FILE_NAME 2>&1 & sleep 5 & monitorResources $! wait cat "$TIME_FILE_NAME" cat "$RESOURCES_FILE_NAME"
执行报错:
./run_test.sh: 1: 1209: not found ./run_test.sh: 1: the: not found ./run_test.sh: 22: PID: not found logging procces ./run_test.sh: 22: PID: not found logging procces
错误分析
- 错误的命令执行语法:
$($PID)和$(PID)是把变量内容当作命令执行,而非引用变量值。比如$($PID)会把PID数字(如1209)当成命令去运行,自然会报"1209: not found";$(PID)会尝试执行PID命令,所以报"PID: not found"。正确引用变量应该用$PID。 - 路径拼写错误:
TIME_FILE_NAME中的outout是拼写错误,应为output,否则后续cat会找不到文件。 - 多余的转义引号:
date +\"%d-%m-%Y-%T\"中的转义引号会导致文件名包含双引号,应该直接写date +"%d-%m-%Y-%T"(双引号内不需要转义)。 top字段不可靠:不同系统的top输出字段位置可能不同,用ps获取进程资源更稳定。- 内存计算未完善:注释的内存计算部分可以修复,实现实际内存占用计算。
修复后的脚本
#!/bin/sh # 修复:去掉date命令的多余转义引号 FILE_EXTENSION="$(date +"%d-%m-%Y-%T")" # 修复:路径拼写错误outout→output TIME_FILE_NAME=output/test_duration_$FILE_EXTENSION.txt RESOURCES_FILE_NAME=output/test_internal_resources_$FILE_EXTENSION.csv monitorResources(){ PID=$1 # 修复:直接引用变量$PID,而非执行命令$($PID) echo "监控的PID是 $PID" mkdir -p output echo "TIME_STAMP, CPU_Usage%, Memory_Usage_MB" > "$RESOURCES_FILE_NAME" # 简化内存总容量获取 TOTAL_MEM=$(free -m | awk '/Mem:/ {print $2}') while ps -p "$PID" > /dev/null do logResources "$PID" "$TOTAL_MEM" sleep 1 done } logResources(){ PID=$1 TOTAL_MEM=$2 # 修复:引用变量$PID,而非执行命令$(PID) echo "正在记录进程 $PID 的资源使用" # 修复:去掉date命令的多余转义引号 TIMESTAMP=$(date +"%H:%M:%S %z") # 用ps命令获取CPU和内存占比,比top更稳定 # ps输出格式:%cpu %mem PROC_STATS=$(ps -p "$PID" -o %cpu=,%mem= | tr -s ' ') CPU_USAGE=$(echo "$PROC_STATS" | cut -d ' ' -f 1) MEM_PERCENT=$(echo "$PROC_STATS" | cut -d ' ' -f 2) # 计算实际内存占用(MB) MEM_USAGE=$(echo "scale=1; $MEM_PERCENT * $TOTAL_MEM / 100" | bc) # 将所有数据写入一行,避免换行问题 echo "$TIMESTAMP, $CPU_USAGE, $MEM_USAGE" >> "$RESOURCES_FILE_NAME" } # 测试用sleep,替换为实际编译命令即可 sleep 5 & # 传递PID给监控函数 monitorResources "$!" wait echo "=== 执行时长 ===" cat "$TIME_FILE_NAME" echo "=== 资源使用记录 ===" cat "$RESOURCES_FILE_NAME"
关键修改说明
- 所有变量引用统一使用
$PID,杜绝把变量当作命令执行的错误。 - 修正路径拼写和
date命令的引号问题,避免文件名异常。 - 用
ps替代top获取进程资源,输出格式更稳定,跨系统兼容性更好。 - 完善内存占用计算逻辑,直接输出一行完整数据,避免文件内容换行混乱。
- 优化变量命名,增强可读性(如
TOTAL→TOTAL_MEM,DATE→TIMESTAMP)。
内容的提问来源于stack exchange,提问作者Tibo G.
相关产品推荐
相关产品推荐

