无需jq,如何在Shell脚本中更新JSON文件?
不依赖jq在Shell脚本中操作JSON的简便方案
之前用jq读写JSON很方便,但不是所有Bash环境都预装jq。我尝试用sed、awk写了复杂的字符串处理脚本(如下),但语法繁琐还没法处理嵌套JSON结构,想找更简便的替代方法:
update_json_key() { local key="$1" local value="$2" local filename="$3" if [[ ! -f "$filename" ]]; then echo "{}" > "$filename" fi SED_INPLACE="" if [[ "$OSTYPE" == "darwin"* ]]; then SED_INPLACE="-i ''" # macOS requires an empty extension for in-place editing else SED_INPLACE="-i" # Linux fi # Ensure the file exists and is a valid JSON object if [ ! -s "$filename" ] || ! grep -q "{" "$filename"; then echo "{}" > "$filename" fi # Check if the key exists if grep -q "\"$key\": " "$filename"; then # Key exists, update it # This regex ensures we don't capture more than we need by being specific with our patterns sed $SED_INPLACE "s/\"$key\": \"[^\"]*\"/\"$key\": \"$value\"/" "$filename" else # Key does not exist, add it if grep -q "^{}$" "$filename"; then # File has only empty JSON, directly add key without comma sed $SED_INPLACE "s/{}/{\"$key\": \"$value\"}/" "$filename" else # File is non-empty, append key before the last closing brace # This ensures that we correctly find the last closing brace even when it's not at the very end sed $SED_INPLACE "s/\(.*\)}$/\1, \"$key\": \"$value\"}/" "$filename" fi fi if [ -e "$filename''" ]; then rm -f "$filename''" fi if [[ -e "$filename" ]] && [[ $(wc -l < "$filename") -gt 1 ]]; then raw_json=$(sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' -e 's/\"\: \?\"/\"\: \"/g' -e 's/, \?\"/,,\"/g' "$filename") rm -f $filename echo "$raw_json" > "$filename" fi formatted_json=$(awk 'BEGIN { FS=","; print "{" } { gsub(/[{}]/, ""); n = split($0, a, ","); for (i = 1; i <= n; i++) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", a[i]); print " " a[i] (i < n ? "," : ""); } } END { print "}" }' "$filename") rm -f $filename echo "$formatted_json" > "$filename" }
推荐方案:用Python内置JSON模块(通用且靠谱)
几乎所有类Unix系统都预装了Python,其标准库的json模块能完美处理JSON的语法规则(包括嵌套、转义字符、空格格式等),比sed/awk的正则方法可靠太多。
替换你现有函数的简化版本:
update_json_key() { local key="$1" local value="$2" local filename="$3" # 确保文件存在,不存在则创建空JSON [[ ! -f "$filename" ]] && echo "{}" > "$filename" # 用Python脚本处理JSON更新 python3 -c " import json, os with open('$filename', 'r+') as f: try: data = json.load(f) except json.JSONDecodeError: data = {} data['$key'] = '$value' f.seek(0) json.dump(data, f, indent=2) f.truncate() " }
这个函数支持:
- 自动创建空JSON文件
- 处理已有键的更新、新键的添加
- 自动格式化JSON(带缩进)
- 原生支持嵌套JSON结构(如果需要更新嵌套键,只需修改Python代码里的
data['$key']为data['parent']['child']这类层级访问)
备选方案:用Perl的JSON::PP模块(核心模块无需额外安装)
如果系统预装Perl,JSON::PP是Perl的核心模块,不需要额外安装,用法类似Python:
update_json_key() { local key="$1" local value="$2" local filename="$3" [[ ! -f "$filename" ]] && echo "{}" > "$filename" perl -MJSON::PP -0777 -ne ' my $data = eval { decode_json($_) } || {}; $data->{"'"$key"'"} = "'"$value"'"; print encode_json($data) . "\n"; ' "$filename" > "$filename.tmp" && mv "$filename.tmp" "$filename" }
为什么不推荐继续用sed/awk?
sed和awk是基于正则的文本处理工具,而JSON是有层级结构的语法,正则无法可靠处理递归嵌套、字符串中的特殊字符(比如包含引号、反斜杠的字符串),很容易破坏JSON的合法性。你的现有脚本只能处理扁平的、格式规范的简单JSON,一旦遇到嵌套或者不标准的空格格式,就会出错。
如果必须用纯shell工具(比如极端环境下连Python/Perl都没有),那只能限制在非常简单的扁平JSON场景,并且要处理大量边界情况,但这几乎不值得投入精力——优先考虑用系统自带的脚本语言处理更靠谱。
内容的提问来源于stack exchange,提问作者Alfred Chow
相关产品推荐
相关产品推荐

