如何在Bash中填充包含多行的字符串数组?

huangapple go评论93阅读模式
英文:

How to fill array with strings which can contain multiple lines in Bash?

问题

I'm having a variable with multiple strings, which can contain multiple lines:

var="foo 'bar baz' 'lorem
ipsum'"

I need all of them as array elements, so my idea was to use xargs -n1 to read every quoted or unquoted string into separate array elements:

mapfile -t arr < <(xargs -n1 <<< "$(echo "$var")")

But this causes this error:

xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option

Finally the only idea I had, was to replace the line feed against a carriage return and restore it afterwards:

# fill array                                  preserve line feed (dirty)
mapfile -t arr < <(xargs -n1 <<< "$(echo "$var" | tr '\n' '\r')")

# restore line feed
for (( i=0; i<${#arr[@]}; i++ )); do
  arr[i]=$(echo "${arr[$i]}" | tr '\r' '\n')
done

It works:

# for (( i=0; i<${#arr[@]}; i++ )); do echo "index: $i, value: ${arr[$i]}"; done
index: 0, value: foo
index: 1, value: bar baz
index: 2, value: lorem
ipsum

But only as long the input variable does not contain a carriage return.

I assume I need xargs output every result delimited by a null byte and import with mapfile's -d '', but it seems xargs is missing a print0 option (tr '\n' '\0' would manipulate the multi-line string itself).

英文:

I'm having a variable with multiple strings, which can contain multiple lines:

var=&quot;foo &#39;bar baz&#39; &#39;lorem
ipsum&#39;&quot;

I need all of them as array elements, so my idea was to use xargs -n1 to read every quoted or unquoted string into separate array elements:

mapfile -t arr &lt; &lt;(xargs -n1 &lt;&lt;&lt; &quot;$(echo &quot;$var&quot;)&quot; )

But this causes this error:

xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option

Finally the only idea I had, was to replace the line feed against a carriage return and restore it afterwards:

# fill array                                  preserve line feed (dirty)
mapfile -t arr &lt; &lt;(xargs -n1 &lt;&lt;&lt; &quot;$(echo &quot;$var&quot; | tr &#39;\n&#39; &#39;\r&#39;)&quot; )

# restore line feed
for (( i=0; i&lt;${#arr[@]}; i++ )); do
  arr[i]=$(echo &quot;${arr[$i]}&quot; | tr &#39;\r&#39; &#39;\n&#39;)
done

It works:

# for (( i=0; i&lt;${#arr[@]}; i++ )); do echo &quot;index: $i, value: ${arr[$i]}&quot;; done
index: 0, value: foo
index: 1, value: bar baz
index: 2, value: lorem
ipsum

But only as long the input variable does not contain a carriage return.

I assume I need xargs output every result delimited by a null byte and import with mapfile's -d &#39;&#39;, but it seems xargs is missing a print0 option (tr &#39;\n&#39; &#39;\0&#39; would manipulate the multi-line string itself).

答案1

得分: 1

这是清理过的代码,演示了如何使用Bash正则表达式从字符串中提取部分内容:

#!/bin/bash -p

var="foo 'bar baz' 'lorem
ipsum'"

leadspace_rx='^[[:space:]]+(.*)$'
bare_rx="^([^'[:space:]]+)(.*)$"
quoted_rx="^'([^']*)'(.*)$"

arr=()
while [[ -n $var ]]; do
    if [[ $var =~ $leadspace_rx ]]; then
        var=${BASH_REMATCH[1]}
    elif [[ $var =~ $bare_rx ]]; then
        arr+=( "${BASH_REMATCH[1]}" )
        var=${BASH_REMATCH[2]}
    elif [[ $var =~ $quoted_rx ]]; then
        arr+=( "${BASH_REMATCH[1]}" )
        var=${BASH_REMATCH[2]}
    else
        printf 'ERROR: Cannot handle: %s\n' "$var" >&2
        exit 1
    fi
done

declare -p arr

输出结果是 declare -a arr=([0]="foo" [1]="bar baz" [2]=$'lorem\nipsum')。如果您认为这个想法值得追求,可以轻松将字符串拆分的代码封装到一个函数中。当前的代码可能会执行一些出乎意料的操作,例如,字符串 a'b'c 被转换为数组 (a b c)。如果您能提供更精确的输入字符串格式规范,我可以尝试修改代码来处理它。

英文:

This Shellcheck-clean code demonstrates a way to do it by using Bash regular expressions to extract parts from the string:

#! /bin/bash -p

var=&quot;foo &#39;bar baz&#39; &#39;lorem
ipsum&#39;&quot;

leadspace_rx=&#39;^[[:space:]]+(.*)$&#39;
bare_rx=&quot;^([^&#39;[:space:]]+)(.*)$&quot;
quoted_rx=&quot;^&#39;([^&#39;]*)&#39;(.*)$&quot;

arr=()
while [[ -n $var ]]; do
    if [[ $var =~ $leadspace_rx ]]; then
        var=${BASH_REMATCH[1]}
    elif [[ $var =~ $bare_rx ]]; then
        arr+=( &quot;${BASH_REMATCH[1]}&quot; )
        var=${BASH_REMATCH[2]}
    elif [[ $var =~ $quoted_rx ]]; then
        arr+=( &quot;${BASH_REMATCH[1]}&quot; )
        var=${BASH_REMATCH[2]}
    else
        printf &#39;ERROR: Cannot handle: %s\n&#39; &quot;$var&quot; &gt;&amp;2
        exit 1
    fi
done

declare -p arr
  • The output is declare -a arr=([0]=&quot;foo&quot; [1]=&quot;bar baz&quot; [2]=$&#39;lorem\nipsum&#39;)
  • The code for splitting up a string could easily be encapsulated in a function if you think this idea is worth pursuing.
  • The current code does things that you might not expect. For instance, the string a&#39;b&#39;c is converted to the array (a b c). If you can provide a more precise specification for the format of input strings I'll see if the code can be modified to handle it.

huangapple
  • 本文由 发表于 2023年3月21日 01:51:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75793665-2.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定