如何将不显示的字符(如换行符 () 和制表符 (\t) 与 jq 的"连接"函数一起使用



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

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

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

使用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: %qn' "$rec"
done < <(jq -j --arg sep "$sep" 'join($sep)' <<<"$data")

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

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

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

Record: $'twornlines'
Record: *

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

#!/usr/bin/env bash
data='["twonlines","threettab-separatedtwords","*","nulu0000here"]'
while IFS= read -r -d '' rec || [[ $rec ]]; do
printf 'Record: %qn' "$rec"
done < <(jq -j '[.[] | gsub("u0000"; "@NUL@")] | join("u0000")' <<<"$data")

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

解决此问题的推荐方法是使用 -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。 输出为:Record: "oneu0018twou0018three"; 这是因为 JQ 正确地将八进制 30 转换为u0018在 JSON 字符串中。

其次,RS 是 ASCII 十进制 30,即八进制 36,即八进制 36,即 将写为$'36'在外壳中。 如果改用此值,程序将生成:Record: "oneu001etwou001ethree"因为那是 包含嵌入式 RS 字符的正确 JSON 字符串。 (作为记录$'30'是 Control-X。

第三,正如查尔斯·达菲(Charles Duffy(所指出的,"对于$(...天生就有问题。

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

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

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

$ 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(带制表符(

您只需使用 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 循环中引用子外壳。如果引用它,则无论 RecordSeparator 字符如何,它都将被视为单个参数。如果您不引用它,它将按预期工作。

最新更新