如果我在Bash中有这样一个数组:
FOO=( a b c )
如何用逗号连接元素?例如,生成a,b,c
。
一个支持多个字符分隔符的100%纯Bash函数是:
function join_by {
local d=${1-} f=${2-}
if shift 2; then
printf %s "$f" "${@/#/$d}"
fi
}
例如,join_by , a b c #a,b,c
join_by ' , ' a b c #a , b , c
join_by ')|(' a b c #a)|(b)|(c
join_by ' %s ' a b c #a %s b %s c
join_by $'\n' a b c #a<newline>b<newline>c
join_by - a b c #a-b-c
join_by '\' a b c #a\b\c
join_by '-n' '-e' '-E' '-n' #-e-n-E-n-n
join_by , #
join_by , a #a
上面的代码基于@gniourf_gniourf、@AdamKatz、@MattCowell和@x-yuri的思路。它使用选项 errexit
(set -e
)和nounset
(set -u
)。
或者,一个更简单的函数只支持单个字符分隔符:
function join_by { local IFS="$1"; shift; echo "$*"; }
例如,join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
这个解决方案基于Pascal Pilz的原始建议。
在此之前提出的解决方案的详细说明可以在"如何在bash脚本中连接()数组元素",由meleu撰写的文章,在dev.to上找到。.
还有另一个解决方案:
#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}
echo $bar
编辑:同样的问题,但是针对多字符变量长度分隔符:
#!/bin/bash
separator=")|(" # e.g. constructing regex, pray it does not contain %s
foo=('foo bar' 'foo baz' 'bar baz')
regex="$( printf "${separator}%s" "${foo[@]}" )"
regex="${regex:${#separator}}" # remove leading separator
echo "${regex}"
# Prints: foo bar)|(foo baz)|(bar baz
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
@
而非*
,比如$(IFS=, ; echo "${foo[@]}")
,就会导致这个命令不起作用呢?我知道*
已经保留了元素中的空格,但不确定为什么,因为通常需要使用@
来实现这一点。 - haridsv*
。在bash手册页面中,搜索“特殊参数”,并查找与*
相邻的解释。 - haridsvSAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
命令(花括号将短横线与变量名称分开)。 - Dennis Williamson不使用任何外部命令:
$ FOO=( a b c ) # initialize the array
$ BAR=${FOO[@]} # create a space delimited string from array
$ BAZ=${BAR// /,} # use parameter expansion to substitute spaces with comma
$ echo $BAZ
a,b,c
警告:此假设假定元素不包含空格。
echo ${FOO[@]} | tr ' ' ','
。 - jesjimher/, }
。 - Neil Gaetano Lindberg这种简单的单字符分隔符解决方案需要非 POSIX 模式。在POSIX模式中,元素仍然正确地连接在一起,但IFS=,
赋值成为永久性的。
IFS=, eval 'joined="${foo[*]}"'
使用 #!bash
头部执行的脚本默认在非 POSIX 模式下执行,但为确保脚本在非 POSIX 模式下运行,建议在脚本开头添加 set +o posix
或 shopt -uo posix
。
对于多字符定界符,建议使用转义和索引技术的 printf
解决方案。
function join {
local __sep=${2-} __temp
printf -v __temp "${__sep//%/%%}%s" "${@:3}"
printf -v "$1" %s "${__temp:${#__sep}}"
}
join joined ', ' "${foo[@]}"
或者
function join {
printf -v __ "${1//%/%%}%s" "${@:2}"
__=${__:${#1}}
}
join ', ' "${foo[@]}"
joined=$__
此内容基于Riccardo Galli的答案,并应用了我的建议。
这与现有解决方案并没有太大的不同,但它避免了使用单独的函数,不会修改父shell中的IFS
,而且所有内容都在一行中:
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
导致a,b,c
限制:分隔符不能超过一个字符。
这可以简化为只使用
(IFS=,; printf '%s' "${arr[*]}")
此时它基本上与Pascal的答案相同,但是使用printf
而不是echo
,并将结果打印到stdout而不是分配给变量。
printf '%s\n' "$((IFS="⁋"; printf '%s' "${arr[*]}") | sed "s,⁋,LONG DELIMITER,g"))"
。⁋
被用作替换的占位符,并且可以是任何单个字符,不能出现在数组值中(因此使用不常见的 Unicode 符号)。 - Gussecho
,而不必在那里调用printf。 - Treviñoprintf
,但我不会将内部的 printf
切换到 echo
,以避免使用 echo
带来的歧义。但我可能可以简化为 (IFS=,; printf -- '%s\n' "${arr[*]}")
。 - Benjamin W.IFS=, printf '%s' "${arr[*]}"
即可。 - mattalxndr这是一个完全使用 Bash 编写的函数,可以完成此任务:
join() {
# $1 is return variable name
# $2 is sep
# $3... are the elements to join
local retname=$1 sep=$2 ret=$3
shift 3 || shift $(($#))
printf -v "$retname" "%s" "$ret${@/#/$sep}"
}
看:
$ a=( one two "three three" four five )
$ join joineda " and " "${a[@]}"
$ echo "$joineda"
one and two and three three and four and five
$ join joinedb randomsep "only one element"
$ echo "$joinedb"
only one element
$ join joinedc randomsep
$ echo "$joinedc"
$ a=( $' stuff with\nnewlines\n' $'and trailing newlines\n\n' )
$ join joineda $'a sep with\nnewlines\n' "${a[@]}"
$ echo "$joineda"
stuff with
newlines
a sep with
newlines
and trailing newlines
$
这样可以保留尾随的换行符,而且不需要使用子shell来获得函数的结果。如果你不喜欢 printf -v
(为什么不喜欢呢?)和传递变量名,当然也可以使用全局变量来存储返回的字符串:
join() {
# $1 is sep
# $2... are the elements to join
# return is in global variable join_ret
local sep=$1 IFS=
join_ret=$2
shift 2 || shift $(($#))
join_ret+="${*/#/$sep}"
}
join_ret
变成局部变量并在最后输出它来使代码更加简洁。这样就可以像通常的 shell 脚本一样使用 join(),例如 $(join ":" one two three)
,而不需要全局变量。 - James Sneeringer$(...)
会修剪尾随的换行符;因此,如果数组的最后一个字段包含尾随的换行符,则会被修剪(请参见演示,在我的设计中未修剪)。 - gniourf_gniourf/usr/bin/printf
。 - Mark Pettitprintf
是 Bash 内置命令。 - gniourf_gniourf我会将数组转化为字符串,然后将空格转换为换行符,并使用paste
将所有内容连接成一行,如下所示:
tr " " "\n" <<< "$FOO" | paste -sd , -
结果:
a,b,c
在我看来,这似乎是最快和最干净的方法!
$FOO
只是数组的第一个元素。而且,对于包含空格的数组元素,这种方法会出错。 - Benjamin W.printf '%s\0' "${FOO[@]}" | paste -zsd ","
。通过这种方式,它支持包含空格和换行符的数组元素。 - mgutt使用 @doesn't matters 的解决方案进行重用,但通过避免 ${:1} 替换和中间变量的需要来实现一条语句。
echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
printf在其手册页面中有“格式字符串将根据需要重复使用以满足参数”的说明,因此字符串的连接已经被记录。然后的技巧是使用LIST长度来截取最后一个分隔符,因为cut只会保留LIST的长度作为字段计数。
function join { perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; } join ', ' a b c # a, b, c
- Daniel Patrufunction join { local IFS=$1; __="${*:2}"; }
或function join { IFS=$1 eval '__="${*:2}"'; }
。然后在之后使用__
。是的,我正在推广使用__
作为结果变量;) (以及常见的迭代变量或临时变量)。如果这个概念传播到一个流行的Bash wiki网站,他们就是抄袭我 :) - konsoleboxprintf
的格式说明符中放置扩展$d
。你可能认为你是安全的,因为你“转义”了%
,但是还有其他注意事项:例如分隔符包含反斜杠(例如\n
)或者分隔符以连字符开头(也许还有其他我现在想不到的情况)。当然,你可以修复它们(将反斜杠替换为双反斜杠并使用printf -- "$d%s"
),但是在某些时候你会感觉你正在与shell作斗争而不是与其合作。这就是为什么在我的下面的答案中,我将分隔符预置到要连接的术语之前的原因。 - gniourf_gniourf