You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bash脚本中sed输出含\r导致for循环异常的排查求助

问题:处理配置文件时提取的段名带\r字符导致脚本异常

问题描述

在Ubuntu 20.04.2 LTS环境下,使用Bash脚本处理my.config配置文件时,通过sed提取的配置段名末尾带有\r(用cat -A查看显示为^M),导致后续for循环调用count_items_in_segment函数出现异常。目前临时通过${var: 0: -1}截断去除\r,需要找出问题根源并获得更合理的解决办法。

环境信息

  • Kernel: 5.15.0-56-generic x86_64
  • 架构: 64位
  • 发行版: Ubuntu 20.04.2 LTS (Focal Fossa)
  • Shell: zsh

相关脚本

#!/bin/bash

input_file="my.config"

function get_all_segment
{
    echo `sed -n -E -e "/\[.*\]/p" $input_file | sed -E -e "s/\[|\]//g"`
}

function count_items_in_segment
{
    sed -n "/\[$1\]/,/\[.*\]/p" $input_file | sed -n -E -e "/^#/d" -e "/^\s+$/d" -e "/\[.*\]/d" -e "p" | wc -l
}

index=1
for seg in `get_all_segment`
do 
    echo "$index: $seg"
    echo `count_items_in_segment $seg`
    ((index++))
done

printf `get_all_segment`

配置文件内容

[client]
database =  test
user =  test
password = pwd
default-character-set = utf8
host = 192.168.0.1
port = 3306
# comment

[client1]
database =  test
default-character-set = utf8
host = 192.168.0.1
port = 3306

[client2]
database =  test
user =  test
default-character-set = utf8
host = 192.168.0.1
port = 3306
[client3]
database =  test

问题根源

这个\r是Windows风格换行符(CRLF,即\r\n)中的回车字符。你的配置文件大概率是在Windows环境下创建或编辑过的,而Linux系统默认使用LF(\n)作为换行符,所以Linux会把\r当成普通字符保留在文件中,最终被sed提取出来。

解决办法

1. 永久转换文件为Linux换行格式

直接修改配置文件,将Windows风格的CRLF转换为Linux风格的LF:

  • 使用dos2unix工具(未安装可执行sudo apt install dos2unix):
    dos2unix my.config
    
  • 无dos2unix时,用sed批量去除每行末尾的\r:
    sed -i 's/\r$//' my.config
    

2. 在提取段名时实时去除\r

修改get_all_segment函数,合并sed操作并同时去除\r,避免多余的命令替换:

function get_all_segment
{
    sed -n -E '/\[.*\]/s/\[|\]|\r//gp' "$input_file"
}

3. 优化脚本的可靠性(可选)

原脚本用for seg in get_all_segment``的方式会导致空格合并,改用while read循环更可靠;同时用awk替代多个sed+wc组合,提升效率:

#!/bin/bash

input_file="my.config"

function get_all_segment
{
    sed -n -E '/\[.*\]/s/\[|\]|\r//gp' "$input_file"
}

function count_items_in_segment
{
    awk -v seg="$1" '
        $0 == "[" seg "]" { in_segment=1; next }
        /^\[.*\]/ { in_segment=0 }
        in_segment && !/^#/ && !/^\s*$/ { count++ }
        END { print count+0 }
    ' "$input_file"
}

index=1
get_all_segment | while read -r seg
do 
    echo "$index: $seg"
    count_items_in_segment "$seg"
    ((index++))
done

内容的提问来源于stack exchange,提问作者L.J.Zeng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 00:35:25