Если у меня есть такой массив в Bash:
FOO=( a b c )
Как мне соединить элементы запятыми? Например, производство a,b,c
.
Если у меня есть такой массив в Bash:
FOO=( a b c )
Как мне соединить элементы запятыми? Например, производство a,b,c
.
Ответы:
Решение по перезаписи Паскаля Пилза как функции в 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}"'; }
. Тогда используйте __
после. Да, я пропагандирую использование __
в качестве переменной результата;) (и общей итерационной переменной или временной переменной). Если концепция попала на популярный вики-сайт Bash, меня скопировали :)
$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
делает то же самое.
Удивительно, но мое решение еще не дано :) Это самый простой способ для меня. Это не нуждается в функции:
IFS=, eval 'joined="${foo[*]}"'
Примечание. Было замечено, что это решение хорошо работает в режиме без POSIX. В режиме POSIX элементы все еще правильно соединены, но 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
$
Это сохраняет даже завершающие символы новой строки и не нуждается в подоболочке для получения результата функции. Если вам не нравится 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 () обычным способом сценариев оболочки, например $(join ":" one two three)
, и не требует глобальной переменной.
$(...)
уравновешивает конечные строки; поэтому, если последнее поле массива содержит завершающие символы новой строки, они будут обрезаны (см. демонстрацию, где они не обрезаны с моим дизайном).
Это не слишком отличается от существующих решений, но позволяет избежать использования отдельной функции, не изменяется IFS
в родительской оболочке и находится в одной строке:
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
в результате чего
a,b,c
Ограничение: разделитель не может быть длиннее одного символа.
Не используя никаких внешних команд:
$ 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
это всего лишь первый элемент массива. Кроме того, это разбивается для элементов массива, содержащих пробелы.
С повторным использованием @ не имеет значения решение, но с одним утверждением, избегая подстановки $ {: 1} и необходимости промежуточной переменной.
echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
printf имеет «Строка формата используется повторно так часто, как это необходимо для удовлетворения аргументов». на его страницах руководства, так что конкатенации строк задокументированы. Тогда хитрость заключается в том, чтобы использовать длину LIST для нарезки последнего sperator, поскольку cut будет сохранять только длину LIST при подсчете полей.
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 ' ' ',')"
. Оператор $()
более мощный, чем backtics (допускает вложение $()
и ""
). Заключение в ${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
join
название конфликтует с какой-то хренью OS X
.. я бы назвал это conjoined
, или может быть jackie_joyner_kersee
?
Спасибо @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'
Например, мой вариант использования заключается в том, что в моем сценарии оболочки передаются некоторые строки, и мне нужно использовать это для запуска SQL-запроса:
./my_script "aa bb cc dd"
В my_script мне нужно сделать «SELECT * FROM table WHERE name IN (« aa »,« bb »,« cc »,« dd »). Тогда приведенная выше команда будет полезна.
printf -v bar ...
вместо того, чтобы запускать цикл printf в подоболочке и захватывать вывод.
Вот то, что поддерживает большинство POSIX-совместимых оболочек:
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.
Преимущество использования этой формы функции заключается в том, что вы можете использовать разделитель необязательно (по умолчанию используется первый символ по умолчанию 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-альфа. Как уже отмечалось, переменная косвенность работает не только с переменными, но и с другими параметрами.
Параметр - это объект, который хранит значения. Это может быть имя, число или один из специальных символов, перечисленных ниже в разделе «Специальные параметры». Переменная - это параметр, обозначаемый именем.
Массивы и элементы массива также являются параметрами (объектами, которые хранят значения), и ссылки на массивы также технически являются ссылками на параметры. И так же, как специальный параметр @
, array[@]
также делает действительную ссылку.
Измененные или выборочные формы расширения (например, расширение подстроки), которые отклоняются от самого параметра, больше не работают.
В выпускной версии Bash 5.0 косвенное изменение переменных уже называется косвенным расширением, и его поведение уже явно задокументировано в руководстве:
Если первый символ параметра является восклицательным знаком (!), А параметр не является nameref, он вводит уровень косвенности. Bash использует значение, сформированное путем расширения остальной части параметра, в качестве нового параметра; затем это расширяется, и это значение используется в остальной части расширения, а не в расширении исходного параметра. Это известно как косвенное расширение.
Принимая во внимание, что в документации ${parameter}
, parameter
упоминается как «параметр оболочки, как описано (в) PARAMETERS или ссылка на массив ». А в документации массивов упоминается, что «на любой элемент массива можно ссылаться с помощью ${name[subscript]}
». Это делает __r[@]
ссылку на массив.
Смотрите мой комментарий в ответе Риккардо Галли .
__
в качестве имени переменной? Делает код действительно нечитаемым.
Если вы строите массив в цикле, вот простой способ:
arr=()
for x in $(some_cmd); do
arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
x=${"${arr[*]}"// /,}
Это самый короткий способ сделать это.
Пример,
arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x # output: 1,2,3,4,5
bash: ${"${arr[*]}"// /,}: bad substitution
Возможно, я упускаю что-то очевидное, так как я новичок во всем, что касается 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
. Достаточно хорошо для меня.
РЕДАКТИРОВАТЬ: Это в основном ответ Нила Гейсвайлера , но обобщен в функцию.
liststr=""
for item in list
do
liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}
Это заботится о дополнительной запятой в конце также. Я не эксперт по Bash. Просто мой 2с, так как это более элементарно и понятно