Bash 条目包含空格的数组扩展

Bash Array expansion with entries containing whitespaces

将包含白色space的数组传递给另一个函数时出现奇怪的问题。当有问题的数组随后从目标函数的上下文中展开时会发生错误,似乎 whitespaces 被错误地解释为分隔符,因此:

  1. 不保留每个数组条目中每个单词之间的空格。
  2. 前面直接有白色的每个单词space,在结果数组中占据一个单独的索引。

如何确保传递给函数的数组在后续扩展后保留其条目中的任何 space?

可以使用以下代码重现错误:

function caller(){

  #string to convert to array.
  str="var1=val1,var2=val2,var3=value\ with\ a\ space"

  #convert argument string to full name value pairs array
  fullNameValPairsArr=($(toNameValuePairsArray "$str"))

  callee fullNameValPairsArr    
}


#called by the top-level "caller" function and passed the array as an argument
function callee(){

  passedArray=[@]

  expandedPassedArray=("${!passedArray}")

  for curEntry in "${expandedPassedArray[@]}"
  do
    
       echo $curEntry
  
  done    

 }



#helper function converts arg string to name value pairs array.
function toNameValuePairsArray(){

  #store args string to local
  fullNameValPairsString=""

  #first split the full name-value pair parameters(via comma delimiter).
  IFS=','

  #Read the split words into an array based on space delimiter
  read -a nameValPairsArr <<< "$fullNameValPairsString"

 
  echo "${nameValPairsArr[@]}"      

}

caller

调用顶级“caller”函数的输出是:

var1=val1                                                                                                                                                    
var2=val2                                                                                                                                                    
var3=value                                                                                                                                                   
with                                                                                                                                                         
a                                                                                                                                                            
space 

如您所见,包含 space 的最后一个参数(即带有 space 的值)被拆分为数组中的几个单独条目;字符串中的 space 未保留。

这是您目前正在做的事情:

  1. read -a
  2. 小心地将字符串拆分成正确的数组
  3. 立即用echo
  4. 连接所有带空格的元素
  5. 用分词的方式把所有的元素都放在空格上

如果您只是跳过第 2 步和第 3 步,就不会出现此问题。

没有从 bash 函数中获取 return 值的好方法,但是您可以,例如使用全局变量:

function caller(){
  #string to convert to array.
  str="var1=val1,var2=val2,var3=value\ with\ a\ space"

  #convert argument string to full name value pairs array
  toNameValuePairsArray "$str"

# Copy from the global variable to an array of our choice
  fullNameValPairsArr=( "${toNameValuePairsArray_result[@]}" )
  callee fullNameValPairsArr
}

#called by the top-level "caller" function and passed the array as an argument
function callee(){
  passedArray=[@]
  expandedPassedArray=("${!passedArray}")
  for curEntry in "${expandedPassedArray[@]}"
  do
       echo $curEntry
  done
 }

#helper function converts arg string to name value pairs array.
function toNameValuePairsArray(){
  #store args string to local
  fullNameValPairsString=""
  #first split the full name-value pair parameters(via comma delimiter).
  IFS=','
  # "return" by assigning to a global variable
  read -a toNameValuePairsArray_result <<< "$fullNameValPairsString"
}

caller

这导致:

var1=val1
var2=val2
var3=value with a space

这是您的主要错误:

  fullNameValPairsArr=($(toNameValuePairsArray "$str"))

因为命令替换未加引号,shell 将对输出执行分词,将带空格的参数分成单独的词。


我会使用允许您将变量 names 传递给函数的“namerefs”。
需要 bash 版本 4.3+。
请参阅手册中的 Shell Parameters

if declare -n a=b 2>/dev/null; then
    unset a
else
    echo "This bash version ($BASH_VERSION) does not implement namerefs." >&2
    exit 1
fi

caller() {
    local str="var1=val1,var2=val2,var3=value\ with\ a\ space"
    local -a fullNameValPairsArr    

    toNameValuePairsArray "$str" fullNameValPairsArr    

    callee fullNameValPairsArr    
}


callee() {
    local -n passedArray=
    for curEntry in "${passedArray[@]}"; do
        echo "$curEntry"
    done    
}


toNameValuePairsArray() {
    local fullNameValPairsString=""
    local -n _ary=      # cannot use same varname as caller

    # don't set *global* IFS
    IFS=',' read -a _ary <<< "$fullNameValPairsString"
}

caller

产出

var1=val1
var2=val2
var3=value with a space

我会更进一步,将输入解析为 关联 数组:

# check bash version...

caller() {
    local str="var1=val1,var2=val2,var3=value\ with\ a\ space"
    #local -a fullNameValPairsArr    
    local -A fullNameValPairsArr    

    toNameValuePairsArray "$str" fullNameValPairsArr    

    callee fullNameValPairsArr    
}


callee() {
    local -n passedArray=
    for idx in "${!passedArray[@]}"; do
        echo "$idx => ${passedArray[$idx]}"
    done    
}


toNameValuePairsArray() {
    local fullNameValPairsString=""
    local -n _ary=      # cannot use same varname as caller

    # don't set *global* IFS
    IFS=',' read -a pairs <<< "$fullNameValPairsString"
    for pair in "${pairs[@]}"; do
        IFS="=" read var value <<<"$pair"
        _ary[$var]=$value
    done
}

caller

产出

var1 => val1
var2 => val2
var3 => value with a space

最后一点,bash 5.1 提供了一个新的可加载 csv 命令

BASH_LOADABLES_PATH="${BASH%/bin/bash}/lib/bash"
enable -f csv csv

str="var1=val1,var2=val2,var3=value\ with\ a\ space"
csv -a pairs "$str"

declare -p pairs

结果

declare -a pairs=([0]="var1=val1" [1]="var2=val2" [2]="var3=value\ with\ a\ space")

文档:

$ help csv
csv: csv [-a ARRAY] string
    Read comma-separated fields from a string.

    Parse STRING, a line of comma-separated values, into individual fields,
    and store them into the indexed array ARRAYNAME starting at index 0.
    If ARRAYNAME is not supplied, "CSV" is the default array name.