Как я могу объединить элементы массива в Bash?

Если у меня есть такой массив в Bash:

FOO=( a b c )

Как присоединиться к элементам с запятыми? Например, создавая a,b,c.

Ответ 1

Решение перезаписи 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

Ответ 2

Еще одно решение:

#!/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

Ответ 3

$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d

Ответ 4

Может быть, например,

SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"

echo "$FOOJOIN"

Ответ 5

Удивительно, но мое решение еще не дано :) Это самый простой способ для меня. Это не нуждается в функции:

IFS=, eval 'joined="${foo[*]}"'

Примечание. Было замечено, что это решение хорошо работает в режиме без POSIX. В режиме POSIX элементы все еще правильно соединены, но IFS=, становится постоянным.

Ответ 6

Здесь используется 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


$

Это сохраняет даже завершающие символы новой строки и не нуждается в подоболочке, чтобы получить результат функции. Если вам не нравится 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}"
}

Ответ 7

Я бы выделил массив как строку, затем преобразую пробелы в строковые каналы, а затем с помощью paste соединим все в одной строке следующим образом:

tr " " "\n" <<< "$FOO" | paste -sd , -

Результаты:

a,b,c

Это кажется самым быстрым и чистым для меня!

Ответ 8

Это не слишком отличается от существующих решений, но позволяет избежать использования отдельной функции, не изменяет IFS в родительской оболочке и находится в одной строке:

arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"

в результате чего

a,b,c

Ограничение: разделитель не может быть длиннее одного символа.

Ответ 9

Не используя никаких внешних команд:

$ 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

Предупреждение, предполагается, что элементы не имеют пробелов.

Ответ 10

При повторном использовании @не имеет значения "решение", но с одним утверждением, избегая подстановки ${: 1} и нуждающейся в промежуточной переменной.

echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )

printf имеет 'Строка формата повторно используется так часто, насколько это необходимо для удовлетворения аргументов.' на его страницах руководства, так что конкатенации строк документированы. Тогда трюк состоит в том, чтобы использовать длину LIST для измельчения последней сператора, так как разрез будет удерживать только длину LIST по количеству полей.

Ответ 11

s=$(IFS=, eval 'echo "${FOO[*]}"')

Ответ 12

Решение 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

Ответ 14

Более короткая версия верхнего ответа:

joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }

Использование:

joinStrings "$myDelimiter" "${myArray[@]}"

Ответ 15

Объедините лучшее из всех миров до сих пор со следующей идеей.

# join with separator
join_ws()  { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }

Этот маленький шедевр

  • 100% чистый bash (расширение параметра с IFS временно отключено, никаких внешних вызовов, нет printf...)
  • компактный, полный и безупречный (работает с одно- и многосимвольными ограничителями, работает с ограничителями, содержащими пробелы, разрывы строк и другие специальные символы оболочки, работает с пустым разделителем)
  • эффективный (без подоболочки, без копии массива)
  • простой и глупый и, в определенной степени, красивый и поучительный, а также

Примеры:

$ 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

Ответ 16

Сейчас я использую:

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 ' ' ','`"

Что работает, но (в общем случае) будет ломаться ужасно, если элементы массива имеют в них пробел.

(Для тех, кого это интересует, это оболочка script вокруг pep8.py)

Ответ 17

Если элементы, к которым вы хотите присоединиться, не являются массивом, а просто строкой, разделенной пробелами, вы можете сделать что-то вроде этого:

foo="aa bb cc dd"
bar='for i in $foo; do printf ",'%s'" $i; done'
bar=${bar:1}
echo $bar
    'aa','bb','cc','dd'

Например, мой вариант использования заключается в том, что в моем сценарии оболочки передаются некоторые строки, и мне нужно использовать это для запуска SQL-запроса:

./my_script "aa bb cc dd"

В my_script мне нужно сделать "SELECT * FROM table WHERE name IN (" aa "," bb "," cc "," dd "). Тогда приведенная выше команда будет полезна.

Ответ 18

Моя попытка.

$ array=(one two "three four" five)
$ echo "${array[0]}$(printf " SEP %s" "${array[@]:1}")"
one SEP two SEP three four SEP five

Ответ 19

Использовать perl для разделителей с несколькими символами:

function join {
   perl -e '$s = shift @ARGV; print join($s, @ARGV);' "[email protected]"; 
}

join ', ' a b c # a, b, c

Или в одной строке:

perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3

Ответ 20

Спасибо @gniourf_gniourf за подробные комментарии о моей комбинации лучших миров на данный момент. Извините за публикацию кода, который не был тщательно разработан и протестирован. Здесь лучше попробовать.

# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }

Эта красота по замыслу

  • (все еще) 100% чистый bash (спасибо за явное указание, что printf также является встроенным. Я не знал об этом раньше...)
  • работает с многосимвольными разделителями
  • более компактный и более полный, и на этот раз тщательно продуманный и длительный стресс-тест со случайными подстроками из сценариев оболочки среди прочих, охватывающий использование специальных символов оболочки или управляющих символов или отсутствие символов как в разделителе и/или параметрах, так и в крайних случаях и угловые дела и другие придирки, как никакие аргументы вообще. Это не гарантирует, что ошибок больше нет, но найти их будет немного сложнее. Кстати, даже самые популярные в настоящее время ответы и связанные с ними проблемы страдают от таких вещей, как эта -e ошибка...

Дополнительные примеры:

$ 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 $ 
$

Ответ 21

Использование косвенной переменной для прямого обращения к массиву также работает. Именованные ссылки также могут быть использованы, но они стали доступны только в 4.3.

Преимущество использования этой формы функции заключается в том, что вы можете иметь необязательный разделитель (по умолчанию используется первый символ по умолчанию IFS, который является пробелом; возможно, вы можете сделать его пустой строкой, если хотите), и это позволяет избежать расширения значений дважды (сначала при передаче в качестве параметров, а затем как "[email protected]" внутри функции).

Это решение также не требует, чтобы пользователь вызывал функцию внутри подстановки команд, которая вызывает подоболочку, чтобы получить объединенную версию строки, назначенной другой переменной.

Что касается недостатков: вы должны быть осторожны при передаче правильного имени параметра, и передача __r даст вам __r[@]. Поведение переменной косвенности для расширения других форм параметров также явно не задокументировано.

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-альфа. Как уже отмечалось, переменная косвенность работает не только с переменными, но и с другими параметрами.

Параметр - это объект, который хранит значения. Это может быть имя, число или один из специальных символов, перечисленных ниже в разделе "Специальные" Параметры. Переменная - это параметр, обозначаемый именем.

Массивы и элементы массива также являются параметрами (объектами, которые хранят значения), и ссылки на массивы также технически являются ссылками на параметры. И так же, как специальный параметр @, array[@] также делает допустимую ссылку.

Измененные или выборочные формы расширения (например, расширение подстроки), которые отклоняются от самого параметра, больше не работают.

Ответ 22

Вот тот, который поддерживает большинство POSIX-совместимых оболочек:

join_by() {
    # Usage:  join_by "||" a b c d
    local arg arr=() sep="$1"
    shift
    for arg in "[email protected]"; do
        if [ 0 -lt "${#arr[@]}" ]; then
            arr+=("${sep}")
        fi
        arr+=("${arg}") || break
    done
    printf "%s" "${arr[@]}"
}

Ответ 23

Этот подход учитывает пробелы внутри значений, но требует цикла:

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}

Ответ 24

Если вы построите массив в цикле, вот простой способ:

arr=()
for x in $(some_cmd); do
   arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}

Ответ 25

x=${"${arr[*]}"///,}

Это самый короткий способ сделать это.

Пример,

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5

Ответ 26

Возможно, мне не хватает чего-то очевидного, так как я новичок в целом для 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. Достаточно хорошо для меня.

EDIT: это в основном ответ Нила Гейсвейлера, но обобщенный в функцию.

Ответ 27

liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

Это также поможет в дополнительной запятой в конце. Я не эксперт bash. Просто мой 2c, так как это более элементарно и понятно [/p >

Ответ 28

awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:""}}' "${arr[@]}"

или

$ a=(1 "a b" 3)
$ b=$(IFS=, ; echo "${a[*]}")
$ echo $b
1,a b,3