【问题标题】:How to use non-displaying characters like newline (\n) and tab (\t) with jq's "join" function如何在 jq 的“join”功能中使用非显示字符,如换行符 (\n) 和制表符 (\t)
【发布时间】:2018-07-08 03:45:43
【问题描述】:

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

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

原来解决方案很简单....(见答案)

【问题讨论】:

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

标签: json bash variables jq


【解决方案1】:

使用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,它会(取决于版本)要么丢弃它们,要么在第一次出现时截断字符串。

【讨论】:

  • 警告:此解决方案假定 $data 中的 JSON 尚未包含编码的 RS 字符,或者应将此类字符视为与添加的 RS 字符相同的分隔符。
  • 当然——这是请求本身固有的。 (我有时在生产代码中采用这种方法,但是当我这样做时,我使用 NUL 作为分隔符,并从 jq 中的值中显式过滤掉它们。
  • @peak, ...我已对此进行了修改以显示使用 NUL 分隔符的示例。
  • @CharlesDuffy,你介意解释一下为什么|| [[ $rec ]] 是必要的吗?也许还有为什么在阅读之前设置IFS= 是必要的。 read-d 标志不会使 IFS 无关紧要吗?
  • @kael,这是因为join() 没有在最后一项之后放置尾随条目,并且read 返回非零退出状态,除非存在结束分隔符,即使它仍然填充在这种情况下,目标变量。因此,如果没有该条件,您将丢失列表中的最后一项。
【解决方案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 输出为:Record: "one\u0018two\u0018three"; 这是因为 jq 正确地将八进制 30 转换为 \u0018 在 JSON 字符串中。

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

第三,正如 Charles Duffy 所说,“$(...) 中的 for rec 本质上是错误的。”

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

第五,unset IFS 不保证能提前将 IFS 恢复到它的状态。

【讨论】:

  • 如果对如何修改它很小心,则无需unset IFSwhile IFS=$'\x1e' read -r -a recs 将更改范围限定为read,并且不会更改任何IFS 的值其他命令。
  • 非常有用的信息,谢谢。我现在不仅学会了不要将for 循环用于文件列表以外的任何内容,而且永远不要在公共场合谈论它;P。
  • 嘿。你也可以安全地使用for 来迭代数组——例如for x in "$@"for x in "${foo[@]}" 都可以。
【解决方案3】:

当与--seq 命令行选项一起使用时,jq 中的 RS 字符是特殊的。例如,将 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"
$

【讨论】:

    【解决方案4】:

    您只需使用 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 $(...) 本质上是错误的。尝试读取仅包含 * 的记录——您会看到它被替换为本地文件名列表。
    • ...相关:DontReadLinesWithFor
    猜你喜欢
    • 2016-09-17
    • 2021-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 2016-04-11
    • 1970-01-01
    相关资源
    最近更新 更多