问题描述
我试图找到一种干净的方法,在 while 循环中将 2 个 json 字符串值分配给 2 个变量。我正在使用的循环和输入如下。
输入:
foo='
[
{
"name":"name string (more info)","nested_name":{
"name":"my name","conclusion":"failure","number":11
}
},{
"name":"name string (more info)","nested_name":{
"name":"my other name","number":13
}
}
]'
目前我有以下几点:
echo "$foo" | jq ".[].name,.[].nested_name.name" | while read -r foo bar; do
# do stuff with foo and bar
done
在迭代 1 中,我想要:
foo = "name string (more info)"
bar = "my name"
迭代 2:
foo = "name string (more info)"
bar = "my other name"
然而,这会产生以下不正确的输出:
foo = "name
bar = string (more info)"
foo = "name
bar = string (more info)"
foo = "my
bar = name"
foo = "my
bar = other name"
我一整天都在做这件事,非常感谢任何意见或建议!
解决方法
使用两个 read
,每行一个:
#!/usr/bin/env bash
json='
[
{
"name":"name string (more info)","nested_name":{
"name":"my name","conclusion":"failure","number":11
}
},{
"name":"name string (more info)","nested_name":{
"name":"my other name","number":13
}
}
]'
while read -r foo && read -r bar; do
printf "foo=%s\nbar=%s\n" "$foo" "$bar"
done < <(jq ".[] | .name,.nested_name.name" <<<"$json")
我不得不更改您的 jq
表达式以获得您想要的输出。
还要注意对原始 JSON 文本和循环变量使用不同的名称,使用 here string 代替 echo
,并使用文件重定向代替管道;如果 while
的主体设置了以后需要的任何变量,则很有用。
另一种方法是将所有 jq
输出读入一个数组,然后对其进行迭代:
readarray -t lines < <(jq ".[] | .name,.nested_name.name" <<<"$json")
for (( i = 0; i < ${#lines[@]}; i += 2 )); do
foo=${lines[i]}
bar=${lines[i+1]}
printf "foo=%s\nbar=%s\n" "$foo" "$bar"
done
,
如果我模拟一些“CSV 输入”,我可以这样做:(注意 IFS
设置为 ,
)
echo 'aaa,bbb\nccc,ddd' | while IFS=,read -r x y; do
echo "x=$x,y=$y"
done
输出:
x=aaa,y=bbb
x=ccc,y=ddd
您可以使用以下 jq
命令生成类似的“CSV 输出”:
jq --raw-output '.[] | "\(.name),\(.nested_name.name)"'
所以我认为它看起来像这样:
echo "$foo" | jq --raw-output '.[] | "\(.name),\(.nested_name.name)"' | while IFS=,read -r foo bar; do
# do stuff with foo and bar
done
,
这个怎么样:https://jqplay.org/s/4MPWmMjFmM
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class DictionaryCollectionType extends AbstractType {
public function buildForm(FormBuilderInterface $builder,array $options)
{
$builder->add('dictionary',CollectionType::class,[
'entry_type' => $options['type'],'entry_options' => array('label' => false),'empty_data' => null,'allow_add' => true,'allow_delete' => true,'label' => false,])
->add('save',SubmitType::class,[
'attr' => ['class' => 'save btn btn-success mt-2','data-toggle' => 'modal','data-target' => '#dictionaryBackdrop','action' => $options['action']],]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,'type' => null,'action' => null
));
}
}
,
我更喜欢这样做:
jq --argjson foo "$foo" --null-input --compact-output '$foo[]' | while IFS= read -r item
do
name="$(jq --argjson item "$item" --null-input --raw-output '$item.name')"
nested_name="$(jq --argjson item "$item" --null-input --raw-output '$item.nested_name.name')"
printf 'name is "%s" and nested_name is "%s"\n' "$name" "$nested_name"
done
虽然此方法需要额外调用 jq
,但它有几个好处:
- 如果您需要迭代具有更复杂结构的东西,它很容易扩展。
- 不使用空格、逗号、引号、制表符和换行符是安全的。
- 简单易懂,值得信赖。它不涉及任何特殊算法、解析或转义。
如果您想要更简洁一些,这里也可以使用缩写选项。
jq --argjson foo "$foo" -nc '$foo[]' | while IFS= read -r item
do
name="$(jq --argjson item "$item" -nr '$item.name')"
nested_name="$(jq --argjson item "$item" -nr '$item.nested_name.name')"
printf 'name is "%s" and nested_name is "%s"\n' "$name" "$nested_name"
done
再详细一点:
- 我们使用
--argjson
将我们的 JSON 字符串作为单个参数传入,并使用--null-input
忽略标准输入。这样我们就不必担心传入过程中的转义或分隔符。 - 我们使用
--compact-output
(或-c
)每行输出一个 JSON 对象。 - 我们使用
IFS=
来确保我们的 JSON 字符串中的空格和制表符不会被用作分隔符。只有换行符将用作分隔符,并且因为 JSON 字符串中的所有换行符都必须进行转义,所以我们可以确信对于原始 JSON 数组中的每个项目,我们将只循环一次。 - 我们使用
--raw-output
(或-r
)将JSON字符串的值直接输出到shell变量中。 (如果我们想要 JSON 编码的字符串,我们可以选择省略它。)