Shell指定行号搜索替换异常:所有设备配置被批量修改
问题排查:VPP启动配置文件批量修改错误问题
配置文件初始格式
dev 1 { #num-tx-queues 64 #num-rx-queues 64 } dev 2 { #num-tx-queues 64 #num-rx-queues 64 } ...... dev 8 { #num-tx-queues 64 #num-rx-queues 64 }
注:文件包含完整8个设备配置,省略号仅为排版简化,所有配置项默认处于注释状态。
问题现象
需要解析环境变量,仅修改指定dev块的配置值,但使用sed按行号替换后,所有dev块的对应配置都被修改为目标值,不符合预期:
dev 1 { num-tx-queues 72 num-rx-queues 72 } dev 2 { num-tx-queues 72 num-rx-queues 72 } ...... dev 8 { num-tx-queues 72 num-rx-queues 72 }
相关代码片段
function vpp_startup_file_update() { i=$1;shift echo "Inside vpp_startup_file_update" for param in $@ do name=${param%%=*} value=${param#*=} case $name in num-rx-queues) local line=$((i+2)) sed -i "$line, \$s/^ #${name} [0-9]*/ ${name} ${value}/" "$CONF_FILE_PATH" ;; num-tx-queues) local line=$((i+1)) sed -i "$line, \$s/^ #${name} [0-9]*/ ${name} ${value}/" "$CONF_FILE_PATH" ;; num-tx-desc) local line=$((i+3)) sed -i "$line, \$s/^ #${name} [0-9]*/ ${name} ${value}/" "$CONF_FILE_PATH" ;; num-rx-desc) local line=$((i+4)) sed -i "$line, \$s/^ #${name} [0-9]*/ ${name} ${value}/" "$CONF_FILE_PATH" ;; *) echo "Parameter : $name value : $value does not match any case" ;; esac done } vpp_dpdk_dev_conf="dev=name=a,pci=2,mtu=1400,num-rx-queues=72,num-tx-queues=72,num-rx-desc=8,num-tx-desc=8,skip=false|true" IFS=',' declare -a DEV=($vpp_dpdk_dev_conf) for i in ${DEV[@]}; do for param in $(IFS='=';echo $i); do name=${param%% *} value=${param#* } cout=1 case $name in pci) line_num=$(grep -Fn "dev $value" $CONF_FILE_PATH | cut --delimiter=":" --fields=1) vpp_startup_file_update $line_num "${DEV[@]}" break ;;
问题根源
- sed范围匹配错误:当前sed命令使用
$line, \$作为匹配范围,意思是从目标行开始到文件末尾,所有符合正则的行都会被替换,直接导致后续所有dev块的同名称配置项被批量修改。 - 参数传递逻辑错误:调用
vpp_startup_file_update时传入了${DEV[@]},会把所有dev的参数都传递进去,造成重复处理。 - 行号偏移不可靠:依赖固定行号偏移计算配置项位置,一旦配置文件格式变化(如空行、注释调整),偏移就会失效,鲁棒性极差。
优化解决方案
核心修复:限定sed仅修改目标dev块
利用sed的地址范围匹配,锁定目标dev块的起始(dev X {)和结束(})位置,只在该范围内执行替换:
# 以num-tx-queues为例,替换原sed命令 sed -i "/^dev $dev_num {/,/^}/s/^[[:space:]]*#${name} [0-9]*/ ${name} ${value}/" "$CONF_FILE_PATH"
重构后的完整代码
function vpp_startup_file_update() { local dev_num=$1 shift local conf_file="$CONF_FILE_PATH" for param in "$@"; do local name=${param%%=*} local value=${param#*=} case "$name" in num-rx-queues|num-tx-queues|num-tx-desc|num-rx-desc) # 仅在目标dev块内替换配置 sed -i "/^dev $dev_num {/,/^}/s/^[[:space:]]*#${name} [0-9]*/ ${name} ${value}/" "$conf_file" ;; *) echo "未知参数: $name = $value" ;; esac done } # 解析环境变量,按|分割多个dev配置 vpp_dpdk_dev_conf="dev=name=a,pci=2,mtu=1400,num-rx-queues=72,num-tx-queues=72,num-rx-desc=8,num-tx-desc=8,skip=false|dev=name=b,pci=3,num-rx-queues=64,num-tx-queues=64" CONF_FILE_PATH="/path/to/your/vpp/startup.conf" IFS='|' read -ra DEV_LIST <<< "$vpp_dpdk_dev_conf" for dev_conf in "${DEV_LIST[@]}"; do local dev_num="" local params=() IFS=',' read -ra DEV_PARAMS <<< "$dev_conf" for param in "${DEV_PARAMS[@]}"; do local name=${param%%=*} local value=${param#*=} if [[ "$name" == "pci" ]]; then dev_num="$value" else params+=("$param") fi done if [[ -n "$dev_num" ]]; then vpp_startup_file_update "$dev_num" "${params[@]}" fi done
额外优化建议
- 使用
[[:space:]]*匹配任意空白字符,避免依赖固定空格数量,适配不同格式的配置文件。 - 用
read -ra解析环境变量,替代直接赋值数组,避免空格等特殊字符导致的拆分错误。 - 增加dev编号校验,先检查配置文件中是否存在目标dev块,再执行修改,避免无效操作。
内容的提问来源于stack exchange,提问作者Niladri Chakraborty
相关产品推荐
相关产品推荐

