我试图解析从curl请求返回的JSON,就像这样:

curl 'http://twitter.com/users/username.json' |
    sed -e 's/[{}]/''/g' | 
    awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'

上面将JSON划分为多个字段,例如:

% ...
"geo_enabled":false
"friends_count":245
"profile_text_color":"000000"
"status":"in_reply_to_screen_name":null
"source":"web"
"truncated":false
"text":"My status"
"favorited":false
% ...

我如何打印一个特定的字段(由-v k=文本表示)?


当前回答

在shell脚本中解析JSON非常痛苦。使用更合适的语言,创建一个工具,以与shell脚本约定一致的方式提取JSON属性。您可以使用您的新工具来解决当前的shell脚本问题,然后将其添加到您的工具包中以备将来使用。

例如,考虑一个jsonlookup工具,如果我说jsonlookup访问令牌id,它将返回在来自标准输入的属性访问中定义的属性令牌中定义的属性id,这些属性令牌可能是JSON数据。如果该属性不存在,该工具将不返回任何内容(退出状态1)。如果解析失败,则退出状态2并返回标准错误消息。如果查找成功,该工具将打印属性的值。

创建了一个用于精确提取JSON值的Unix工具后,您可以轻松地在shell脚本中使用它:

access_token=$(curl <some horrible crap> | jsonlookup access token id)

任何语言都可以实现jsonlookup。下面是一个相当简洁的Python版本:

#!/usr/bin/python

import sys
import json

try: rep = json.loads(sys.stdin.read())
except:
    sys.stderr.write(sys.argv[0] + ": unable to parse JSON from stdin\n")
    sys.exit(2)
for key in sys.argv[1:]:
    if key not in rep:
        sys.exit(1)
    rep = rep[key]
print rep

其他回答

有一种更简单的方法可以从JSON字符串中获取属性。使用包。Json文件作为一个例子,试试这个:

#!/usr/bin/env bash
my_val="$(json=$(<package.json) node -pe "JSON.parse(process.env.json)['version']")"

我们使用的是过程。env,因为这会将文件的内容作为字符串放入Node.js,而不会有恶意内容逃脱引用并被解析为代码的风险。

有很多专门为从命令行操作JSON而设计的工具,它们会比用Awk更容易、更可靠,比如jq:

curl -s 'https://api.github.com/users/lambda' | jq -r '.name'

您还可以使用可能已经安装在系统上的工具,例如使用json模块的Python,从而避免任何额外的依赖,同时仍然具有适当的json解析器的好处。下面假设你想要使用UTF-8,原始JSON应该用它来编码,而且大多数现代终端也使用它:

Python 3:

curl -s 'https://api.github.com/users/lambda' | \
    python3 -c "import sys, json; print(json.load(sys.stdin)['name'])"

Python 2:

export PYTHONIOENCODING=utf8
curl -s 'https://api.github.com/users/lambda' | \
    python2 -c "import sys, json; print json.load(sys.stdin)['name']"

常见问题

为什么不是纯壳的解决方案呢?

标准POSIX/Single Unix Specification shell是一种非常有限的语言,它不包含表示序列(列表或数组)或关联数组(在其他一些语言中也称为哈希表、映射、字典或对象)的工具。这使得在可移植的shell脚本中表示解析JSON的结果有些棘手。有一些简单的方法可以做到这一点,但如果键或值包含某些特殊字符,其中许多方法都可能会失效。

Bash 4 and later, zsh, and ksh have support for arrays and associative arrays, but these shells are not universally available (macOS stopped updating Bash at Bash 3, due to a change from GPLv2 to GPLv3, while many Linux systems don't have zsh installed out of the box). It's possible that you could write a script that would work in either Bash 4 or zsh, one of which is available on most macOS, Linux, and BSD systems these days, but it would be tough to write a shebang line that worked for such a polyglot script.

最后,在shell中编写一个完整的JSON解析器将是一个非常重要的依赖项,您还可以使用现有的依赖项,如jq或Python。要实现良好的实现,它不会是一行代码,甚至不会是五行代码片段。

为什么不使用awk、sed或grep呢?

可以使用这些工具从具有已知形状和格式的JSON中进行一些快速提取,例如每行一个键。在其他的回答中有几个关于这方面建议的例子。

然而,这些工具是为基于行或基于记录的格式设计的;它们不是为递归解析带有可能转义字符的匹配分隔符而设计的。

因此,使用awk/sed/grep的这些快速而肮脏的解决方案很可能是脆弱的,如果输入格式的某些方面发生变化,例如折叠空白,或在JSON对象中添加额外的嵌套级别,或字符串中的转义引号,就会中断。一个足够健壮、能够处理所有JSON输入而不中断的解决方案也相当庞大和复杂,因此与在jq或Python上添加另一个依赖关系没有太大区别。

我曾经处理过由于shell脚本中糟糕的输入解析而导致的大量客户数据被删除的情况,所以我从不推荐在这种情况下可能很脆弱的快速和肮脏的方法。如果您正在进行一些一次性处理,请参阅其他答案以获得建议,但我仍然强烈建议只使用现有的经过测试的JSON解析器。

历史记录

这个答案最初建议使用jsawk,它应该仍然可以工作,但使用起来比jq要麻烦一些,并且依赖于安装的独立JavaScript解释器,它不像Python解释器那么常见,所以上面的答案可能更可取:

curl -s 'https://api.github.com/users/lambda' | jsawk -a 'return this.name'

这个答案最初也使用了问题中的Twitter API,但该API不再有效,因此很难复制示例进行测试,并且新的Twitter API需要API密钥,因此我已经切换到使用GitHub API,它可以在没有API密钥的情况下轻松使用。原问题的第一个答案是:

curl 'http://twitter.com/users/username.json' | jq -r '.text'

YAML处理器yq

考虑使用yq进行JSON处理。 yq是一个轻量级、可移植的命令行YAML处理器(JSON是YAML的一个子集)。 语法类似于jq。

输入

{
  "name": "Angel",
  "address": {
    "street": "Stairway to",
    "city": "Heaven"
  }
}

用法举例1

yq e ` .name ` $FILE

Angel

用法举例二

yq有一个很好的内置特性,可以使JSON和YAML成为grep-able

yq——输出格式道具$FILE

name = Angel
address.street = Stairway to
address.city = Heaven

有一个有趣的工具在现有的答案中还没有涉及到,那就是使用用Go编写的gron,它的口号是Make JSON可greppable!这正是它所做的。

所以从本质上讲,gron将JSON分解为离散的赋值,查看它的绝对“路径”。与jq等其他工具相比,它的主要优点是允许在不知道要搜索的记录是如何嵌套的情况下搜索值,而不会破坏原始的JSON结构

例如,我想从下面的链接搜索'twitter_username'字段,我只是这样做

% gron 'https://api.github.com/users/lambda' | fgrep 'twitter_username'
json.twitter_username = "unlambda";
% gron 'https://api.github.com/users/lambda' | fgrep 'twitter_username' | gron -u
{
  "twitter_username": "unlambda"
}

就这么简单。请注意gron -u (ungron的缩写)如何从搜索路径重新构造JSON。使用fgrep只是为了将搜索过滤到所需的路径,而不是让搜索表达式作为正则表达式计算,而是作为固定字符串(本质上是grep -F)

另一个搜索字符串以查看记录在嵌套结构中的位置的示例

% echo '{"foo":{"bar":{"zoo":{"moo":"fine"}}}}' | gron | fgrep "fine"
json.foo.bar.zoo.moo = "fine";

它还通过-s命令行标志支持JSON流,在这里您可以连续地对输入流进行gron以获得匹配的记录。此外,gron具有零运行时依赖性。你可以下载Linux、Mac、Windows或FreeBSD的二进制文件并运行它。

更多的用法示例和行程可以在官方Github页面-高级用法中找到

至于为什么可以使用gron而不是其他JSON解析工具,请参阅项目页面的作者注释。

为什么我不应该直接使用jq?

Jq非常棒,比gron强大得多,但这种强大带来了复杂性。Gron的目标是使您更容易使用您已经知道的工具,如grep和sed。

这里我不能用任何答案。jq、shell数组、声明、grep -P、后视、前视、Python、Perl、Ruby甚至Bash都不可用。

剩下的答案都不太管用。JavaScript听起来很熟悉,但罐头上写的是Nescaffe——所以也不行:)即使有,对于我的简单需求——它们也会过度消耗和缓慢。

然而,对我来说,从我的调制解调器的JSON格式的回复中获得许多变量是极其重要的。我在Bourne shell (sh)做它与一个非常修剪下来的BusyBox在我的路由器!单独使用AWK没有任何问题:只需设置分隔符并读取数据。对于单个变量,这就是全部!

awk 'BEGIN { FS="\""; RS="," }; { if ($2 == "login") {print $4} }' test.json

还记得我没有数组吗?我必须在AWK解析数据中分配给我在shell脚本中需要的11个变量。我所到之处,都有人说这是不可能完成的任务。这也没有问题。

我的解决办法很简单。这段代码将:

parse .json file from the question (actually, I have borrowed a working data sample from the most upvoted answer) and picked out the quoted data, plus create shell variables from within the awk assigning free named shell variable names. eval $( curl -s 'https://api.github.com/users/lambda' | awk ' BEGIN { FS="""; RS="," }; { if ($2 == "login") { print "Login=""$4""" } if ($2 == "name") { print "Name=""$4""" } if ($2 == "updated_at") { print "Updated=""$4""" } }' ) echo "$Login, $Name, $Updated"

里面的空白没有任何问题。在我的使用中,相同的命令解析一个很长的单行输出。由于使用eval,此解决方案仅适用于可信数据。

调整它以提取未引用的数据很简单。对于大量变量,可以使用else if实现边际速度增益。缺乏数组显然意味着:没有额外的操作就没有多个记录。但是在数组可用的情况下,调整这个解决方案是一项简单的任务。

@maikel的sed回答几乎是有效的(但我不能评论它)。对于我的格式化好的数据-它工作。这里使用的例子没有太多(缺少引号)。它很复杂,很难修改。另外,我不喜欢进行11次调用来提取11个变量。为什么?我计时100循环提取9个变量:sed函数花了48.99秒,我的解决方案花了0.91秒!不公平?只提取9个变量:0.51秒vs. 0.02秒。