如何用Bash编写JSON文件的最高效方式是什么?

4

我需要用 Bash 脚本编写一个 JSON 文件,我知道可以通过像 echo 'something' >> $file 这样的方式逐步构建文件,但是使用 echo 重定向而不是真正的文件输出似乎有点“hacky”。如果这是最好的方法,而且根本不是 hacky 的方法,我很乐意使用 echo,但我只是想知道是否有更好的方法来从 Bash 脚本中输出文件。


2
不幸的是,“echo”是做这件事情的“最好”方式...而且不幸的是,如果你从“bash”中去掉了“hacky”,那么我们就没有理由再使用它了... - Jason Hu
1
定义“最有效率”是什么?处理器占用更少?磁盘占用更少?代码更少?代码更易读? - Mr. Llama
@HuStmpHrrr,是的,但是一旦通过对每行进行单独重定向来消除重新打开惩罚,与仅调用外部进程一次相比,每行调用一次内置函数将更快,除非你正在谈论大量行。 - Charles Duffy
3
顺便提一下,绝不要用这种方法生成 JSONjq才是正确的工具,虽然它关注的是正确性而非效率。如果你问“在 bash 中编写 JSON 文件的最佳方式是什么”,那将是完全不同的问题。 - Charles Duffy
1
“在我的本地机器上安装jq并在服务器上运行”这句话并不是很清楚。如果您的意思是在本地编译它,然后将编译后的二进制文件复制到服务器上,那么是的——如果它们不是相同的架构,则需要进行交叉编译,这超出了我能够在此处描述的范围,但绝对是可能的。然而,这种做法并不是一个好主意——当软件依赖于人们手工构建的二进制文件时,会导致难以维护的系统。而且Python 2.6确实内置了JSON模块:https://docs.python.org/2.6/library/json.html - Charles Duffy
显示剩余13条评论
5个回答

15

高效生成输出

echo 是一个内建命令,而非外部命令,因此它并不像你想象的那么低效。真正低效的是在每个 echo 结尾加上 >> 文件名

这样做很糟糕:

# EVIL!
echo "something" >file
echo "first line" >>file
echo "second line" >>file

这很好:

# NOT EVIL!
{
  echo "something" >&3
  printf '%s\n' "first line" "$second line" >&3
  # ... etc ...
} 3>file

只需打开一次输出文件,就可以消除主要的低效率。

明确一点:调用echo 20次比调用cat一次要更有效率,因为cat是一个外部进程,不是shell的一部分。 关于运行echo "foo" >>file 20次的高度低效在于需要打开和关闭20次输出文件;而不是echo本身。


正确生成JSON

不要使用catechoprintf或类似的工具。相反,使用能够理解JSON的工具——任何其他方法都可能导致潜在的不正确(甚至是可利用注入攻击)的结果。

例如:

jq \
  --arg something "$some_value_here" \
  --arg another "$another_value" \
  '.["something"]=$something | .["another_value"]=$another' \
  <template.json >output.json

将基于template.json,生成一个JSON文件,其中something设置为shell变量"$some_value_here"的值,而another_value则设置为第二个值。与天真的方法不同,这将正确处理包含文字引号或其他需要转义才能正确表示的字符的变量值。


关于echo的一点说明

尽管上述所有内容都已经说过了--应该避免使用echo,而应该使用printf(带有适当的静态格式字符串)。根据< A HREF =“http://pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html”rel =“nofollow noreferrer”> POSIX sh标准:

应用程序使用

除非省略了- n(作为第一个参数)和转义序列,否则无法在所有POSIX系统上可移植地使用echo。

假定IFS具有其标准值或未设置,则printf实用程序可以可移植地用于模拟echo实用程序的任何传统行为,如下所示:

[...]

鼓励新应用程序改用printf而不是echo。

原理

由于历史应用程序中广泛使用echo实用程序,因此未将其过时。希望进行提示而不带s或可能期望回显-n的符合应用程序应使用从第九版系统派生的printf实用程序。

如规定的那样,echo以最简单的方式写入其参数。 echo的两个不同的历史版本以致命的不兼容方式变化。

BSD echo检查字符串-n的第一个参数,这会导致其在输出中在最终参数后面遵循的否则将省略的字符。

System V echo不支持任何选项,但允许在其操作数中使用转义序列,如在OPERANDS部分为XSI实现所述。

echo实用程序不支持实用程序语法指南10,因为历史应用程序依赖于echo以回显除BSD版本中的-n选项之外的所有参数。


如果你只是使用 echo,那么你不妨使用多行字符串。 ;) - Mr. Llama
1
@Charles Duffy:您确定OP所指的效率是执行时间,而不是代码可读性吗? - Eugeniu Rosca
@thatotherguy,我完全同意。 - Charles Duffy
1
@HuStmpHrrr,我在jq中进行了一些令人惊讶的高级分析(包括不同输入源之间的连接),它是一种非常强大的语言。 - Charles Duffy
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Jason Hu
显示剩余4条评论

8

您可以使用cathere-document格式

cat <<'EOF' > output.json
{
    "key": "value",
    "num": 5,
    "tags": ["good", "bad"],
    "money": "$0"
}
EOF

请注意在这个文档锚点周围的单引号。这可以防止文档内容的插值。如果没有它,$0 可以被替换。
如果您将效率定义为原始速度而不是可读性,则应考虑使用Charles Duffy 的答案,因为对于少量行(echo 0.01s vs cat 0.1s),它几乎快了一个数量级。
如果您需要创建大于几百行的文件,则应考虑使用除cat/echo之外的其他方法。

我不会轻易地称之为更有效率 -- 调用 cat 涉及到 fork()/exec() 循环。 - Charles Duffy
啊,我看到你已经完成了基准测试。非常感谢,加一分。 :) - Charles Duffy

4

将数据构建在环境变量中,并输出一次。

var=something
var="$var something else"
var="$var and another thing"
echo "$var" > file

需要使用引号 -- echo "$var" -- 来避免字符串分割和值的通配符扩展;如果该值包含换行符,则尤其重要,否则 echo $var 会将其丢弃。 - Charles Duffy
没错,这就是我所说的高效。 - Jason Hu
1
我还建议使用bash本地的追加语法:var+="something else"$'\n'(同时演示了换行符的bash语法)。 - Charles Duffy
对于足够长的缓冲区,收集字符串到数组中,并使用 printf 一次性发出它们可能更有效率 -- 如果需要,可以使用分隔符。例如:arr=( ); arr+=( "first line" "second line" ); arr+=( "third line" ); printf '%s\n' "${arr[@]}"(如果想要一个大行而不是 \n,则不需要)-- 这样可以避免 bash 在数组连接方面的低效率。 - Charles Duffy
此外,参见 POSIX 规范中关于 echo 命令为何被弃用的 RATIONALE 和 APPLICATION notes 部分的 http://pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html。 - Charles Duffy

1
除了使用echo,您还可以使用cat
cat > myfile << EOF
Hello
World
!
EOF

正如我在其他地方评论的那样,强制调用像/usr/bin/cat这样的外部工具几乎不是一种效率优化。 - Charles Duffy

0

您可以使用cat和"heredocs"来最小化您需要进行的调用次数。

$ cat foo.sh
cat <<'HERE' > output
This
that
the other
    indentation is 
        preserved

as are 

    blank lines

The end.
HERE
$ sh foo.sh
$ cat output
This
that
the other
    indentation is 
        preserved

as are 

    blank lines

The end.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接