提问人:kael 提问时间:7/8/2018 最后编辑:kael 更新时间:7/14/2023 访问量:7762
如何在 jq 的“join”函数中使用换行符 (\n) 和制表符 (\t) 等非显示字符
How to use non-displaying characters like newline (\n) and tab (\t) with jq's "join" function
问:
我在互联网上的任何地方都找不到它,所以我想我会把它添加为文档。
我想在非显示字符(“RecordSeparator”)周围加入一个 json 数组,这样我就可以在 bash 中安全地迭代它,但我不太清楚该怎么做。我尝试了几次排列,但没有奏效。\30
echo '["one","two","three"]' | jq 'join("\30")'
事实证明,解决方案非常简单......(见答案)
答:
您只需使用 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 字符。如果你不引用它,它将按预期工作。
评论
.|
for rec in $(...)
本质上是有问题的。尝试读取仅包含 -- 您会看到它被替换为本地文件名列表。*
解决问题的推荐方法是使用 -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
评论
unset IFS
while IFS=$'\x1e' read -r -a recs
read
IFS
for
for
for x in "$@"
for x in "${foo[@]}"
用于消除记录之间的文字换行符,并仅使用您自己的分隔符。这适用于您的简单情况: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,它将(取决于版本)丢弃它们, 或在字符串首次出现时截断字符串。
评论
jq
|| [[ $rec ]]
IFS=
read
-d
IFS
join()
read
当与命令行选项一起使用时,RS 字符在 jq 中是特殊的。例如,对于存储在名为 shell 变量中的 JSON 数组,我们可以按如下方式调用 jq:--seq
data
$ 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"
$
Sirrt 用于死灵发布,但它可能会帮助某人:
使用 jq 的标志:--raw-output
["one","two","three"] | jq --raw-output 'join("\t")'
生成(带制表符)one two three
评论
join
jq 'join("'$'\30''")'
\uxxxx
jq 'join("\u001e")'
jq
jq -n --arg str $'\030' '$str'
"\u0018"
--arg
$str
jq