Bash中for与while循环嵌套实现区块数对比的问题排查
问题排查:Bash脚本实现区块数对比更新失败
让我来帮你排查这两次嵌套尝试的问题,顺便给你一个可行的修复方案~
需求回顾
我需要编写Bash脚本实现以下功能:
- 遍历
/usr/local/bin/下所有*-cli命令,执行getblockcount获取当前区块数 - 读取
blocks.log文件,将当前区块数与文件中对应币种的记录对比:- 若文件无该币种记录则添加
- 若有则在当前区块数更大时更新记录
已有可用代码
遍历获取区块数的for循环
这段代码能正确遍历所有cli命令,提取币种名并输出区块数:
#!/bin/bash wallets=`find /usr/local/bin/ -iname '*-cli'` for i in $wallets; do current_blocks=`$i getblockcount` coin_name=${i:15:-4} # 截取路径与后缀,如将/usr/local/bin/bitcoin-cli转为bitcoin echo $coin_name $current_blocks echo $coin_name $current_blocks >> blocks.log done
执行输出示例:
bitcoin 1457824 litecoin 759345
读取日志的while循环
这段代码能正确读取blocks.log的每一行:
while read line ; do set $line echo $1 $2 done < blocks.log
两次嵌套尝试的错误分析
第一次尝试的问题
for i in $wallets; do current_blocks=`$i getblockcount` coin_name=${i:15:-4} while read line; do set $line if [ "$1" == "$coin_name" ]; then echo "File contains the coin_name, compare the blocks now" if (( "$current_blocks" >= "$2" )); then echo "Current blocks are greater than the saved blocks" echo "Saving the new blocks count now" sed -i "s/$1/$1 $current_blocks/" blocks.log else echo "Current blocks are less than or equals to saved blocks" fi else echo "File does not contain the coin_name, adding it now" echo "$coin_name $current_blocks" >> blocks.log fi done < blocks.log done
这里有三个核心问题:
- 重复读取与重复添加:每次外层for循环都会从头读取整个
blocks.log,文件里每一条不匹配当前币种的记录都会触发一次else分支,导致同一个币种被多次追加到日志里。 - sed替换语法错误:
s/$1/$1 $current_blocks/会把所有包含币种名的文本替换,比如原行是bitcoin 1457823,替换后会变成bitcoin 1457824 1457823,完全不符合预期。正确的替换应该是匹配整行开头的币种名,替换整行的区块数。 - 读写文件冲突:在while循环读取
blocks.log的同时用sed -i修改它,会导致文件指针混乱,读取的内容可能不是最新的,甚至出现读取异常。
第二次尝试的问题
for i in $wallets; do current_blocks=`$i getblockcount` coin_name=${i:15:-4} read line < blocks.log set $line if [ "$1" == "$coin_name" ]; then echo "File contains the coin_name, compare the blocks now" if (( "$current_blocks" >= "$2" )); then echo "Current blocks are greater than the saved blocks" echo "Saving the new blocks count now" # sed -i "s/$1/$1 $current_blocks/" blocks.log else echo "Current blocks are less than or equals to saved blocks" fi else echo "File does not contain the coin_name, adding it now" echo "$coin_name $current_blocks" >> blocks.log fi done
这里的核心问题是只读取了日志的第一行:read line < blocks.log每次只会读取文件的第一行,所以只有第一个币种能被正确检查,后续的币种即使已经存在于日志中,也会被误判为不存在而重复添加。
修复后的可行方案
我们可以简化逻辑,不需要嵌套循环,而是用grep先检查币种是否存在,再做对应的操作,同时用临时文件避免读写冲突:
#!/bin/bash LOG_FILE="blocks.log" # 确保日志文件存在,避免首次运行报错 touch "$LOG_FILE" # 遍历所有*-cli命令(用通配符更简洁,替代find) for cli_path in /usr/local/bin/*-cli; do # 跳过非文件(比如目录,虽然通配符一般不会返回,但做个保险) [ -f "$cli_path" ] || continue # 获取当前区块数和币种名(用更健壮的字符串截取,适配不同路径长度) current_blocks=$("$cli_path" getblockcount) coin_name=${cli_path##*/} # 提取文件名,比如bitcoin-cli coin_name=${coin_name%-cli} # 去掉-cli后缀,得到bitcoin # 检查日志中是否存在该币种的记录(^$coin_name 确保匹配整行开头,避免部分匹配) if grep -q "^$coin_name " "$LOG_FILE"; then # 提取已保存的区块数 saved_blocks=$(grep "^$coin_name " "$LOG_FILE" | awk '{print $2}') # 对比区块数 if (( current_blocks > saved_blocks )); then echo "更新币种 $coin_name:区块数从 $saved_blocks 变为 $current_blocks" # 使用临时文件替换,避免直接修改原文件的读写冲突 sed "s/^$coin_name .*/$coin_name $current_blocks/" "$LOG_FILE" > "$LOG_FILE.tmp" mv "$LOG_FILE.tmp" "$LOG_FILE" else echo "币种 $coin_name:当前区块数 $current_blocks 不大于已保存的 $saved_blocks,无需更新" fi else # 不存在则追加记录 echo "添加新币种 $coin_name,区块数 $current_blocks" echo "$coin_name $current_blocks" >> "$LOG_FILE" fi done
代码说明
- 更健壮的币种名提取:用
${cli_path##*/}和${coin_name%-cli}替代固定长度截取,即使路径长度变化也能正常工作。 - 精准的存在性检查:
grep -q "^$coin_name "确保匹配整行开头的币种名,避免类似bitcoin和bitcoincash的混淆。 - 安全的文件修改:先用sed生成临时文件,再替换原文件,彻底避免读写原文件时的冲突。
内容的提问来源于stack exchange,提问作者Marry Jane
相关产品推荐
相关产品推荐

