I have an array and I am printing it like this:
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中实现这一点?
数据数组是动态的,它可能有任意数量的值。
有几种方法可以做到这一点:
1. 直接使用printf
进行连接(通过Charles Duffy的评论)
printf -v joined '%s,' "${data[@]}"
echo "${joined%,}"
printf
内置函数会隐式地合并数组。您可以像下面的3a一样交互式地打印,使用一行命令printf '%s,' "${data[@]}"
,但是最后会留下一个尾随逗号。(即使在POSIX shell中,这种方法也有效,尽管您必须使用$@
作为您的数组,因为POSIX无法处理其他类型的数组)。
2. 更改$IFS
字段分隔符(通过chepner's答案)。
join_arr() {
local IFS="$1"
shift
echo "$*"
}
join_arr , "${data[@]}"
这会在函数的范围内重新定义字段分隔符,因此当自动展开$data
数组时,它将使用期望的定界符而不是全局$IFS
的第一个值(如果它为空或未定义,则如此)。
虽然可以在没有函数的情况下完成此操作,但保留$IFS
有些麻烦: Charles Duffy指出,在临时重新分配后恢复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
),你实际上可以一步一步地观察这个构建过程,这在交互式环境中可能有所帮助。
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
替换$parameter
中的每个pattern
实例。在这种情况下,string
是${IFS:0:1}
,即从开头开始并在一个字符后结束的$IFS
子字符串。
Z Shell (zsh
)可以在一个嵌套的参数扩展中完成此操作:
echo "${${data[@]}//${IFS:0:1}/,}"
(尽管 Z Shell 也可以更优雅地使用其专用的join
标志,如 echo "${(j:,:)data}"
所示,正如@DavidBaynard在本答案下面的评论中所提到的。)
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
),然后将其打印在替换之前。%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
。 - Charles Duffyprintf -v var '%s,' "${data[@]}"; echo "${var%,}"
-- 这不会改变 IFS
,也不会对数据的格式做出任何假设。 - Charles Duffy请注意,在s:string:标志或SH_WORD_SPLIT选项进行字段拆分之前会发生这种情况。(来源:https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion) - David Baynard
IFS
,请使用一个函数:join () {
local IFS="$1"
shift
echo "$*"
}
join , "${data[@]}"
$IFS
被覆盖的问题,并使用其他语言用户非常熟悉的语法。 - Adam Katz(IFS=,; echo "${data[*]}")
,但代价是(几乎肯定)要为子Shell 分叉一个新进程。 - chepnerIFS
的值。你必须使用子shell来完成。函数的好处是可以使用 local
命令避免覆盖全局值。 - chepnerfoo=`echo $(echo ${data[@]}) | tr ' ' ','`
通过将空格(默认)转换为逗号,您可以控制分隔符!(或者任何其他您能想到的分隔符):)
foo=`echo ${data[@]} | tr ' ' ','`
- neowulf33如果你想用逗号分隔,请将逗号作为IFS
的第一个字符:
data=( first second third )
IFS=,
echo "${data[*]}"
...发出:
first,second,third
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
$IFS
保持定义状态;否则后面的脚本命令可能会出现问题。 - Adam KatzoIFS=$IFS; ...; IFS=$oIFS
也不是一个空操作 -- 它会将 unset IFS
改变为 IFS=''
,这两种状态具有不同的行为(前者的行为类似于 IFS=$' \t\n'
)。如果想要安全,那么使用 local
方法或在子shell中进行作用域限定是合适的 -- chepner 的回答已经涵盖了它们。我不愿意编写假装具有不存在的安全功能的代码,并且认为任何执行未引用扩展而没有显式设置 IFS 值的代码(或在运行 read
时未明确设置 IFS
)都是有缺陷的。 - Charles Duffy在子命令中更改IFS的单行代码(安全):
echo "$(IFS=,; echo "${data[*]}")"
$ data=(file1 file2 file3)
$ (IFS=,; echo "${data[*]}")
file1,file2,file3
$ printf "%q" "$IFS"
$' \t\n'
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
这种方法不会赢得任何奖项,但它适合我的更复杂需求,即将带有(foo bar baz)
的数组更改为用空格分隔并用转义双引号括起来的逗号分隔字符串\"foo\", \"bar\", \"baz\"
。这是IFS
不太适合的。
data=(foo bar baz)
data_str="${data[*]}"
separated_data_str=\"${data_str// /\", \"}\"
echo "${data[*]}"
不应该生成换行符,除非它们存在于值本身内。你能否[编辑]以包括declare -p data
的输出,这样我们就可以看到数组的实际形式是什么?(如果它不是一个数组,而实际上只是一个带有字面换行符的大字符串,那么这将解释你当前的输出)。 - Charles Duffyecho "${${data[@]}//${IFS:0:1}/,}"
- Adam Katz