如何将数组转换为逗号分隔的字符串?

Arr*_*han 20 arrays bash shell

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

echo "${data[*]}"
Run Code Online (Sandbox Code Playgroud)

输出:

/QE-CI-RUN-71/workspace/QE-AU/57/testng-results_1.xml 
/QE-CI-RUN-71/workspace/QE-AU/57/testng-results_2.xml
Run Code Online (Sandbox Code Playgroud)

我想将上述输出存储为逗号分隔值。我怎样才能在 Bash 中实现这一目标?

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

Ada*_*atz 26

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

1.直接加入printf(通过Charles Duffy 的评论

printf -v joined '%s,' "${data[@]}"
echo "${joined%,}"
Run Code Online (Sandbox Code Playgroud)

printf内置隐含加入阵列。您可以像下面的 3a 一样使用单行阅读交互式打印printf '%s,' "${data[@]}",但会留下一个尾随逗号。(此方法甚至适用于 POSIX shell,但您必须将其$@用作数组,因为 POSIX 无法处理其他数组类型)。

2.更改$IFS字段分隔符(通过chepner的回答

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

join_arr , "${data[@]}"
Run Code Online (Sandbox Code Playgroud)

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

这可以在没有函数的情况下完成,但是保留有些麻烦$IFSCharles Duffy 指出,IFS="$OLD_IFS"在临时重新分配后恢复可以评估为IFS="",但是如果$IFS以前未定义,则unset IFS与可以将它们分开并且可以将它们分开,这种函数方法是由于使用了localto limit$IFS的范围,所以更干净。

3a. 循环遍历其内容(并逐步打印)

delim=""
for item in "${data[@]}"; do
  printf "%s" "$delim$item"
  delim=","
done
echo # add a newline
Run Code Online (Sandbox Code Playgroud)

如果该循环中的其他代码涉及外部调用(甚至sleep 0.1),您实际上将逐块观看此构建,这在交互式设置中很有帮助。

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

delim=""
joined=""
for item in "${data[@]}"; do
  joined="$joined$delim$item"
  delim=","
done
echo "$joined"
Run Code Online (Sandbox Code Playgroud)

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

data_string="${data[*]}"
echo "${data_string//${IFS:0:1}/,}"
Run Code Online (Sandbox Code Playgroud)

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

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

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

echo "${${data[@]}//${IFS:0:1}/,}"
Run Code Online (Sandbox Code Playgroud)

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

  • “字符串然后替换”方法也会改变其他空间。考虑`data=( "first item" "second item" "third item" )`; 您想要输出“第一项、第二项、第三项”,而不是“第一、项、第二、项、第三、项”。 (3认同)
  • 单独的大括号*不*限制更改的范围。这是命令组和子shell 之间的主要区别;命令组仍然在当前 shell 中执行。 (3认同)
  • 在实践中,顺便说一句,我通常会使用 `printf -v var '%s,' "${data[@]}"; echo "${var%,}"` -- 不会改变 `IFS`,并且不会对数据的外观做出假设。 (2认同)

che*_*ner 9

为了更容易定位 的变化IFS,请使用函数:

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

join , "${data[@]}"
Run Code Online (Sandbox Code Playgroud)

  • 有一种更短的方法来本地化该值, `(IFS=,; echo "${data[*]}")`,但代价是(几乎肯定)为子 shell 分叉一个新进程。 (2认同)

Cha*_*ffy 8

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

data=( first second third )
IFS=,
echo "${data[*]}"
Run Code Online (Sandbox Code Playgroud)

...发出:

first,second,third
Run Code Online (Sandbox Code Playgroud)

为了避免使 IFS 处于修改状态,您可以将此代码嵌入到函数中并将 IFS 声明为本地。如果您有 bash 4.3 或更高版本,则可以使用namevar支持来参数化保存到的变量输出,而无需子 shell 的开销:

comma_sep() {
  local -n comma_sep__dest=$1; shift || return
  local IFS=,
  comma_sep__dest=$*
}

comma_sep result "${data[@]}"
echo "$result" # prints first,second,third
Run Code Online (Sandbox Code Playgroud)

  • 请小心以这种方式定义“$IFS”;您可能不喜欢它稍后在脚本中对命令所做的操作。 (2认同)

小智 6

对于 ksh,试试这个!

foo=`echo $(echo ${data[@]}) | tr ' ' ','`
Run Code Online (Sandbox Code Playgroud)

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