如何在 jq 的“join”函数中使用换行符 (\n) 和制表符 (\t) 等非显示字符

How to use non-displaying characters like newline (\n) and tab (\t) with jq's "join" function

提问人:kael 提问时间:7/8/2018 最后编辑:kael 更新时间:7/14/2023 访问量:7762

问:

我在互联网上的任何地方都找不到它,所以我想我会把它添加为文档。

我想在非显示字符(“RecordSeparator”)周围加入一个 json 数组,这样我就可以在 bash 中安全地迭代它,但我不太清楚该怎么做。我尝试了几次排列,但没有奏效。\30echo '["one","two","three"]' | jq 'join("\30")'

事实证明,解决方案非常简单......(见答案)

JSON bash 变量 jq

评论

0赞 kael 7/9/2018
对不起已经回答这个问题的两个人,我原来的问题措辞不好。为了清楚起见,我现在已经对其进行了编辑。具体来说,我只是在寻找在函数中使用非显示字符的语法。我的原始答案(使用类似的东西)和@Charles Duffy 的答案(使用这样的语法:)都很好用。请注意,@Charles Duffy 的答案对于迭代有一些额外的价值。joinjq 'join("'$'\30''")'\uxxxxjq 'join("\u001e")'
0赞 Charles Duffy 7/9/2018
啊,明白了。基本上,规则是,为了相信你的代码将与未来的版本一起使用(正如峰值所表明的那样,将来对未转义的文字可能不那么宽松),你应该使用 JSON 转义语法。顺便说一句,要将 bash 转义转换为 JSON 转义,您可以执行如下操作: ,它输出 .或者,您可以使用相同的 () 语法来传递文字,并在 .:)jqjq -n --arg str $'\030' '$str'"\u0018"--arg$strjq

答:

-1赞 kael 7/8/2018 #1

您只需使用 bash 的语法来插入内联特殊字符,如下所示: .$'\30'echo '["one","two","three"]' | jq '. | join("'$'\30''")'

下面是整个工作示例:

data='["one","two","three"]'

IFS=$'\30'
for rec in $(echo "$data" | jq '. | join("'$'\30''")'); do
    echo "Record: $rec"
done
unset IFS

这将打印

Record: one
Record: two
Record: three

不出所料。

注意:重要的是不要在 for 循环中引用子 shell。如果引用它,它将被视为单个参数,而不考虑 RecordSeparator 字符。如果你不引用它,它将按预期工作。

评论

0赞 peak 7/9/2018
首字母是不必要的。.|
0赞 Charles Duffy 7/9/2018
for rec in $(...)本质上是有问题的。尝试读取仅包含 -- 您会看到它被替换为本地文件名列表。*
0赞 Charles Duffy 7/9/2018
...相关新闻: DontReadLinesWithFor
1赞 peak 7/9/2018 #2

解决问题的推荐方法是使用 -c 命令行 选项,例如:

echo "$data" | jq -c '.[]' |
while read -r rec
do
    echo "Record: $rec"
done

输出:

Record: "one"
Record: "two"
Record: "three"

OP提出的答案存在问题

OP的答复中存在几个问题,基于以下几点$'\30'

首先,它不能可靠地工作,例如在 Mac 上使用 bash 输出为:; 这是因为 jq 正确地将八进制 30 转换为 JSON 字符串。Record: "one\u0018two\u0018three"\u0018

其次,RS 是 ASCII 十进制 30,即八进制 36,其中 将像在 shell 中一样编写。 如果改用此值,程序将生成: 因为那是 包含嵌入 RS 字符的正确 JSON 字符串。(记录在案的是 Control-X。$'\36'Record: "one\u001etwo\u001ethree"$'\30'

第三,正如查尔斯·达菲(Charles Duffy)所指出的,“对于以$(...本质上是有问题的。

第四,任何假设 jq 将来都会接受的方法 非法的 JSON 字符串是脆弱的,因为在 将来,JQ 可能会禁止它们,或者至少需要命令行 切换以允许它们。

第五,不能保证事先将IFS恢复到其状态。unset IFS

评论

0赞 Charles Duffy 7/9/2018
如果仔细修改它,则没有必要这样做:将更改范围限定为 ,并且不会更改任何其他命令的值。unset IFSwhile IFS=$'\x1e' read -r -a recsreadIFS
0赞 kael 7/9/2018
非常有用的信息,谢谢。我现在不仅学会了除了文件列表之外,永远不要对任何东西使用循环,而且也永远不要在公共;P谈论它。for
0赞 Charles Duffy 7/9/2018
嘿。你也可以安全地用于迭代数组 - 或者很好,例如。forfor x in "$@"for x in "${foo[@]}"
4赞 Charles Duffy 7/9/2018 #3

用于消除记录之间的文字换行符,并仅使用您自己的分隔符。这适用于您的简单情况:jq -j

#!/usr/bin/env bash
data='["one","two","three"]'
sep=$'\x1e' # works only for non-NUL characters, see NUL version below
while IFS= read -r -d "$sep" rec || [[ $rec ]]; do
  printf 'Record: %q\n' "$rec"
done < <(jq -j --arg sep "$sep" 'join($sep)' <<<"$data")

...但它也适用于更有趣的场景,即幼稚的答案会失败:

#!/usr/bin/env bash
data='["two\nlines","*"]'
while IFS= read -r -d $'\x1e' rec || [[ $rec ]]; do
  printf 'Record: %q\n' "$rec"
done < <(jq -j 'join("\u001e")' <<<"$data")

返回(在 Cygwin 上运行时,因此为 CRLF):

Record: $'two\r\nlines'
Record: \*

也就是说,如果愤怒地使用它,我建议使用 NUL 分隔符,并将它们从输入值中过滤掉:

#!/usr/bin/env bash
data='["two\nlines","three\ttab-separated\twords","*","nul\u0000here"]'
while IFS= read -r -d '' rec || [[ $rec ]]; do
  printf 'Record: %q\n' "$rec"
done < <(jq -j '[.[] | gsub("\u0000"; "@NUL@")] | join("\u0000")' <<<"$data")

NUL 是一个不错的选择,因为它是一个根本无法存储在 C 字符串中的字符(就像 bash 使用的字符串一样),因此在它们被切除时可以忠实传达的数据范围不会丢失——如果它们确实进入了 shell,它将(取决于版本)丢弃它们, 或在字符串首次出现时截断字符串。

评论

0赞 peak 7/9/2018
警告:此解决方案假定 $data 中的 JSON 尚未包含编码的 RS 字符,或者此类字符应以与添加的 RS 字符相同的方式被视为分隔符。
0赞 Charles Duffy 7/9/2018
当然,这是请求本身的内在要求。(我有时在生产代码中采用这种方法,但是当我这样做时,我使用NUL作为分隔符,并将它们从中的值中显式过滤掉)。jq
0赞 Charles Duffy 7/9/2018
@peak,...我对此进行了修改,以显示一个使用 NUL 分隔符的示例。
0赞 kael 7/9/2018
@CharlesDuffy,你介意解释一下为什么有必要吗?也许也是为什么在读取之前设置是必要的。国旗不是无关紧要吗?|| [[ $rec ]]IFS=read-dIFS
0赞 Charles Duffy 7/9/2018
@kael,这是因为它不会在最后一项之后放置尾随条目,并且除非存在结束分隔符,否则返回非零退出状态,即使在这种情况下它仍然填充目标变量。因此,如果没有该条件,您将丢失列表中的最后一项。join()read
0赞 peak 7/10/2018 #4

当与命令行选项一起使用时,RS 字符在 jq 中是特殊的。例如,对于存储在名为 shell 变量中的 JSON 数组,我们可以按如下方式调用 jq:--seqdata

$ jq -n --seq --argjson arg '[1,2]' '$arg | .[]'

以下为文字实录:

$ data='["one","two","three"]'
$ jq -n --seq --argjson arg "$data" '$arg | .[]' | tr $'\36' X
X"one"
X"two"
X"three"
$
0赞 rockad 7/14/2023 #5

Sirrt 用于死灵发布,但它可能会帮助某人:

使用 jq 的标志:--raw-output

["one","two","three"] | jq --raw-output 'join("\t")'生成(带制表符)one two three