使用 jq 将数字字节值数组转换为字符串

Convert an array of numeric byte values into a string with jq

我将 UTF-8 字符串的二进制表示形式作为数值数组,每个值都在 0..255 范围内。

如何使用jq将该数组转换为字符串?内置 implode 仅处理代码点数组。此外,jq.

中没有按位运算的函数

此类数组在 newman (Postman CLI) json 输出中被观察为值,属性 response.stream.data.

例如,字符串 "Hi, Мир!" 进入 [72,105,44,32,208,156,208,184,209,128,33] 字节数组,而它的代码点是 [72,105,44,32,1052,1080,1088,33]。后者的 implode 给出原始字符串,而前者的 implode 给出 "Hi,ÐиÑ!" 或类似的东西。

def btostring:
  if length == 0 then ""

  elif .[0] >= 240 then
     ([((((.[0] - 240) * 64) + (.[1] - 128)) * 64 + (.[2] - 128)) * 64 + (.[3] - 128)]
      | implode) + (.[4:] | btostring)

  elif .[0] >= 224 then
     ([  ((.[0] - 224) * 64 +  (.[1] - 128)) * 64 + (.[2] - 128)]
      | implode) + (.[3:] | btostring)

  elif .[0] >= 128 then
     ([  (.[0] - 192)  * 64 +  (.[1] - 128) ]
      | implode) + (.[2:] | btostring)

  else  (.[0:1] | implode ) + (.[1:] | btostring)
  end;

示例:

def hi: [72,105,44,32,208,156,208,184,209,128,33] ;

hi | btostring

输出(使用jq -r):

Hi, Мир!

扩展示例:

def hi: [72,105,44,32,208,156,208,184,209,128,33];
def euro: [226,130,172];        # 11100010 10000010 10101100
def fire: [240,159,156,130];    # 11110000 10011111 10011100 10000010

(hi, euro, fire) | btostring

输出:

Hi, Мир!
€

(在某些设备上,上面的最后一行将是一个框而不是三角形。)

另一种使用 foreach 的方法。主要思想是保持当前 char 的剩余字节数 (.[0]) 和到目前为止读取的位 (.[1])。这是过滤器:

[foreach .[] as $item (
    [0, 0]
    ;
    if .[0] > 0 then [.[0] - 1, .[1] * 64 + ($item % 64)]
    elif $item >= 240 then [3, $item % 8]
    elif $item >= 224 then [2, $item % 16]
    elif $item >= 192 then [1, $item % 32]
    elif $item < 128 then [0, $item]
    else error("Malformed UTF-8 bytes")
    end
    ;
    if .[0] == 0 then .[1] else empty end
)] | implode

此外,对格式错误字节的错误检测尚未完成。

这里是本页其他地方给出的递归btostring的非递归版本,主要是为了说明在 jq 中如何将递归实现转化为非递归实现。

def btostring:
  . as $in
  | [ foreach range(0;length) as $ix ({skip:0, point:[]};
        if .skip > 0 then .skip += -1
        elif $in[$ix] >= 240 then
          .point = [(((($in[$ix]   - 240)  * 64)
                     + ($in[$ix+1] - 128)) * 64
                     + ($in[$ix+2] - 128)) * 64
                     + ($in[$ix+3] - 128)]
          | .skip = 3
        elif $in[$ix] >= 224 then
          .point = [  (($in[$ix]   - 224)  * 64
                     + ($in[$ix+1] - 128)) * 64
                     + ($in[$ix+2] - 128)]
          | .skip = 2
        elif $in[$ix] >= 128 then
          .point = [   ($in[$ix]   - 192)  * 64
                     + ($in[$ix+1] - 128)]
          | .skip = 1
        else .point = $in[$ix:$ix+1]
        end;
        if .skip == 0 then .point|implode else empty end) ]
  | add ;

基于 that I used successully, here's an extension that does not break or fail with invalid UTF-8 encoding.

无效的 UTF-8 起始字节(128-193、245-255)或序列被解释为 ISO 8859-1。

def btostring:
    if type != "array" then .
    elif length == 0 then ""
    elif .[0] >= 245 then
        (.[0:1] | implode ) + (.[1:] | btostring)
    elif .[0] >= 240 then
        if length >= 4 and .[1] >= 128 and .[2] >= 128 and .[3] >= 128 then
            ([((((.[0] - 240) * 64) + (.[1] - 128)) * 64 + (.[2] - 128)) * 64 + (.[3] - 128)] | implode) + (.[4:] | btostring)
        else
            (.[0:1] | implode ) + (.[1:] | btostring)
        end
    elif .[0] >= 224 then
        if length >= 3 and .[1] >= 128 and .[2] >= 128 then
            ([  ((.[0] - 224) * 64  + (.[1] - 128)) * 64 + (.[2] - 128)] | implode) + (.[3:] | btostring)
        else
            (.[0:1] | implode ) + (.[1:] | btostring)
        end
    elif .[0] >= 194 then
        if length >= 2 and .[1] >= 128 then
            ([   (.[0] - 192) * 64  + (.[1] - 128) ] | implode) + (.[2:] | btostring)
        else
            (.[0:1] | implode ) + (.[1:] | btostring)
        end
    else
        (.[0:1] | implode ) + (.[1:] | btostring)
    end;