【问题标题】:How can I convert an array into a comma separated string?如何将数组转换为逗号分隔的字符串?
【发布时间】:2019-05-19 05:43:18
【问题描述】:

我有一个数组,我正在这样打印它:

echo "${data[*]}"

输出:

/QE-CI-RUN-71/workspace/QE-AU/57/testng-results_1.xml 
/QE-CI-RUN-71/workspace/QE-AU/57/testng-results_2.xml

我想将上述输出存储为逗号分隔值。如何在 Bash 中实现这一点?

数据数组是动态的,它可以有任意数量的值。

【问题讨论】:

  • echo "${data[*]}" 不应该生成换行符,除非它们在值本身中。您能否在edit 中包含declare -p data 的输出,这样我们就可以看到数组的实际形式是什么? (如果不是一个数组,它实际上只是一个带有文字换行符的大字符串,这将解释您当前的输出)。
  • 我添加了 bash 标签,因为 POSIX shell 不允许使用数组。如果您指的是不同的外壳,请更正此问题。
  • zsh 一站式解决方案:echo "${${data[@]}//${IFS:0:1}/,}"
  • 这能回答你的问题吗? How can I join elements of an array in Bash?

标签: arrays bash shell


【解决方案1】:

如果您想用逗号分隔,请将其作为IFS 中的第一个字符:

data=( first second third )
IFS=,
echo "${data[*]}"

...发射:

first,second,third

【讨论】:

  • 小心以这种方式定义$IFS;您可能不喜欢它稍后在脚本中对命令的作用。
  • 问题是oIFS=$IFS; ...; IFS=$oIFS也不是noop——它会将unset IFS更改为IFS='',这是具有不同行为的两种不同状态(前者的行为类似于IFS=$' \t\n' )。如果想要安全,local 方法或使用子shell 确定范围是合适的——而且 chepner 的答案已经涵盖了它们。我宁愿不编写假装具有实际上并不存在的安全功能的代码,并且还要考虑任何在没有显式 IFS 值的情况下执行不带引号的扩展的代码(或者在运行 read 时无法显式设置 IFS)天生就坏了。
【解决方案2】:

有几种方法可以做到这一点:

1。 直接加入printf(通过Charles Duffy’s comment

printf -v joined '%s,' "${data[@]}"
echo "${joined%,}"

printf 内置隐式连接数组。您可以像下面的 3a 那样交互打印,使用单行阅读 printf '%s,' "${data[@]}",但您会留下一个尾随逗号。 (这种方法甚至可以在 POSIX shell 中使用,尽管您必须使用 $@ 作为您的数组,因为 POSIX 无法处理其他数组类型)。

2。 更改$IFS 字段分隔符(通过chepner’s answer

join_arr() {
  local IFS="$1"
  shift
  echo "$*"
}

join_arr , "${data[@]}"

这仅在此函数的范围内重新定义字段分隔符,因此当 $data 数组自动扩展时,它使用所需的分隔符而不是全局 $IFS 的第一个值或(如果它为空或未定义)空间。

这可以在没有函数的情况下完成,但是保留$IFS 有一些麻烦:Charles Duffy notes that 在临时重新分配后恢复IFS="$OLD_IFS" 可以评估为IFS="",但如果$IFS 以前未定义,那就不同了来自unset IFS,虽然可以将它们分开,但由于使用local 来限制$IFS 的范围,这种功能性方法要干净得多。

此解决方案仅支持单字符分隔符。有关支持任意长度分隔符的类似功能,请参见下面的 #5。

3a。 循环浏览其内容(并逐步打印)

delim=""
for item in "${data[@]}"; do
  printf "%s" "$delim$item"
  delim=","
done
echo # add a newline

如果该循环中的其他代码涉及外部调用(甚至是 sleep 0.1),您实际上会观看这个构建,这对交互式设置很有帮助。 p>

3b。 遍历其内容(并构建一个变量)

delim=""
joined=""
for item in "${data[@]}"; do
  joined="$joined$delim$item"
  delim=","
done
echo "$joined"

4。 将数组保存为字符串并对其运行替换(注意,数组必须缺少空格*)

data_string="${data[*]}"
echo "${data_string//${IFS:0:1}/,}"

* 仅当$IFS 的第一个字符(默认为空格) 存在于数组的任何项中时才有效。

这使用 bash 模式替换:${parameter//pattern/string} 将用string 替换$parameterpattern 的每个实例。在这种情况下,string${IFS:0:1}$IFS 的子字符串从开头开始,在一个字符之后结束。

Z Shell (zsh) 可以在一个嵌套参数扩展中做到这一点:

echo "${${data[@]}//${IFS:0:1}/,}"

(尽管 Z Shell 也可以在此答案下方的评论中使用其专用的 join 标志作为 echo "${(j:,:)data}" 作为 noted by @DavidBaynard 更优雅地做到这一点。)

5。 在隐式循环中加入替换(通过Nicholas Sushkin's answer to a duplicate question

join_by() {
  local d="${1-}" f="${2-}"
  if shift 2; then
    printf %s "$f" "${@/#/$d}"
  fi
}

join_by , "${data[@]}"

这与上面的 #2 非常相似(通过 chepner),但它使用模式替换而不是 $IFS,因此支持多字符分隔符。 $d 保存分隔符,$f 保存数组中的第一项(我稍后会说原因)。真正的魔力是${@/#/$d},它将每个数组元素的开头(#)替换为分隔符($d)。由于您不想从分隔符开始,因此它使用shift 不仅可以通过分隔符参数,还可以通过第一个数组元素(另存为$f),然后将其打印在替换之前。

printf 在你给它额外的参数时会有一个奇怪的行为,就像我们在这里做的那样。模板 (%s) 仅指定将有一个参数,因此其余参数就像一个循环一样,并且它们都相互连接。考虑将该关键行更改为printf "%s\n" "$f" "${@/#/$d}"。您最终会在每个元素之后添加一个换行符。如果您在打印连接的数组后想要一个尾随换行符,请使用printf %s "$f" "${@/#/$d}" $'\n'(我们需要使用$'…' 符号来告诉bash 解释转义;另一种方法是插入一个文字换行符,但是那么代码看起来很奇怪)。

【讨论】:

  • “string-then-replacement”方法也会改变其他空格。考虑data=( "first item" "second item" "third item" );你想要first item,second item,third item 的输出,而不是first,item,second,item,third,item
  • 实际上,顺便说一句,我通常会使用printf -v var '%s,' "${data[@]}"; echo "${var%,}"——不会更改IFS,也不会对数据的样子做出假设。
  • 大括号本身不会限制更改的范围。这是命令组和子shell 之间的主要区别。命令组仍然在当前 shell 中执行。
  • @AdamKatz 对于 zsh,你可以使用 "${(j:,:)data}" 来做同样的事情。单词拆分有一个微妙之处: > 请注意,这发生在 s:string: 标志或 SH_WORD_SPLIT 选项进行字段拆分之前。 (来源:zsh.sourceforge.io/Doc/Release/…
  • @DavidBaynard - 谢谢,我已将其添加到答案中。它没有自己的部分,因为这是一个 Bash 问题,但我认为值得一提。
【解决方案3】:

为了更容易本地化IFS的变化,使用一个函数:

join () {
  local IFS="$1"
  shift
  echo "$*"
}

join , "${data[@]}"

【讨论】:

  • 我喜欢这个。它解决了覆盖$IFS的问题,并使用了其他语言用户会非常熟悉的语法。
  • 有一种更短的方法来本地化该值,(IFS=,; echo "${data[*]}"),但代价是(几乎可以肯定)为子 shell 分叉一个新进程。
  • 大括号也不会本地化 IFS 的值。你必须使用一个子shell来做到这一点。该函数的好处是可以使用local命令避免覆盖全局值。
【解决方案4】:
printComma(){
    printf "%s," "${@:1:${#}-1}"
    printf "%s" "${@:${#}}"
}
printNewline(){
    printf "%s\n" "${@:1:${#}-1}"
    echo "${@:${#}}"
}
join () {
  local IFS="$1"
  shift
  echo "$*"
}
declare -a comma=(
    a
    b
    c
)
declare -a newline=(
    xyz
    abc
    def
    ghi
)
echo "\
Comma separated list $(printComma "${comma[@]}")
Newline list: $(printNewline "${newline[@]}")

Comma separated list $(join , "${comma[@]}")
Newline list: $(join \n "${newline[@]}")"
Comma separated list a,b,c
Newline list: xyz
abc
def
ghi

Comma separated list a,b,c
Newline list: xyznabcndefnghi

【讨论】:

    【解决方案5】:

    对于 ksh,试试这个!

    foo=`echo $(echo ${data[@]}) | tr ' ' ','`
    

    通过这种方式,您可以通过将空格(默认)转换为逗号来控制分隔符! (或任何其他你能想到的):)

    【讨论】:

      猜你喜欢
      • 2020-05-21
      • 2011-06-18
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      • 2021-12-06
      相关资源
      最近更新 更多