如何将数组转换为逗号分隔的字符串?
如何将一个数组转换为逗号分隔的字符串?
有几种方法可以实现这个目标:
1. 使用`printf`直接连接数组元素(通过Charles Duffy的评论)
printf -v joined '%s,' "${data[@]}" echo "${joined%,}"
`printf`内置命令可以隐式地连接数组元素。你可以像3a那样以交互方式打印,使用一行代码`printf '%s,' "${data[@]}"`,但是会留下一个尾随的逗号。这种方法甚至可以在POSIX shell中工作,只需将数组改为`$@`,因为POSIX无法处理其他类型的数组。
2. 改变`$IFS`字段分隔符(通过chepner的回答)
join_arr() { local IFS="$1" shift echo "$*" } join_arr , "${data[@]}"
这个方法在函数的作用域内重新定义了字段分隔符,这样当自动扩展`$data`数组时,它将使用所需的分隔符,而不是全局`$IFS`的第一个值,或者(如果它为空或未定义)使用空格。这个方法也可以在没有函数的情况下实现,但是需要注意保存和恢复`$IFS`的值,这种函数式的方法更加简洁,因为使用`local`关键字限制了`$IFS`的作用范围。
这个解决方法只支持单字符的分隔符。参见下面的第5种方法,它支持任意长度的分隔符。
3a. 遍历数组内容(并逐个打印)
delim="" for item in "${data[@]}"; do printf "%s" "$delim$item" delim="," done echo
如果循环中的其他代码涉及外部调用(甚至是`sleep 0.1`),你实际上可以看到这个字符串逐个构建的过程,这在交互环境中很有帮助。
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}`会将`$parameter`中的每个`pattern`替换为`string`。在这种情况下,`string`是`${IFS:0:1}`,即从开头开始到一个字符结束的`$IFS`的子字符串。
Z Shell(`zsh`)可以在一个嵌套的参数展开中完成这个操作:
echo "${${data[@]}//${IFS:0:1}/,}"
(尽管Z Shell也可以使用其专用的`join`标志更简洁地实现,如下所示:`echo "${(j:,:)data}"`)
5. 使用替换隐式循环连接(通过Nicholas Sushkin对一个重复问题的回答)
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解释转义字符;另一种方法是插入一个字面换行符,但是代码看起来很奇怪)。
“字符串-然后替换”的方法也会改变其他空格。考虑`data=("first item" "second item" "third item")`;你期望的输出是`first item,second item,third item`,而不是`first,item,second,item,third,item`。
这是我在没有更好的数据示例的情况下的最佳解决方案。我尝试解决这个问题的免责声明,但是bash在组合变量替换或涉及`$IFS`等隐式变量时并不特别聪明,所以我决定不这样做。我后来也添加了一个循环的答案。
实际上,我通常会使用`printf -v var '%s,' "${data[@]}"; echo "${var%,}"`的方法——它不改变`IFS`,也不对数据的格式作出任何假设。
作为一个POSIX shell程序员,我经常错过像`sprintf`那样类似于格式化的`printf -v var`。不过,我的循环按照数据的原样输出,这样在负载较重的系统上使用较大的输入时,交互会更加响应(而且不需要删除尾随的分隔符)。我不确定这种浪费赋值的成本有多大,但我认为它是可以忽略的(特别是在这种情况下,因为我们受到命令参数长度的限制,即`getconf ARG_MAX`)。
花括号本身不能限制更改的作用域。这是命令组和子shell之间的主要区别;命令组仍然在当前shell中执行。
在`for`循环中,第一次迭代中`delim`的值是未定义的。也许你想将其初始化为空?
- 哦,如此周全啊。是的,初始化它会更安全。已更新。
- 我用一行代码使用了一个简单的管道`echo ${data[@]} | tr \ ,`。
- 假设你的`$data`数组中没有空格(参见我最后的方法),这种方法也有效,但是它会增加一个外部调用的开销。作为一次性操作,这无关紧要。嵌套在循环中,这将累积起来。我在回答及其评论中迄今为止提到的所有其他方法都使用了内置命令(shell内置的命令)。
- 对于zsh,你可以使用`${(j:,:)data}`完成同样的操作。关于单词分割有一些微妙之处:注意这是在`s:string:`标志或`SH_WORD_SPLIT`选项进行字段分割之前发生的。(来源:zsh.sourceforge.io/Doc/Release/…)
感谢上面这些帖子中提供的解决方案,你可以根据你的需求选择适合的方法来将一个数组转换为逗号分隔的字符串。
问题的出现原因是希望将一个数组转换为以逗号分隔的字符串,并且希望能够避免覆盖全局变量IFS
的值。解决方法是创建一个函数join
,该函数可以接受一个参数作为分隔符,并将数组的元素连接为一个字符串输出。
在这个解决方法中,使用join
函数可以方便地将数组转换为以逗号分隔的字符串。函数中的IFS
变量被设置为传入的参数值,然后使用shift
命令将参数列表向左移动一个位置,从而忽略掉第一个参数。最后,使用echo
命令将剩余的参数输出为一个字符串。
这种解决方法的优点是可以避免覆盖全局变量IFS
的值,并且使用了其他编程语言用户熟悉的语法。另外,还提到了一种更短的方法,即在子shell中设置IFS
的值并使用echo
命令输出数组的元素,但这种方法可能会创建一个新的进程。
此外,文章还提到了花括号不能本地化IFS
的值,需要使用子shell来实现。通过使用local
命令,函数的局部变量可以避免覆盖全局变量的值。
最终,通过使用join
函数和传入逗号作为参数,可以将数组data
转换为以逗号分隔的字符串。