Answers:
Pascal Pilz将解决方案重写为100%纯Bash中的函数(无外部命令):
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
另外,我们可以使用printf来支持多字符定界符,使用@gniourf_gniourf的想法
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
例如,
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
konsolebox
样式:) function join { local IFS=$1; __="${*:2}"; }
或function join { IFS=$1 eval '__="${*:2}"'; }
。然后使用__
。是的,我是一个提倡使用__
as结果变量;)(以及常见的迭代变量或临时变量)的人。如果这个概念进入了流行的Bash Wiki网站,他们就会抄袭我:)
$d
放在的格式说明符中printf
。您认为自己是安全的,因为您已经“转义了”,%
但是还有其他警告:当分隔符包含反斜杠(例如\n
)时,或者当分隔符以连字符开头时(可能还有我现在想不到的其他字符)。您当然可以解决这些问题(将反斜杠替换为双反斜杠并使用printf -- "$d%s"
),但是在某些时候,您会感觉到您正在与外壳进行斗争而不是使用它。这就是为什么在下面的回答中,我将分隔符放在要连接的术语之前。
另一个解决方案:
#!/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
printf -v bar ",%s" "${foo[@]}"
?它fork
实际上少了一个clone
。甚至还需要读取文件:printf -v bar ",%s" $(<infile)
。
$separator
不含有%s
或这样的,你可以让你printf
健壮:printf "%s%s" "$separator" "${foo[@]}"
。
printf "%s%s"
在一审中唯一一组输出,将使用分离器,然后简单地拼接参数的其余部分。
printf "%s" "${foo[@]/#/$separator}"
。
IFS=; regex="${foo[*]/#/$separator}"
。在这一点上,这基本上成为gniourf_gniourf的答案,而IMO从一开始就是比较干净的,也就是说,使用函数来限制IFS更改和临时变量的范围。
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
,而不是*
,如$(IFS=, ; echo "${foo[@]}")
?我可以看到,*
已经在元素中保留了空格,再次不确定如何保留,因为@
为此通常需要这样做。
*
。在bash手册页中,搜索“特殊参数”,然后在以下位置查找说明*
:
也许,例如
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(花括号将破折号与变量名分开)。
echo $IFS
做同样的事。
这是完成此任务的100%纯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
局部变量,然后在末尾回显该变量,使其变得更整洁。这允许join()以常规的shell脚本方式使用,例如$(join ":" one two three)
,并且不需要全局变量。
$(...)
修剪尾随换行符;因此,如果数组的最后一个字段包含尾随换行符,则将对它们进行修剪(请参见演示,其中我的设计未对其进行修剪)。
不使用外部命令:
$ 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 ' ' ','
我会将数组作为字符串回显,然后将空格转换为换行符,然后使用paste
将所有内容连接到一行中,如下所示:
tr " " "\n" <<< "$FOO" | paste -sd , -
结果:
a,b,c
这对我来说似乎是最快,最干净的!
$FOO
不过,它只是数组的第一个元素。同样,这对于包含空格的数组元素也无效。
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"
'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
可以接受任何长度分隔符的printf解决方案(基于@无关紧要的答案)
#/!bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
sep=',' # can be of any length
bar=$(printf "${sep}%s" "${foo[@]}")
bar=${bar:${#sep}}
echo $bar
printf
格式说明。(如%s
无意中$sep
会产生问题。
sep
可以用消毒${sep//\%/%%}
。我喜欢您的解决方案比${bar#${sep}}
或${bar%${sep}}
(替代)好。如果将其转换为将结果存储到通用变量(例如__
)而不是的函数,则很好echo
。
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
$ set a 'b c' d
$ history -p "$@" | paste -sd,
a,b c,d
HISTSIZE=0
呢?
paste -sd,
与历史无关。
HISTSIZE=0
尝试一下,它将不起作用。
最佳答案的简短版本:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
用法:
joinStrings "$myDelimiter" "${myArray[@]}"
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; }
适用于以下用途:join_strings 'delim' "${array[@]}"
或未引用:join_strings 'delim' ${array[@]}
到目前为止,将世界上最好的与以下思想相结合。
# join with separator
join_ws() { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }
这个小杰作是
例子:
$ join_ws , a b c
a,b,c
$ join_ws '' a b c
abc
$ join_ws $'\n' a b c
a
b
c
$ join_ws ' \/ ' A B C
A \/ B \/ C
join_ws ,
(不带参数)错误地输出,,
。2. join_ws , -e
错误地输出任何内容(那是因为您错误地使用echo
而不是printf
)。我实际上不知道您为什么宣传echo
代替的使用printf
:echo
臭名昭著的坏处,并且printf
是一个强大的内置函数。
现在我正在使用:
TO_IGNORE=(
E201 # Whitespace after '('
E301 # Expected N blank lines, found M
E303 # Too many blank lines (pep8 gets confused by comments)
)
ARGS="--ignore `echo ${TO_IGNORE[@]} | tr ' ' ','`"
哪个可行,但是(在一般情况下)如果数组元素中有空格,则会严重中断。
(对于那些感兴趣的人,这是一个围绕pep8.py的包装脚本)
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"
。运算符$()
比背景符功能强大(允许嵌套$()
和""
)。${TO_IGNORE[@]}
用双引号包装也应有所帮助。
将perl用于多字符分隔符:
function join {
perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@";
}
join ', ' a b c # a, b, c
或一行:
perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
感谢@gniourf_gniourf对到目前为止我最好的世界组合的详细评论。抱歉,发布代码未经全面设计和测试。这是一个更好的尝试。
# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
从概念上讲,这种美是
其他示例:
$ join_ws '' a b c
abc
$ join_ws ':' {1,7}{A..C}
1A:1B:1C:7A:7B:7C
$ join_ws -e -e
-e
$ join_ws $'\033[F' $'\n\n\n' 1. 2. 3. $'\n\n\n\n'
3.
2.
1.
$ join_ws $
$
如果您要连接的元素不是数组,而只是一个用空格分隔的字符串,则可以执行以下操作:
foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
'aa','bb','cc','dd'
例如,我的用例是在我的shell脚本中传递了一些字符串,我需要使用它在SQL查询上运行:
./my_script "aa bb cc dd"
在my_script中,我需要执行“ SELECT * FROM table WHERE name IN('aa','bb','cc','dd')。然后上述命令将非常有用。
printf -v bar ...
而不是必须在子shell中运行printf循环并捕获输出。
这是大多数POSIX兼容shell支持的功能:
join_by() {
# Usage: join_by "||" a b c d
local arg arr=() sep="$1"
shift
for arg in "$@"; do
if [ 0 -lt "${#arr[@]}" ]; then
arr+=("${sep}")
fi
arr+=("${arg}") || break
done
printf "%s" "${arr[@]}"
}
local
)。
使用变量间接指向直接引用数组也可以。也可以使用命名引用,但是它们仅在4.3中可用。
使用这种形式的函数的优点是您可以使分隔符为可选(默认为default的第一个字符IFS
,它是一个空格;如果愿意,可以将其设置为空字符串),并且避免将值扩展两次(第一个作为参数传递,第二个作为"$@"
函数传递)。
此解决方案也不需要用户在命令替换内调用该函数-召唤一个子外壳,以获得分配给另一个变量的字符串的合并版本。
function join_by_ref {
__=
local __r=$1[@] __s=${2-' '}
printf -v __ "${__s//\%/%%}%s" "${!__r}"
__=${__:${#__s}}
}
array=(1 2 3 4)
join_by_ref array
echo "$__" # Prints '1 2 3 4'.
join_by_ref array '%s'
echo "$__" # Prints '1%s2%s3%s4'.
join_by_ref 'invalid*' '%s' # Bash 4.4 shows "invalid*[@]: bad substitution".
echo "$__" # Prints nothing but newline.
随意为该功能使用更舒适的名称。
这适用于3.1到5.0-alpha。正如所观察到的,变量间接寻址不仅适用于变量,还适用于其他参数。
参数是存储值的实体。它可以是名称,数字或特殊参数下面列出的特殊字符之一。变量是用名称表示的参数。
数组和数组元素也是参数(存储值的实体),并且对数组的引用在技术上也就是对参数的引用。就像特殊参数一样@
,array[@]
也提供了有效的参考。
与参数本身不同的引用的扩展或选择性扩展形式(如子字符串扩展)不再起作用。
在Bash 5.0的发行版本中,变量间接已被称为间接扩展,其行为已在手册中明确记录:
如果parameter的第一个字符是感叹号(!),并且parameter不是nameref,则它引入一个间接级别。Bash使用通过扩展其余参数形成的值作为新参数;然后对其进行扩展,并在其余扩展中使用该值,而不是原始参数的扩展。这称为间接扩展。
请注意,在的文档中${parameter}
,parameter
将其称为“如在参数中描述的shell参数或数组引用 ”。并且在数组文档中,提到“可以使用来引用数组的任何元素${name[subscript]}
”。 这将__r[@]
成为数组引用。
看到我的评论在里卡尔多·加利的回答。
__
用作变量名?使代码真正不可读。
如果您以循环方式构建数组,请使用以下简单方法:
arr=()
for x in $(some_cmd); do
arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
也许我缺少明显的东西,因为我是整个bash / zsh的新手,但是在我看来,您根本不需要使用它printf
。没有它也不会变得非常丑陋。
join() {
separator=$1
arr=$*
arr=${arr:2} # throw away separator and following space
arr=${arr// /$separator}
}
至少到目前为止,它对我没有任何问题。
例如,join \| *.sh
假设我在我的~
目录中,则输出utilities.sh|play.sh|foobar.sh
。对我来说足够好了。
编辑:这基本上是Nil Geisweiller的答案,但被概括为一个函数。