Якщо у мене є такий масив у 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 man" знайдіть "Спеціальні параметри" і шукайте пояснення поруч із *
:
Можливо, наприклад,
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 має "Рядок формату повторно використовується стільки разів, скільки потрібно для задоволення аргументів." на його довідкових сторінках, так що зв'язування рядків задокументовано. Тоді хитрість полягає у використанні довжини СПИСОК для подрібнення останнього спіратора, оскільки вирізання збереже лише довжину СПИСОК, коли кількість лічиться.
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
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
згадується як «раковині параметра , як описано (в) Параметри або посилання на масив ». А в документації масивів зазначається, що "Будь-який елемент масиву може бути посилається за допомогою ${name[subscript]}
". Це робить__r[@]
посилання на масив.
Дивіться мій коментар у відповіді Ріккардо Галлі .
__
як ім’я змінної? Робить код дійсно нечитабельним.
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
. Досить добре для мене.
EDIT: Це в основному відповідь Ніла Гейсвейлера , але узагальнена у функції.
liststr=""
for item in list
do
liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}
Це також переймається додатковою комою в кінці. Я не фахівець з басу. Просто мій 2с, оскільки це більш елементарно і зрозуміло