如何用原生Bash实现从YAML文件提取指定属性值的自定义函数
原有代码问题排查
- 语法错误:变量赋值时等号两侧不能加空格,
curr_line_prop = ${curr_line_props[0]}会被bash识别为命令执行,直接报错 - 匹配逻辑错误:通配符匹配不能用单中括号
[]的test表达式,必须用双中括号[[]]才支持 - 缺失YAML缩进处理:完全没有判断字段的层级,会把不同父节点下的同名字段混淆,比如多个AppNode下的Name字段会被误匹配
- 返回值逻辑错误:bash的
return只能返回0-255的状态码,不能返回字符串结果,正确的返回方式是用echo输出结果,调用时通过命令替换$()接收 - 没有处理值的格式:读取到的属性值会携带多余的前置空格、引号,没有做清洗
- 名称匹配逻辑未实现:代码中只预留了数字索引的处理分支,用属性值匹配(比如
[$DomainName])的逻辑完全为空,无法满足调用要求
纯Bash实现的getValue函数
不需要任何外部依赖,完全符合要求的调用规则,支持数字索引和名称匹配,适配你提供的YAML格式:
function getValue { local prop_path="$1" local yaml_file="$2" # 解析属性路径为层级数组 IFS='_' read -r -a levels <<< "$prop_path" local level_count=${#levels[@]} local current_level=0 local expected_indent=0 local INDENT_STEP=2 # YAML每层级缩进为2空格,若你的文件是4空格可修改此值 # 逐行读取YAML,兼容结尾无换行的情况 while IFS= read -r line || [ -n "$line" ]; do # 跳过空行、注释行 [[ $line =~ ^[[:space:]]*# ]] || [[ -z $line ]] && continue # 计算当前行缩进长度 local indent_str=${line%%[^[:space:]]*} local indent=${#indent_str} # 提取键和值,清洗前后空格 local line_content=${line#$indent_str} local key=${line_content%%:*} local value=${line_content#*:} value=$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^["'\'']//' -e 's/["'\'']$//') # 缩进小于预期,说明已经退出当前匹配的父节点,回退层级 if (( indent < expected_indent && current_level > 0 )); then ((current_level--)) expected_indent=$((expected_indent - INDENT_STEP)) fi # 层级已匹配完直接退出 (( current_level >= level_count )) && break # 解析当前待匹配层级的键和索引 local curr_spec=${levels[$current_level]} local curr_key=${curr_spec%%\[*} local curr_idx="" [[ $curr_spec =~ \[(.*)\] ]] && curr_idx=${BASH_REMATCH[1]} # 当前行键名不匹配,跳过 [[ $key != "$curr_key" ]] && continue # 处理索引逻辑 if [[ -n $curr_idx ]]; then # 数字索引:匹配第N个同层级同键节点 if [[ $curr_idx =~ ^[0-9]+$ ]]; then local match_count=0 local temp_line="$line" while true; do ((match_count++)) if (( match_count == curr_idx )); then ((current_level++)) expected_indent=$((indent + INDENT_STEP)) (( current_level == level_count )) && echo "$value" && return 0 break fi # 找下一个同层级同键节点 local found_next=0 while IFS= read -r next_line || [ -n "$next_line" ]; do local next_indent_str=${next_line%%[^[:space:]]*} local next_indent=${#next_indent_str} if (( next_indent <= indent )); then [[ $next_line =~ ^[[:space:]]*$curr_key: ]] && temp_line="$next_line" && found_next=1 break fi done (( found_next == 0 )) && break line="$temp_line" done # 名称匹配:匹配下Name等于指定值的同键节点 else local found_match=0 while true; do # 查找当前节点下的Name字段 while IFS= read -r sub_line || [ -n "$sub_line" ]; do local sub_indent_str=${sub_line%%[^[:space:]]*} local sub_indent=${#sub_indent_str} (( sub_indent <= indent )) && break local sub_content=${sub_line#$sub_indent_str} if [[ $sub_content == Name:* ]]; then local name_val=${sub_content#*:} name_val=$(echo "$name_val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^["'\'']//' -e 's/["'\'']$//') if [[ $name_val == "$curr_idx" ]]; then found_match=1 break 2 fi break fi done # 没匹配到找下一个同层级同键节点 local found_next=0 while IFS= read -r next_line || [ -n "$next_line" ]; do local next_indent_str=${next_line%%[^[:space:]]*} local next_indent=${#next_indent_str} if (( next_indent <= indent )); then [[ $next_line =~ ^[[:space:]]*$curr_key: ]] && line="$next_line" && found_next=1 break fi done (( found_next == 0 )) && break done if (( found_match == 1 )); then ((current_level++)) expected_indent=$((indent + INDENT_STEP)) (( current_level == level_count )) && echo "$value" && return 0 fi fi else # 无索引直接匹配 ((current_level++)) expected_indent=$((indent + INDENT_STEP)) (( current_level == level_count )) && echo "$value" && return 0 fi done < "$yaml_file" # 未匹配到返回空 echo "" return 1 }
使用示例
调用时需要用bash的命令替换语法接收返回值:
# 替换为你的YAML文件实际路径 YAML_PATH="/path/my_yaml.yml" DomainName=$(getValue "Domains_Domain[1]_Name" "$YAML_PATH") AppSpaceName=$(getValue "Domains_Domain[$DomainName]_AppSpaces_AppSpace[1]_Name" "$YAML_PATH") AppNodeName=$(getValue "Domains_Domain[$DomainName]_AppSpaces_AppSpace[$AppSpaceName]_AppNodes_AppNode[1]_Name" "$YAML_PATH") AppName=$(getValue "Domains_Domain[$DomainName]_AppSpaces_AppSpace[$AppSpaceName]_AppNodes_AppNode[$AppNodeName]_Applications_Application[1]_Name" "$YAML_PATH") AppVersion=$(getValue "Domains_Domain[$DomainName]_AppSpaces_AppSpace[$AppSpaceName]_AppNodes_AppNode[$AppNodeName]_Applications_Application[$AppName]_Version" "$YAML_PATH")
内容的提问来源于stack exchange,提问作者AKPuvvada
相关产品推荐
相关产品推荐

