如何通过jq的“ join”功能使用换行符(\ n)和制表符(\ t)等非显示字符

时间:2018-07-08 03:45:44

标签: json bash variables jq

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

我想在非显示字符\30(“ RecordSeparator”)周围加入一个json数组,以便可以安全地在bash中对其进行迭代,但是我不知道该怎么做。我尝试了echo '["one","two","three"]' | jq 'join("\30")'和它的一些排列方式,但是没有用。

结果证明解决方案非常简单。...(请参阅答案)

4 个答案:

答案 0 :(得分:2)

使用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所使用的那样),因此在可以存储的数据范围内没有任何损失。当它们被切除时会忠实地传达-如果它们 did 进入外壳,它将(取决于版本)丢弃它们,或在第一次出现时截断字符串。< / p>

答案 1 :(得分:1)

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

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

输出:

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

OP提出的答案问题

基于$'\30'的OP的答案中的提案存在几个问题

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

第二,RS是ASCII十进制30,即八进制36,其中 将在外壳中写为$'\36'。 如果改用此值,程序将产生: Record: "one\u001etwo\u001ethree",因为那是 带有嵌入式RS字符的正确JSON字符串。 (因为记录$'\30'是Control-X。)

第三点,正如查尔斯·达菲(Charles Duffy)所指出的,“因为$(...)中的rec本质上是越野车。”

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

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

答案 2 :(得分:0)

--seq命令行选项一起使用时,RS字符在jq中是特殊的。例如,使用存储在名为data的shell变量中的JSON数组,我们可以按以下方式调用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"
$

答案 3 :(得分:-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字符如何,它都将被当作一个参数。如果您不引用它,它将按预期工作。