我正在尝试解析从 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=text
表示)?
grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json
。这很容易解决任务&仅适用于 grep 并且非常适用于简单的 JSON。对于复杂的 JSON,您应该使用适当的解析器。
有许多专门设计用于从命令行操作 JSON 的工具,它们比使用 Awk 更容易、更可靠,例如 jq
:
curl -s 'https://api.github.com/users/lambda' | jq -r '.name'
您还可以使用可能已经安装在系统上的工具来执行此操作,例如使用 json
module 的 Python,这样可以避免任何额外的依赖关系,同时仍然可以从适当的 JSON 解析器中受益。以下假设您要使用 UTF-8,原始 JSON 应该被编码,并且也是大多数现代终端使用的:
蟒蛇 3:
curl -s 'https://api.github.com/users/lambda' | \
python3 -c "import sys, json; print(json.load(sys.stdin)['name'])"
蟒蛇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 的结果有些棘手。有 somewhat hacky ways to do it,但如果键或值包含某些特殊字符,它们中的许多可能会中断。
Bash 4 及更高版本、zsh 和 ksh 支持数组和关联数组,但这些 shell 并不普遍可用(由于从 GPLv2 到 GPLv3 的更改,macOS 在 Bash 3 停止更新 Bash,而许多 Linux 系统没有zsh 开箱即用)。您可以编写一个可以在 Bash 4 或 zsh 中运行的脚本,其中一个在当今大多数 macOS、Linux 和 BSD 系统上都可用,但是很难编写一个适用于这样的 shebang 行多语言脚本。
最后,在 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'
为了快速提取特定键的值,我个人喜欢使用“grep -o”,它只返回正则表达式的匹配项。例如,要从推文中获取“文本”字段,例如:
grep -Po '"text":.*?[^\\]",' tweets.json
这个正则表达式比你想象的更健壮;例如,它可以很好地处理其中嵌入了逗号和转义引号的字符串。我认为,如果它是原子的,你可以做一个实际上保证提取价值的工作。 (如果它有嵌套,那么正则表达式当然不能这样做。)
为了进一步清理(尽管保留字符串的原始转义),您可以使用类似:| perl -pe 's/"text"://; s/^"//; s/",$//'
。 (我为 this analysis 做了这个。)
对于所有坚持你应该使用真正的 JSON 解析器的反对者——是的,这对于正确性至关重要,但是
要进行非常快速的分析,例如计算值以检查数据清理错误或大致了解数据,在命令行上敲出一些东西会更快。打开编辑器编写脚本会分散注意力。 grep -o 比 Python 标准 json 库快几个数量级,至少在为推文执行此操作时(每个大约 2 KB)。我不确定这是否只是因为 json 很慢(有时我应该与 yajl 进行比较);但原则上,正则表达式应该更快,因为它是有限状态并且更可优化,而不是必须支持递归的解析器,在这种情况下,会花费大量 CPU 为您不关心的结构构建树。 (如果有人编写了一个有限状态转换器来进行正确的(深度受限的)JSON 解析,那就太棒了!同时我们有“grep -o”。)
为了编写可维护的代码,我总是使用真正的解析库。我没有尝试过 jsawk,但如果它运行良好,那将解决第 1 点。
最后一个更古怪的解决方案:我编写了一个脚本,它使用 Python json
并将所需的键提取到制表符分隔的列中;然后我通过一个围绕 awk
的包装器进行管道传输,该包装器允许对列进行命名访问。 In here: the json2tsv and tsvawk scripts。所以对于这个例子,它将是:
json2tsv id text < tweets.json | tsvawk '{print "tweet " $id " is: " $text}'
这种方法没有解决 #2,比单个 Python 脚本效率低,而且有点脆弱:它强制字符串值中的换行符和制表符标准化,以便与 awk 的字段/记录分隔的世界视图很好地配合。但它确实让您停留在命令行上,比 grep -o
更正确。
grep -Po '"text":(\d*?,|.*?[^\\]",)'
jq .name
在命令行上工作,不需要“打开编辑器来编写脚本”。 2. 正则表达式产生错误结果的速度有多快并不重要
| grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
-P
选项。我在 OSX 10.11.5 上测试,grep --version
是 grep (BSD grep) 2.5.1-FreeBSD
。我让它与 OSX 上的“扩展正则表达式”选项一起工作。上面的命令是 grep -Eo '"text":.*?[^\\]",' tweets.json
。
基于这里的一些建议(尤其是在评论中)建议使用 Python,我很失望没有找到一个例子。
因此,这是从一些 JSON 数据中获取单个值的单行程序。它假设您正在(从某处)管道传输数据,因此在脚本上下文中应该很有用。
echo '{"hostname":"test","domainname":"example.com"}' | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["hostname"]'
jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }
,以便我可以编写:curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'
&更多类似的可怕东西......顺便说一句,obj[0]
似乎没有必要,看起来只有 obj
在默认情况下可以正常工作(?)。
obj[0]
在解析 { "port":5555 }
时会导致错误。删除 [0]
后工作正常。
print(obj["hostname"])
而不是 print obj["hostname"]
在 martinr's and Boecko's lead 之后:
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
| grep field
。谢谢!
jq
通常不安装,而 python 安装。此外,一旦你在 Python 中,你不妨一路走下去,用 import json...
解析它
您只需 download jq
binary for your platform 并运行 (chmod +x jq
):
$ curl 'https://twitter.com/users/username.json' | ./jq -r '.name'
它从 json 对象中提取 "name"
属性。
jq
homepage 表示它类似于 JSON 数据的 sed
。
jq
是一个了不起的工具。
curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
使用 Node.js
如果系统安装了 Node.js,则可以使用 -p
打印和 -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
node -p -e 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
curl -s https://api.github.com/users/trevorsenior | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).name"
cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
使用 Python's JSON support 而不是使用 AWK!
像这样的东西:
curl -s http://twitter.com/users/username.json | \
python -c "import json,sys;obj=json.load(sys.stdin);print(obj['name']);"
macOS v12.3 (Monterey) removed /usr/bin/python
,因此对于 macOS v12.3 及更高版本,我们必须使用 /usr/bin/python3
。
curl -s http://twitter.com/users/username.json | \
python3 -c "import json,sys;obj=json.load(sys.stdin);print(obj['name']);"
json.load(sys.stdin)['"key']"
写得更少,例如:curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']"
。
/usr/bin/python
在 macOS 12.3
上不存在,因此现在需要使用 python3。
你问过如何在脚上射击自己,而我是来提供弹药的:
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'
我认为其他人已经发出了足够的警报。我会拿着手机等着叫救护车。准备好时开火。
在 Python 中使用 Bash
在 .bashrc 文件中创建一个 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"
}
curl http://foo | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["environment"][0]["name"]'
sys.stdout.write()
如果您希望它同时与 python 2 和 3 一起使用。
getJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
更新 (2020)
我对外部工具(例如 Python)的最大问题是您必须处理包管理器和依赖项才能安装它们。
但是,既然我们将 jq
作为一个独立的静态工具,可以通过 GitHub Releases 和 Webi (webinstall.dev/jq) 轻松跨平台安装,我建议:
Mac、Linux:
curl -sS https://webinstall.dev/jq | bash
视窗 10:
curl.exe -A MS https://webinstall.dev/jq | powershell
原创 (2011)
TickTick 是一个用 bash 编写的 JSON 解析器(少于 250 行代码)。
以下是作者的文章摘录,Imagine a world where Bash supports 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
这是使用大多数 distributions 上可用的标准 Unix 工具。它也适用于反斜杠 (\) 和引号 (")。
警告:这与 jq 的威力不相上下,并且仅适用于非常简单的 JSON 对象。这是对原始问题以及您无法安装其他工具的情况的尝试。
function parse_json()
{
echo $1 | \
sed -e 's/[{}]/''/g' | \
sed -e 's/", "/'\",\"'/g' | \
sed -e 's/" ,"/'\",\"'/g' | \
sed -e 's/" , "/'\",\"'/g' | \
sed -e 's/","/'\"---SEPERATOR---\"'/g' | \
awk -F=':' -v RS='---SEPERATOR---' "\$1~/\"$2\"/ {print}" | \
sed -e "s/\"$2\"://" | \
tr -d "\n\t" | \
sed -e 's/\\"/"/g' | \
sed -e 's/\\\\/\\/g' | \
sed -e 's/^[ \t]*//g' | \
sed -e 's/^"//' -e 's/"$//'
}
parse_json '{"username":"john, doe","email":"john@doe.com"}' username
parse_json '{"username":"john doe","email":"john@doe.com"}' email
--- outputs ---
john, doe
johh@doe.com
sed
和 awk
不是 bash
脚本语言的一部分——它们是外部工具。
使用 PHP CLI 解析 JSON
这可以说是题外话,但由于优先权占主导地位,这个问题仍然不完整,没有提到我们可信赖和忠实的 PHP,对吗?
它使用相同的示例 JSON,但让我们将其分配给一个变量以减少晦涩难懂。
export JSON='{"hostname":"test","domainname":"example.com"}'
现在为了 PHP 的优点,它使用 file_get_contents 和 php://stdin 流包装器。
echo $JSON | php -r 'echo json_decode(file_get_contents("php://stdin"))->hostname;'
或者如使用 fgets 和 CLI 常量 STDIN 处已打开的流所指出的那样。
echo $JSON | php -r 'echo json_decode(fgets(STDIN))->hostname;'
$argn
而不是 fgets(STDIN)
$argn
与 -E 或 -R 标志一起使用,并且仅当 JSON 内容位于一行时...
如果有人只想从简单的 JSON 对象中提取值而不需要嵌套结构,则可以使用正则表达式,甚至无需离开 Bash。
这是我使用基于 JSON standard 的 bash 正则表达式定义的函数:
function json_extract() {
local key=$1
local json=$2
local string_regex='"([^"\]|\\.)*"'
local number_regex='-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?'
local value_regex="${string_regex}|${number_regex}|true|false|null"
local pair_regex="\"${key}\"[[:space:]]*:[[:space:]]*(${value_regex})"
if [[ ${json} =~ ${pair_regex} ]]; then
echo $(sed 's/^"\|"$//g' <<< "${BASH_REMATCH[1]}")
else
return 1
fi
}
注意事项:不支持对象和数组作为值,但支持标准中定义的所有其他值类型。此外,无论在 JSON 文档中有多深,只要它具有完全相同的键名,就会匹配一对。
使用 OP 的示例:
$ json_extract text "$(curl 'http://twitter.com/users/username.json')"
My status
$ json_extract friends_count "$(curl 'http://twitter.com/users/username.json')"
245
不幸的是,使用 grep
的最高投票答案返回了在我的场景中不起作用的 full 匹配,但如果您知道 JSON 格式将保持不变,您可以使用 lookbehind 和 lookahead 以仅提取所需的值。
# echo '{"TotalPages":33,"FooBar":"he\"llo","anotherValue":100}' | grep -Po '(?<="FooBar":")(.*?)(?=",)'
he\"llo
# echo '{"TotalPages":33,"FooBar":"he\"llo","anotherValue":100}' | grep -Po '(?<="TotalPages":)(.*?)(?=,)'
33
# echo '{"TotalPages":33,"FooBar":"he\"llo","anotherValue":100}' | grep -Po '(?<="anotherValue":)(.*?)(?=})'
100
使用 Ruby 和 http://flori.github.com/json/ 的版本
< file.json ruby -e "require 'rubygems'; require 'json'; puts JSON.pretty_generate(JSON[STDIN.read]);"
或更简洁地说:
< file.json ruby -r rubygems -r json -e "puts JSON.pretty_generate(JSON[STDIN.read]);"
;
在 Ruby 中不是必需的(它仅用于将通常位于不同行的语句连接成单行)。
这是另一个 Bash 和 Python 混合的答案。我发布了这个答案,因为我想处理更复杂的 JSON 输出,但是,降低了我的 bash 应用程序的复杂性。我想在 Bash 中从 http://www.arcgis.com/sharing/rest/info?f=json 中打开以下 JSON 对象:
{
"owningSystemUrl": "http://www.arcgis.com",
"authInfo": {
"tokenServicesUrl": "https://www.arcgis.com/sharing/rest/generateToken",
"isTokenBasedSecurity": true
}
}
在以下示例中,我利用 Python 创建了自己的 jq
和 unquote
实现。您会注意到,一旦我们将 Python 对象从 json
导入 Python 字典,我们就可以使用 Python 语法来导航字典。要浏览上述内容,语法为:
数据
数据[“authInfo”]
数据[“authInfo”][“tokenServicesUrl”]
通过在 Bash 中使用魔法,我们省略了 data
,只在数据右侧提供 Python 文本,即
jq
jq'[“authInfo”]'
jq'[“authInfo”][“tokenServicesUrl”]'
注意,没有参数,jq 充当 JSON 美化器。使用参数,我们可以使用 Python 语法从字典中提取我们想要的任何内容,包括导航子字典和数组元素。
以下是 Bash Python 混合函数:
#!/bin/bash -xe
jq_py() {
cat <<EOF
import json, sys
data = json.load( sys.stdin )
print( json.dumps( data$1, indent = 4 ) )
EOF
}
jq() {
python -c "$( jq_py "$1" )"
}
unquote_py() {
cat <<EOF
import json,sys
print( json.load( sys.stdin ) )
EOF
}
unquote() {
python -c "$( unquote_py )"
}
以下是 Bash Python 函数的示例用法:
curl http://www.arcgis.com/sharing/rest/info?f=json | tee arcgis.json
# {"owningSystemUrl":"https://www.arcgis.com","authInfo":{"tokenServicesUrl":"https://www.arcgis.com/sharing/rest/generateToken","isTokenBasedSecurity":true}}
cat arcgis.json | jq
# {
# "owningSystemUrl": "https://www.arcgis.com",
# "authInfo": {
# "tokenServicesUrl": "https://www.arcgis.com/sharing/rest/generateToken",
# "isTokenBasedSecurity": true
# }
# }
cat arcgis.json | jq '[ "authInfo" ]'
# {
# "tokenServicesUrl": "https://www.arcgis.com/sharing/rest/generateToken",
# "isTokenBasedSecurity": true
# }
cat arcgis.json | jq '[ "authInfo" ][ "tokenServicesUrl" ]'
# "https://www.arcgis.com/sharing/rest/generateToken"
cat arcgis.json | jq '[ "authInfo" ][ "tokenServicesUrl" ]' | unquote
# https://www.arcgis.com/sharing/rest/generateToken
有一种更简单的方法可以从 JSON 字符串中获取属性。以 package.json
文件为例,试试这个:
#!/usr/bin/env bash
my_val="$(json=$(<package.json) node -pe "JSON.parse(process.env.json)['version']")"
我们使用 process.env
,因为这会将文件的内容作为字符串导入 Node.js,而不会有恶意内容转义其引用并被解析为代码的风险。
require()
实际上可以运行外部代码, JSON.parse 不能。
JSON.parse()
是的,您无疑是安全的......但是在这里,JSON 运行时 接收 带内的(不受信任的)内容(可信)代码。
JSON.parse()
,那么您也很安全,但这里也不会发生这种情况。
JSON.parse()
的shell 变量替换为代码。您假设放置文字反引号将保留内容文字,但这是一个完全不安全的假设,因为文字反引号可以存在于文件内容(以及变量)中,因此可以终止引用并输入一个不带引号的上下文,其中值作为代码执行。
既然 PowerShell 是跨平台的,我想我会放弃它,因为我发现它相当直观且非常简单。
curl -s 'https://api.github.com/users/lambda' | ConvertFrom-Json
ConvertFrom-Json 将 JSON 转换为 PowerShell 自定义对象,因此您可以从那时起轻松使用这些属性。例如,如果您只想要“id”属性,您只需这样做:
curl -s 'https://api.github.com/users/lambda' | ConvertFrom-Json | select -ExpandProperty id
如果你想从 Bash 中调用整个事情,那么你必须这样调用它:
powershell 'curl -s "https://api.github.com/users/lambda" | ConvertFrom-Json'
当然,有一种无需 curl 的纯 PowerShell 方法,即:
Invoke-WebRequest 'https://api.github.com/users/lambda' | select -ExpandProperty Content | ConvertFrom-Json
最后,还有 ConvertTo-Json 可以轻松地将自定义对象转换为 JSON。这是一个例子:
(New-Object PsObject -Property @{ Name = "Tester"; SomeList = @('one','two','three')}) | ConvertTo-Json
这会产生像这样的漂亮 JSON:
{
"Name": "Tester",
"SomeList": [
"one",
"two",
"three"
]
}
诚然,在 Unix 上使用 Windows shell 有点亵渎神明,但 PowerShell 在某些方面确实很擅长,解析 JSON 和 XML 就是其中之一。这是跨平台版本的 GitHub 页面:PowerShell
我不能在这里使用任何答案。 jq、shell 数组、declare、grep -P、lookbehind、lookahead、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 个变量。无论我往哪里看,都说这是一项不可能完成的任务。也没有问题。
我的解决方案很简单。此代码将:
从问题中解析 .json 文件(实际上,我从最受好评的答案中借用了一个工作数据样本)并挑选出引用的数据,并从 awk 中创建 shell 变量,并分配免费的命名 shell 变量名。 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" "" } }' ) 回显 "$Login, $Name, $Updated"
里面的空白没有任何问题。在我的使用中,相同的命令解析一个长的单行输出。使用 eval 时,此解决方案仅适用于受信任的数据。
将其调整为拾取未引用的数据很简单。对于大量变量,使用 else if 可以实现边际速度增益。缺少数组显然意味着:没有额外的摆弄就没有多条记录。但是在阵列可用的情况下,调整此解决方案是一项简单的任务。
@maikel 的 sed 答案几乎可以工作(但我不能对此发表评论)。对于我格式良好的数据 - 它有效。与此处使用的示例无关(缺少引号将其丢弃)。它复杂且难以修改。另外,我不喜欢必须进行 11 次调用来提取 11 个变量。为什么?我对提取 9 个变量的 100 次循环进行了计时:sed 函数耗时 48.99 秒,而我的解决方案耗时 0.91 秒!不公平?只提取 9 个变量:0.51 与 0.02 秒。
你可以试试这样的 -
curl -s 'http://twitter.com/users/jaypalsingh.json' |
awk -F=":" -v RS="," '$1~/"text"/ {print}'
也有 XML 文件的人可能想查看我的 Xidel。它是一个命令行界面、无依赖性的 JSONiq 处理器。 (即,它还支持用于 XML 或 JSON 处理的 XQuery。)
问题中的示例是:
xidel -e 'json("http://twitter.com/users/username.json")("name")'
或者使用我自己的非标准扩展语法:
xidel -e 'json("http://twitter.com/users/username.json").name'
xidel -s https://api.github.com/users/lambda -e 'name'
(或 -e '$json/name'
,或 -e '($json).name'
)。
您可以使用 jshon
:
curl 'http://twitter.com/users/username.json' | jshon -e text
现有答案中未涵盖的一个有趣工具是使用 gron
written in Go,它的标语是 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
的运行时依赖项为零。您可以download a binary在 Linux、Mac、Windows 或 FreeBSD 上运行它。
更多使用示例和旅行可以在官方 Github 页面找到 - Advanced Usage
至于为什么您可以使用 gron
而不是其他 JSON 解析工具,请参阅项目页面的作者注释。
为什么我不应该只使用 jq?
jq 很棒,而且比 gron 强大得多,但这种力量带来了复杂性。 gron 旨在让您更容易使用您已经知道的工具,例如 grep 和 sed。
以下是使用 AWK 的一种方法:
curl -sL 'http://twitter.com/users/username.json' | awk -F"," -v k="text" '{
gsub(/{|}/,"")
for(i=1;i<=NF;i++){
if ( $i ~ k ){
print $i
}
}
}'
对于更复杂的 JSON 解析,我建议使用 Python jsonpath 模块(由 Stefan Goessner 编写) -
安装它 - sudo easy_install -U jsonpath 使用它 - 示例 file.json (来自 http://goessner.net/articles/JsonPath) - { "store": { "book": [ { "category": "reference", “作者”:“奈杰尔·里斯”,“标题”:“世纪谚语”,“价格”:8.95 },{“类别”:“小说”,“作者”:“伊芙琳·沃”,“标题”:“荣誉之剑”,“价格”:12.99 },{“类别”:“小说”,“作者”:“赫尔曼梅尔维尔”,“标题”:“白鲸记”,“isbn”:“0-553-21311- 3", "price": 8.99 }, { "category": "fiction", "author": "JRR Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395 -8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } } } 解析它(提取所有价格 < 10 的书名) - cat file.json | python -c "import sys, json, jsonpath; print '\n'.join(jsonpath.jsonpath(json.load(sys.stdin), 'store.book[?(@.price < 10)].title') )" 将输出 - 世纪白鲸记 注意:上面的命令行不包括错误检查。对于带有错误检查的完整解决方案,您应该创建一个小的 Python 脚本,并使用 try-except 包装代码。
jsonpath
时遇到了一点问题,所以安装了 jsonpath_rw
,所以如果上述方法不起作用,您可以尝试以下类似的方法:1) /usr/bin/python -m pip install jsonpath-rw
2) cat ~/trash/file.json | /usr/bin/python -c "from jsonpath_rw import jsonpath, parse; import sys,json; jsonpath_expr = parse('store.book[0]'); out = [match.value for match in jsonpath_expr.find(json.load(sys.stdin))]; print out;"
(我使用了完整路径python 二进制文件,因为我在安装多个 python 时遇到了一些问题)。
如果您安装了 PHP 解释器:
php -r 'var_export(json_decode(`curl http://twitter.com/users/username.json`, 1));'
例如:
我们有一个资源,它提供带有国家 ISO codes: http://country.io/iso3.json 的 JSON 内容,我们可以很容易地在带有 curl 的 shell 中看到它:
curl http://country.io/iso3.json
但它看起来不是很方便,而且不可读。更好地解析 JSON 内容并查看可读结构:
php -r 'var_export(json_decode(`curl http://country.io/iso3.json`, 1));'
此代码将打印如下内容:
array (
'BD' => 'BGD',
'BE' => 'BEL',
'BF' => 'BFA',
'BG' => 'BGR',
'BA' => 'BIH',
'BB' => 'BRB',
'WF' => 'WLF',
'BL' => 'BLM',
...
如果您有嵌套数组,则此输出看起来会更好...
还有一个非常简单但功能强大的 JSON CLI 处理工具 fx。
https://i.stack.imgur.com/OE1cE.png
例子
使用匿名函数:
echo '{"key": "value"}' | fx "x => x.key"
输出:
value
如果不传递匿名函数参数 → ...,代码将自动转换为匿名函数。您可以通过此关键字访问 JSON:
$ echo '[1,2,3]' | fx "this.map(x => x * 2)"
[2, 4, 6]
或者也只使用点语法:
echo '{"items": {"one": 1}}' | fx .items.one
输出:
1
您可以传递任意数量的匿名函数来减少 JSON:
echo '{"items": ["one", "two"]}' | fx "this.items" "this[1]"
输出:
two
您可以使用扩展运算符更新现有 JSON:
echo '{"count": 0}' | fx "{...this, count: 1}"
输出:
{"count": 1}
只是普通的 JavaScript。无需学习新语法。
更高版本的 fx 具有交互模式! -
这是 pythonpy 的一个很好的用例:
curl 'http://twitter.com/users/username.json' | py 'json.load(sys.stdin)["name"]'
Here is a good reference。在这种情况下:
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++) { where = match(a[i], /\"text\"/); if(where) {print a[i]} } }'
sed
的东西都不应再收到任何赞成票。
如果 pip
在系统上可用,则:
$ pip install json-query
使用示例:
$ curl -s http://0/file.json | json-query
{
"key":"value"
}
$ curl -s http://0/file.json | json-query my.key
value
$ curl -s http://0/file.json | json-query my.keys.
key_1
key_2
key_3
$ curl -s http://0/file.json | json-query my.keys.2
value_2
不定期副业成功案例分享
print
语句将始终编码为 ASCII,因为您在管道中使用 Python。在命令中插入PYTHONIOENCODING=<desired codec>
以设置适合您终端的不同输出编码。在 Python 3 中,在这种情况下默认为 UTF-8(使用print()
函数)。curl -s
等价于curl --silent
,而jq -r
表示jq --raw-output
,即没有字符串引号。