Jak mogę połączyć elementy tablicy w Bash?


416

Jeśli mam taką tablicę w Bash:

FOO=( a b c )

Jak połączyć elementy przecinkami? Na przykład produkcja a,b,c.

Odpowiedzi:


571

Przepisywanie rozwiązania przez Pascala Pilza jako funkcja w 100% czystej Bash (bez zewnętrznych poleceń):

function join_by { local IFS="$1"; shift; echo "$*"; }

Na przykład,

join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c

Alternatywnie możemy użyć printf do obsługi ograniczników wieloznakowych, korzystając z pomysłu @gniourf_gniourf

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

Na przykład,

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

9
Użyj tego dla separatorów wieloznakowych: funkcja join {perl -e '$ s = shift @ARGV; print join ($ s, @ARGV); ' „$ @”; } join ',' abc # a, b, c
Daniel Patru

4
@dpatru w każdym razie, aby zrobić to czyste bash?
CMCDragonkai

4
@puchu To, co nie działa, to separatory wieloznakowe. Powiedzenie „przestrzeń nie działa” sprawia, że ​​brzmi to tak, jakby połączenie z przestrzenią nie działa. To robi.
Eric,

6
Sprzyja to odradzaniu podpowłoki, jeśli przechowuje się dane wyjściowe w zmiennej. Użyj konsoleboxstylu :) function join { local IFS=$1; __="${*:2}"; }lub function join { IFS=$1 eval '__="${*:2}"'; }. Następnie użyj __po. Tak, promuję użycie __jako zmiennej wynikowej;) (i wspólnej zmiennej iteracyjnej lub zmiennej tymczasowej). Jeśli pomysł
trafi

6
Nie umieszczaj rozszerzenia $dw specyfikatorze formatu printf. Myślisz, że jesteś bezpieczny, ponieważ „uciekłeś”, %ale istnieją inne zastrzeżenia: kiedy ogranicznik zawiera odwrotny ukośnik (np. \n) Lub kiedy ogranicznik zaczyna się od myślnika (i może innych, o których nie mogę teraz myśleć). Możesz je oczywiście naprawić (zastąpić odwrotnymi ukośnikami podwójnymi odwrotnymi ukośnikami i użyć printf -- "$d%s"), ale w pewnym momencie poczujesz, że walczysz z powłoką zamiast z nią pracować. Dlatego w poniższej odpowiedzi wstawiłem separator do warunków, które należy dołączyć.
gniourf_gniourf

206

Jeszcze inne rozwiązanie:

#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}

echo $bar

Edycja: tak samo, ale dla wieloznakowego separatora o zmiennej długości:

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

7
+1. Co printf -v bar ",%s" "${foo[@]}". To o jeden forkmniej (właściwie clone). Jest nawet rozwidlone odczytu pliku: printf -v bar ",%s" $(<infile).
TrueY

14
Zamiast modlić $separatornie zawiera %slub takich, można zrobić swój printfwytrzymałość: printf "%s%s" "$separator" "${foo[@]}".
musiphil

5
@musiphil Wrong. From bash man: „Format jest ponownie wykorzystywany, jeśli jest to konieczne, aby zużyć wszystkie argumenty. Użycie dwóch symboli zastępczych formatu, takich jak in, printf "%s%s"spowoduje użycie separatora TYLKO pierwszego zestawu danych wyjściowych, a następnie po prostu połączy pozostałe argumenty.
AnyDev

3
@AndrDevEK: Dzięki za złapanie błędu. Zamiast tego sugerowałbym coś takiego printf "%s" "${foo[@]/#/$separator}".
musiphil

2
@musiphil, Dzięki. Tak! Następnie printf staje się zbędny i można zmniejszyć tę linię do IFS=; regex="${foo[*]/#/$separator}". W tym momencie zasadniczo staje się to odpowiedzią gniourf_gniourf, która IMO jest czystsza od samego początku, to znaczy za pomocą funkcji ograniczającej zakres zmian IFS i zmienne temp.
AnyDev

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

3
Zewnętrzne podwójne cudzysłowy i podwójne cudzysłowy wokół jelita grubego nie są konieczne. Konieczne są tylko wewnętrzne podwójne cudzysłowy:bar=$( IFS=, ; echo "${foo[*]}" )
ceving 11.09

8
+1 za najbardziej kompaktowe rozwiązanie, które nie wymaga pętli, które nie wymaga zewnętrznych poleceń i które nie nakłada dodatkowych ograniczeń na zestaw znaków argumentów.
ceving

22
podoba mi się to rozwiązanie, ale działa tylko wtedy, gdy IFS jest jedną postacią
Jayen

8
Masz pojęcie, dlaczego to nie działa, jeśli używasz @zamiast *, jak w $(IFS=, ; echo "${foo[@]}")? Widzę, że *już zachowuje białe znaki w elementach, znowu nie jestem pewien, jak to zrobić , ponieważ @zwykle jest to wymagane ze względu na to.
haridsv

10
Odpowiedź na moje własne pytanie znalazłem powyżej. Odpowiedź jest taka, że ​​IFS jest rozpoznawany tylko dla *. Na stronie podręcznika użytkownika bash wyszukaj „Parametry specjalne” i poszukaj wyjaśnienia obok *:
haridsv 16.04.2014

66

Może np.

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

echo "$FOOJOIN"

3
Jeśli to zrobisz, myślisz, że IFS- jest zmienną. Musisz zrobić echo "-${IFS}-"(nawiasy klamrowe oddzielają myślniki od nazwy zmiennej).
Wstrzymano do odwołania.

1
Nadal mam ten sam wynik (po prostu wstawiam myślniki, aby zilustrować cel ... echo $IFSrobi to samo.
David Wolever,

41
To powiedziawszy, nadal wydaje się działać ... Tak więc, jak większość rzeczy z Bash, będę udawać, że rozumiem i kontynuuję swoje życie.
David Wolever,

2
„-” nie jest poprawnym znakiem dla nazwy zmiennej, więc powłoka działa poprawnie, gdy używasz $ IFS-, nie potrzebujesz $ {IFS} - (bash, ksh, sh i zsh w systemie Linux i solaris również się zgadzam).
Idelic,

2
@David różnica między twoim echem a Dennisem polega na tym, że użył podwójnego cytowania. Zawartość IFS jest używana „na wejściu” jako deklaracja znaków separatora słów - więc zawsze otrzymasz pusty wiersz bez cudzysłowów.
martin clayton

30

Co zaskakujące, moje rozwiązanie nie zostało jeszcze podane :) To dla mnie najprostszy sposób. Nie potrzebuje funkcji:

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

Uwaga: Zaobserwowano, że to rozwiązanie działa dobrze w trybie innym niż POSIX. W trybie POSIX elementy są nadal prawidłowo łączone, ale IFS=,stają się trwałe.


niestety działa tylko dla ograniczników
jednoznakowych

24

Oto w 100% czysta funkcja Bash, która wykonuje zadanie:

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}"
}

Popatrz:

$ 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


$

Zachowuje to nawet końcowe znaki nowego wiersza i nie wymaga podpowłoki, aby uzyskać wynik funkcji. Jeśli nie podoba ci się printf -v(dlaczego by ci się nie podobało?) I przekazujesz nazwę zmiennej, możesz oczywiście użyć zmiennej globalnej dla zwracanego ciągu:

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}"
}

1
Twoje ostatnie rozwiązanie jest bardzo dobre, ale można je uczynić czystszym, tworząc join_retzmienną lokalną, a następnie odbijając ją na końcu. Pozwala to na użycie join () w zwykły sposób skryptowania powłoki, np. $(join ":" one two three)I nie wymaga zmiennej globalnej.
James Sneeringer

1
@JamesSneeringer Celowo użyłem tego projektu, aby uniknąć podpowłoki. W skrypcie powłoki, w przeciwieństwie do wielu innych języków, używane w ten sposób zmienne globalne niekoniecznie są złe; szczególnie jeśli są tutaj, aby pomóc uniknąć podpowłoki. Ponadto $(...)przycina końcowe znaki nowej linii; więc jeśli ostatnie pole tablicy zawiera końcowe znaki nowej linii, zostaną one przycięte (zobacz wersję demonstracyjną, w której nie zostały one przycięte przez mój projekt).
gniourf_gniourf

Działa to z separatorami wieloznakowymi, co mnie uszczęśliwia ^ _ ^
spiffytech,

Aby odpowiedzieć na pytanie „dlaczego nie chciałbyś printf -v?”: W Bash zmienne lokalne nie są tak naprawdę lokalnie funkcjonalne, więc możesz robić takie rzeczy. (Wywołaj funkcję f1 z lokalną zmienną x, która z kolei wywołuje funkcję f2, która modyfikuje x - która jest zadeklarowana lokalnie w zakresie f1). Ale tak naprawdę nie powinny działać zmienne lokalne. Jeśli zmienne lokalne naprawdę są lokalne (lub zakłada się, że tak jest, na przykład w skrypcie, który musi działać zarówno na bash, jak i ksh), to powoduje problemy z tym całym „zwróć wartość, przechowując ją w zmiennej o tej nazwie”.
tetsujin

15

Nie różni się to zbytnio od istniejących rozwiązań, ale unika używania osobnej funkcji, nie modyfikuje się IFSw powłoce nadrzędnej i znajduje się w jednym wierszu:

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

powodując

a,b,c

Ograniczenie: separator nie może być dłuższy niż jedna postać.


13

Bez użycia zewnętrznych poleceń:

$ 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

Ostrzeżenie, zakłada, że ​​elementy nie mają białych znaków.


4
Jeśli nie chcesz używać zmiennej pośredniej, możesz to zrobić jeszcze krócej:echo ${FOO[@]} | tr ' ' ','
jesjimher

2
Nie rozumiem głosów negatywnych. Jest to bardzo kompaktowe i czytelne rozwiązanie niż inne zamieszczone tutaj, i wyraźnie ostrzega, że ​​nie działa, gdy są wolne miejsca.
jesjimher

12

Chciałbym powtórzyć tablicę jako ciąg znaków, a następnie przekształcić spacje w kanały liniowe, a następnie użyć pastedo połączenia wszystkiego w jednym wierszu w taki sposób:

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

Wyniki:

a,b,c

To wydaje mi się najszybsze i najczystsze!


$FOOjest tylko pierwszym elementem tablicy. Ponadto powoduje to uszkodzenie elementów tablicy zawierających spacje.
Benjamin W.

9

Ponowne użycie @ nie ma znaczenia rozwiązanie, ale z jedną instrukcją, unikając podstacji $ {: 1} i potrzeby zmiennej pośredniej.

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

printf ma „Ciąg formatu jest ponownie wykorzystywany tak często, jak to konieczne, aby spełnić argumenty”. na stronach podręcznika, aby udokumentować konkatenacje łańcuchów. Zatem sztuczką jest użycie długości LISTY do posiekania ostatniego speratora, ponieważ cięcie zachowa tylko długość LISTY w miarę liczenia pól.


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

8
Powinieneś udzielić odpowiedzi.
joce

Ten najlepszy. Dzięki!!
Peter Pan

4
Chciałbym móc głosować za odpowiedzią, ponieważ otwiera ona lukę w zabezpieczeniach i ponieważ niszczy spacje w elementach.
węgorz ghEEz

1
@ bxm rzeczywiście wydaje się, że zachowuje spacje i nie pozwala na ucieczkę z kontekstu argumentów echa. Doszedłem do wniosku, że dodawanie @Qmoże uchronić połączone wartości przed błędną interpretacją, gdy mają w sobie 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 '
łącznik

1
Unikaj rozwiązań wykorzystujących podpowłoki, chyba że jest to konieczne.
konsolebox

5

printf rozwiązanie, które akceptuje separatory dowolnej długości (na podstawie @ nie ma znaczenia odpowiedź)

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

Daje to wynik z przecinkiem wiodącym.
Mark Renouf

Ostatni słupek = $ {bar: $ {# sep}} usuwa separator. Właśnie skopiowałem i wkleiłem powłokę bash i to działa. Jakiej powłoki używasz?
Riccardo Galli,

2
Dowolny printf specyfikator formatu (np. %sNiezamierzenie w $sepspowoduje problemy).
Peter.O

sepmożna się zdezynfekować ${sep//\%/%%}. I jak rozwiązania lepsze niż ${bar#${sep}}lub ${bar%${sep}}(alternatywnie). Jest to przydatne, jeśli zostanie przekonwertowane na funkcję przechowującą wynik w zmiennej ogólnej, takiej jak __, a nie w echonim.
konsolebox

function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
konsolebox

4
$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d

To powinno być na górze.
Eric Walker,

6
To nie powinno być na górze: co jeśli HISTSIZE=0?
har-wradim

@ har-wradim, sztuczka nie polega paste -sd,na wykorzystaniu historii.
Veda,

@ Veda Nie, chodzi o użycie kombinacji i nie zadziałałoby, jeśli HISTSIZE=0- wypróbuj.
har-wradim

4

Krótsza wersja najwyższej odpowiedzi:

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

Stosowanie:

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

1
Dłuższa wersja, ale nie trzeba kopiować wycinka argumentów do zmiennej tablicowej:join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Rockallite

Jeszcze inna wersja: join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } Działa z użyciem: join_strings 'delim' "${array[@]}"lub bez cudzysłowu:join_strings 'delim' ${array[@]}
Cometsong

4

Połącz najlepsze ze wszystkich dotychczasowych światów z następującym pomysłem.

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

To małe arcydzieło

  • 100% czysty bash (rozszerzenie parametrów z IFS tymczasowo rozbrojonym, brak połączeń zewnętrznych, brak printf ...)
  • kompaktowy, kompletny i bezbłędny (współpracuje z ogranicznikami jedno- i wieloznakowymi, współpracuje z ogranicznikami zawierającymi białe znaki, podziały linii i inne znaki specjalne powłoki, współpracuje z pustym ogranicznikiem)
  • wydajny (bez podpowłoki, bez kopiowania tablicy)
  • proste i głupie, a do pewnego stopnia piękne i pouczające

Przykłady:

$ 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

1
Niezbyt przyjemnie: co najmniej 2 problemy: 1. join_ws ,(bez argumentów) niepoprawnie wyprowadza ,,. 2. join_ws , -ebłędnie wyprowadza nic (to dlatego, że niewłaściwie używasz echozamiast printf). W rzeczywistości nie wiem, dlaczego reklamowałeś użycie echozamiast printf: echojest notorycznie zepsuty i printfjest solidnym wbudowanym programem.
gniourf_gniourf

1

Obecnie używam:

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

Które działa, ale (w ogólnym przypadku) przerwie się okropnie, jeśli elementy tablicy będą miały spację.

(Dla zainteresowanych jest to skrypt otoki wokół pep8.py )


skąd bierzesz te wartości tablicowe? jeśli tak to kodujesz, dlaczego nie po prostu foo = "a, b, c".?
ghostdog74,

W tym przypadku faktycznie jestem ciężko kodowania wartości, ale chcę, aby umieścić je w tablicy, więc mogę wypowiedzieć się na temat każdego z osobna. Zaktualizowałem odpowiedź, aby pokazać ci, co mam na myśli.
David Wolever,

Zakładając, że faktycznie za pomocą bash, to może działać lepiej: ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Operator $()ma większą moc niż backtics (umożliwia zagnieżdżanie $()i ""). ${TO_IGNORE[@]}Pomóc powinno także zawijanie podwójnych cudzysłowów.
kevinarpe

1

Moja próba

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

1

Użyj perla dla separatorów wieloznakowych:

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

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

Lub w jednym wierszu:

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

działa dla mnie, chociaż joinnazwa koliduje z jakimś gównem na OS X... zadzwoniłbym conjoined, a może jackie_joyner_kersee?
Alex Gray,

1

Dziękuję @gniourf_gniourf za szczegółowe komentarze na temat mojej dotychczasowej kombinacji najlepszych światów. Przepraszamy za kod pocztowy, który nie został dokładnie zaprojektowany i przetestowany. Oto lepsza próba.

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

To piękno z założenia jest

  • (wciąż) 100% czysty bash (dzięki za wyraźne wskazanie, że printf jest również wbudowany. Nie wiedziałem o tym wcześniej ...)
  • współpracuje z ogranicznikami wieloznakowymi
  • bardziej zwarta i bardziej kompletna, tym razem dokładnie przemyślana i długoterminowo przetestowana pod obciążeniem z losowymi podciągami między innymi ze skryptów powłoki, obejmująca użycie specjalnych znaków powłoki lub znaków kontrolnych lub brak znaków w separatorze i / lub parametrach oraz przypadki krawędzi , a przypadki narożne i inne spory nie mają żadnych argumentów. To nie gwarantuje, że nie będzie już żadnego błędu, ale znalezienie go będzie trochę trudniejsze. BTW, nawet obecnie najczęściej głosowane odpowiedzi i pokrewne cierpią z powodu takich rzeczy jak ten -e błąd ...

Dodatkowe przykłady:

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

1

Jeśli elementy, które chcesz połączyć, nie są tablicą, ale ciągiem znaków oddzielonych spacją, możesz zrobić coś takiego:

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

na przykład, moim przypadkiem użycia jest to, że niektóre ciągi znaków są przekazywane w moim skrypcie powłoki i muszę go użyć, aby uruchomić zapytanie SQL:

./my_script "aa bb cc dd"

W moim skrypcie muszę wykonać polecenie „WYBIERZ * Z tabeli, GDZIE nazwa IN („ aa ”,„ bb ”,„ cc ”,„ dd ”). Wtedy powyższe polecenie będzie przydatne.


Możesz użyć printf -v bar ...zamiast uruchamiać pętlę printf w podpowłoce i przechwytywać dane wyjściowe.
codeforester

wszystkie powyższe fantazyjne rozwiązania nie działały, ale twoje prymitywne dla mnie (Y)
ishandutta2007,

1

Oto jeden, który obsługuje większość powłok kompatybilnych z 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[@]}"
}

To dobry kod Bash, ale POSIX w ogóle nie ma tablic (lub local).
Anders Kaseorg

@Anders: Tak, nauczyłem się tego na własnej skórze bardzo niedawno :( Na razie jednak to zostawię, ponieważ wydaje się, że większość powłok kompatybilnych z POSIX obsługuje tablice.
user541686

1

Działa również użycie pośredniej zmiennej do bezpośredniego odniesienia do tablicy. Można również użyć nazwanych referencji, ale stały się one dostępne dopiero w 4.3.

Zaletą korzystania z tej formy funkcji jest to, że separator może być opcjonalny (domyślnie jest to pierwszy znak domyślny IFS, który jest spacją; być może uczyni to pustym ciągiem, jeśli chcesz), i pozwala on uniknąć dwukrotnego zwiększania wartości (najpierw gdy przekazany jako parametry, a drugi jako"$@" wewnątrz funkcji).

To rozwiązanie nie wymaga również od użytkownika wywoływania funkcji w podstawieniu polecenia - które przywołuje podpowłokę, aby otrzymać połączoną wersję ciągu przypisanego do innej zmiennej.

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.

Możesz użyć wygodniejszej nazwy dla tej funkcji.

Działa to od 3.1 do 5.0-alfa. Jak zaobserwowano, pośrednia zmienność działa nie tylko ze zmiennymi, ale także z innymi parametrami.

Parametr to jednostka przechowująca wartości. Może to być nazwa, liczba lub jeden ze znaków specjalnych wymienionych poniżej w części Parametry specjalne. Zmienna jest parametrem oznaczonym nazwą.

Tablice i elementy tablic są również parametrami (encje przechowujące wartość), a odniesienia do tablic są również technicznie odniesieniami do parametrów. I podobnie jak parametr specjalny @,array[@] również stanowi ważne odniesienie.

Zmienione lub selektywne formy rozszerzenia (takie jak rozszerzenie substringu), które odbiegają od odniesienia do samego parametru, już nie działają.

Aktualizacja

W wydanej wersji Bash 5.0 zmienna pośrednia jest już nazywana rozszerzeniem pośrednim, a jej zachowanie jest już wyraźnie udokumentowane w podręczniku:

Jeśli pierwszym znakiem parametru jest wykrzyknik (!), A parametr nie jest nazwą, to wprowadza on poziom pośredni. Bash używa wartości utworzonej przez rozwinięcie reszty parametru jako nowego parametru; jest on następnie rozwijany i ta wartość jest wykorzystywana w pozostałej części rozwinięcia, a nie w rozszerzeniu oryginalnego parametru. Jest to znane jako ekspansja pośrednia.

Biorąc pod uwagę, że w dokumentacji ${parameter}, parameterjest określany jako „parametr powłoki, jak opisano (w) PARAMETRY lub odwołanie do tablicy ”. W dokumentacji tablic wspomniano, że „do dowolnego elementu tablicy można się odwoływać za pomocą ${name[subscript]}”. To sprawia, __r[@]że odwołanie do tablicy.

Dołącz według wersji argumentów

Zobacz mój komentarz w odpowiedzi Riccardo Galli .


2
Czy istnieje jakiś konkretny powód, aby używać go __jako nazwy zmiennej? Sprawia, że ​​kod jest naprawdę nieczytelny.
Pesa,

@PesaThe To tylko preferencja. Wolę używać nazw ogólnych dla zmiennej zwracanej. Inne nieogólne nazwy przypisują się określonym funkcjom i wymaga zapamiętywania. Wywołanie wielu funkcji zwracających wartości dla różnych zmiennych może sprawić, że kod będzie trudniejszy do naśladowania. Użycie nazwy ogólnej zmusiłoby skrypter do przeniesienia wartości ze zmiennej zwracanej do właściwej zmiennej, aby uniknąć konfliktu, i sprawia, że ​​kod staje się bardziej czytelny, ponieważ staje się wyraźny tam, gdzie idą zwracane wartości. Robię jednak kilka wyjątków od tej reguły.
konsolebox

0

To podejście zajmuje się spacjami w obrębie wartości, ale wymaga pętli:

#!/bin/bash

FOO=( a b c )
BAR=""

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

0

Jeśli budujesz tablicę w pętli, oto prosty sposób:

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

0

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

To najkrótszy sposób na zrobienie tego.

Przykład,

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

1
To nie działa poprawnie dla łańcucha ze spacjami: `t = (a" b c "d); echo $ {t [2]} (drukuje „b c”); echo $ {"$ {t [*]}" // /,} (drukuje a, b, c, d)
kounoupis

7
bash: ${"${arr[*]}"// /,}: bad substitution
Cameron Hudson,

0

Być może późno na imprezę, ale to działa dla mnie:

function joinArray() {
  local delimiter="${1}"
  local output="${2}"
  for param in ${@:3}; do
    output="${output}${delimiter}${param}"
  done

  echo "${output}"
}

-1

Być może brakuje mi czegoś oczywistego, ponieważ jestem nowicjuszem w całym bash / zsh, ale wydaje mi się, że nie musisz go wcale używać printf. Bez tego nie robi się naprawdę brzydko.

join() {
  separator=$1
  arr=$*
  arr=${arr:2} # throw away separator and following space
  arr=${arr// /$separator}
}

Przynajmniej do tej pory działało dla mnie bez problemu.

Na przykład, join \| *.shco powiedzmy, że jestem w moim ~katalogu, generuje dane wyjściowe utilities.sh|play.sh|foobar.sh. Wystarczająco dobrze dla mnie.

EDYCJA: Jest to w zasadzie odpowiedź Nila Geisweillera , ale uogólniona na funkcję.


1
Nie jestem downvoter, ale manipulowanie globalną funkcją wydaje się dość zwariowane.
tripleee

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

Daje to również dodatkowy przecinek na końcu. Nie jestem ekspertem od bash. Tylko mój 2c, ponieważ jest to bardziej elementarne i zrozumiałe


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

lub

$ a=(1 "a b" 3)
$ b=$(IFS=, ; echo "${a[*]}")
$ echo $b
1,a b,3
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.