使用 Unix 工具解析 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]}'
% ...
"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
% ...

答案

有许多专门用于从命令行操作 JSON 的工具,它们比使用 Awk 进行操作要容易得多,也更可靠,例如jq

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

您也可以使用系统中可能已经安装的工具(例如使用json模块的 Python)来执行此操作,从而避免任何额外的依赖关系,同时仍然可以使用适当的 JSON 解析器。以下假设您要使用 UTF-8,原始 JSON 应该用 UTF-8 编码,这也是大多数现代终端所使用的:

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']"

历史笔记

这个答案最初推荐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'

为了快速提取特定键的值,我个人喜欢使用 “grep -o”,它仅返回正则表达式的匹配项。例如,要从推文中获取 “文本” 字段,例如:

grep -Po '"text":.*?[^\\]",' tweets.json

这个正则表达式比您想象的要强大。例如,它可以很好地处理带有嵌入式逗号和转义引号的字符串。我认为如果再做一点工作,就可以保证实际上是可以提取值的,如果它是原子的。 (如果它具有嵌套,那么正则表达式当然不能做到这一点。)

为了进一步清洁(虽然保持字符串的原逃逸),你可以使用这样的: | perl -pe 's/"text"://; s/^"//; s/",$//' 。 (我为此进行了分析 。)

对于所有坚持认为应该使用真正的 JSON 解析器的仇恨者 - 是的,这对于正确性至关重要,但是

  1. 要进行真正快速的分析,例如对值进行计数以检查数据清除错误或对数据有一般的了解,在命令行中执行某些操作会更快。打开编辑器来编写脚本会让人分心。
  2. grep -o比 Python 标准json库快几个数量级,至少在进行 tweet 时(每个〜2 KB)。我不确定这是否只是因为json速度慢(我应该和 yajl 比较);但是从原则上讲,正则表达式应该更快,因为它是有限状态并且更加可优化,而不是必须支持递归的解析器,在这种情况下,它会花很多 CPU 来为不需要的结构树。 (如果有人编写了一个进行了正确的(深度受限的)JSON 解析的有限状态转换器,那就太好了!与此同时,我们还有 “grep -o”。)

为了编写可维护的代码,我始终使用真正的解析库。我还没有尝试过jsawk ,但是如果运行良好,那将解决第一个问题。

最后一个更棘手的解决方案:我编写了一个脚本,该脚本使用 Python json并将所需的键提取到制表符分隔的列中;然后我遍历awk的包装器,该包装器允许对列进行命名访问。 在这里:json2tsv 和 tsvawk 脚本 。因此,对于此示例,它将是:

json2tsv id text < tweets.json | tsvawk '{print "tweet " $id " is: " $text}'

这种方法不能解决#2 问题,它比单个 Python 脚本效率低,而且还有些脆弱:它强制对字符串值中的换行符和制表符进行规范化,以便与 awk 的字段 / 记录分隔的世界视图很好地配合使用。但是,与grep -o相比,它确实可以让您留在命令行中,具有更多的正确性。

基于此处的一些建议(尤其是注释中的建议)建议使用 Python,我很失望地没有找到一个示例。

因此,这是一个从某些 JSON 数据中获取单个值的衬里。它假定您正在(从某处)管道传输数据,因此在脚本上下文中应该很有用。

echo '{"hostname":"test","domainname":"example.com"}' | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["hostname"]'

跟随 MartinR 和 Boecko 的领导:

$ curl -s 'http://twitter.com/users/username.json' | python -mjson.tool

这将为您提供极为友好的 grep 输出。很方便:

$ curl -s 'http://twitter.com/users/username.json' | python -mjson.tool | grep my_key

您可以只为您的平台下载jq二进制文件并运行( chmod +x jq ):

$ curl 'https://twitter.com/users/username.json' | ./jq -r '.name'

它从 json 对象中提取"name"属性。

jq主页说它就像sed一样用于 JSON 数据。

使用Python 的 JSON 支持而不是使用 awk!

像这样:

curl -s http://twitter.com/users/username.json | \
    python -c "import json,sys;obj=json.load(sys.stdin);print obj['name'];"

使用 Node.js

如果系统已安装 ,则可以使用-p print 和-e带有JSON.parse脚本标志来提取所需的任何值。

一个使用 JSON 字符串{ "foo": "bar" }并提取 “foo” 值的简单示例:

$ node -pe 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
bar

由于我们可以访问cat和其他实用程序,因此可以将其用于文件:

$ node -pe 'JSON.parse(process.argv[1]).foo' "$(cat foobar.json)"
bar

或任何其他格式,例如包含 JSON 的 URL:

$ node -pe 'JSON.parse(process.argv[1]).name' "$(curl -s https://api.github.com/users/trevorsenior)"
Trevor Senior

您已经问过如何射击自己的脚,我在这里提供弹药:

curl -s 'http://twitter.com/users/username.json' | sed -e 's/[{}]/''/g' | awk -v RS=',"' -F: '/^text/ {print $2}'

您可以使用tr -d '{}'代替sed 。但是将它们完全排除似乎也具有预期的效果。

如果要去sed 's/\(^"\|"$\)//g'引号,则将上述结果通过sed 's/\(^"\|"$\)//g'

我认为其他人已经发出了足够的警报。我将用手机待命给救护车打电话。准备好后开火。

在 Python 中使用 Bash

在. bash_rc 文件中创建一个 bash 函数

function getJsonVal () { 
    python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1))"; 
}

然后

$ curl 'http://twitter.com/users/username.json' | getJsonVal "['text']"
My status
$

这是相同的功能,但带有错误检查功能。

function getJsonVal() {
   if [ \( $# -ne 1 \) -o \( -t 0 \) ]; then
       cat <<EOF
Usage: getJsonVal 'key' < /tmp/
 -- or -- 
 cat /tmp/input | getJsonVal 'key'
EOF
       return;
   fi;
   python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1))";
}

其中 $#-ne 1 确保至少输入 1,-t 0 确保从管道重定向。

关于此实现的好处是,您可以访问嵌套的 json 值并获取 json 作为回报! =)

例:

$ echo '{"foo": {"bar": "baz", "a": [1,2,3]}}' |  getJsonVal "['foo']['a'][1]"
2

如果您真的想花哨的话,可以打印数据:

function getJsonVal () { 
    python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1, sort_keys=True, indent=4))"; 
}

$ echo '{"foo": {"bar": "baz", "a": [1,2,3]}}' |  getJsonVal "['foo']"
{
    "a": [
        1, 
        2, 
        3
    ], 
    "bar": "baz"
}

TickTick是用 bash(<250 行代码)编写的 JSON 解析器

这是作者文章的摘录, 想像一下 Bash 支持 JSON 的世界

#!/bin/bash
. ticktick.sh

``  
  people = { 
    "Writers": [
      "Rod Serling",
      "Charles Beaumont",
      "Richard Matheson"
    ],  
    "Cast": {
      "Rod Serling": { "Episodes": 156 },
      "Martin Landau": { "Episodes": 2 },
      "William Shatner": { "Episodes": 2 } 
    }   
  }   
``  

function printDirectors() {
  echo "  The ``people.Directors.length()`` Directors are:"

  for director in ``people.Directors.items()``; do
    printf "    - %s\n" ${!director}
  done
}   

`` people.Directors = [ "John Brahm", "Douglas Heyes" ] ``
printDirectors

newDirector="Lamont Johnson"
`` people.Directors.push($newDirector) ``
printDirectors

echo "Shifted: "``people.Directors.shift()``
printDirectors

echo "Popped: "``people.Directors.pop()``
printDirectors