Bash'te bir dizinin öğelerine nasıl katılabilirim?


416

Bash böyle bir dizi varsa:

FOO=( a b c )

Öğelere virgülle nasıl katılırım? Örneğin, üretmek a,b,c.

Yanıtlar:


571

Pascal Pilz tarafından% 100 saf Bash işlevi olarak yeniden yazma çözümü (harici komutlar yok):

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

Örneğin,

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

Alternatif olarak, @gniourf_gniourf fikrini kullanarak çok karakterli sınırlayıcıları desteklemek için printf'yi kullanabiliriz

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

Örneğin,

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
Çok karakterli ayırıcılar için bunu kullanın: function join {perl -e '$ s = shift @ARGV; print join ($ s, @ARGV); ' "$ @"; } katılmak ',' abc # a, b, c
Daniel Patru

4
@dpatru yine de saf bash yapmak için?
CMCDragonkai

4
@puchu Çalışmayan çok karakterli ayırıcılar. "Mekan çalışmıyor" demek, bir mekanla birleşmek işe yaramaz gibi geliyor. Öyle.
Eric

6
Bu, çıktıyı değişkene depolarsa yumurtlama alt kabuklarını destekler. konsoleboxStili kullanın :) function join { local IFS=$1; __="${*:2}"; }veya function join { IFS=$1 eval '__="${*:2}"'; }. Sonra kullanın __. Evet, __sonuç değişkeni olarak kullanımı teşvik eden biriyim ;) (ve ortak bir yineleme değişkeni veya geçici değişken). Konsept popüler bir Bash wiki sitesine
ulaşırsa

6
Genişletmeyi $dbiçim belirleyicisine koymayın printf. "Kaçtığınızdan" güvenli olduğunu düşünüyorsunuz, %ancak başka uyarılar da var: sınırlayıcı ters eğik çizgi içerdiğinde (örneğin, \n) veya sınırlayıcı bir kısa çizgi ile başladığında (ve belki de şimdi düşünemediğim diğer). Elbette bunları düzeltebilirsiniz (ters eğik çizgileri çift ters eğik çizgilerle değiştirin ve kullanın printf -- "$d%s"), ancak bir noktada onunla çalışmak yerine kabuğa karşı savaştığınızı hissedeceksiniz. Bu nedenle, aşağıdaki cevabımda, sınırlayıcıyı birleştirilecek terimlere tercih ettim.
gniourf_gniourf

206

Yine başka bir çözüm:

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

echo $bar

Düzenleme: aynı ama çok karakterli değişken uzunluk ayırıcı için:

#!/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. Ne olmuş printf -v bar ",%s" "${foo[@]}". Bir forkdaha az (aslında clone). Hatta bir dosyayı okumak için çatallı printf -v bar ",%s" $(<infile).
TrueY

14
Bunun yerine dua $separatoriçermiyor %sya da varsa siz yapabilirsiniz printfsağlam: printf "%s%s" "$separator" "${foo[@]}".
musiphil

5
@musiphil Yanlış. Bash man'dan: "Biçim, tüm bağımsız değişkenleri tüketmek için gerektiği şekilde yeniden kullanılır. İki biçim yer tutucusunun printf "%s%s"kullanılması, ilk örnekte SADECE çıkış kümesinde ayırıcı kullanır ve yalnızca diğer argümanları birleştirir.
AnyDev

3
@AndrDevEK: Hatayı yakaladığınız için teşekkürler. Bunun yerine böyle bir şey öneririm printf "%s" "${foo[@]/#/$separator}".
musiphil

2
@musiphil, teşekkürler. Evet! Daha sonra printf gereksiz hale gelir ve bu çizgi azaltılabilir IFS=; regex="${foo[*]/#/$separator}". Bu noktada bu aslında gniourf_gniourf'un IMO'nun başlangıçtan daha temiz olduğu yanıtı, yani IFS değişikliklerinin ve geçici değişkenlerin kapsamını sınırlamak için işlevini kullanarak cevabı olur.
AnyDev

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

3
Dış çift tırnaklar ve kolon etrafındaki çift tırnaklar gerekli değildir. Sadece iç çift tırnak işareti gereklidir:bar=$( IFS=, ; echo "${foo[*]}" )
ceving

8
Döngülere ihtiyaç duymayan, harici komutlara ihtiyaç duymayan ve bağımsız değişkenlerin karakter kümesine ek kısıtlamalar getirmeyen en kompakt çözüm için +1.
ceving

22
Ben çözümü seviyorum, ama sadece IFS bir karakter ise çalışır
Jayen 29:13

8
Kullanılıyorsa herhangi bir fikir bu neden çalışmıyor @yerine *olduğu gibi $(IFS=, ; echo "${foo[@]}")? Görüyorum ki *zaten beyazlar için boşlukları koruyor, yine nasıl emin değilim, çünkü @bu uğruna genellikle gerekli.
haridsv

10
Kendi sorumun cevabını yukarıda buldum. Cevap IFS'nin sadece tanınmasıdır *. Bash man sayfasında, "Special Parameters" ı (Özel Parametreler) arayın ve yanındaki açıklamaya bakın.* :
haridsv

66

Belki, örneğin,

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

echo "$FOOJOIN"

3
Bunu yaparsanız, IFS-'nin değişken olduğunu düşünür. Yapmanız gerekir echo "-${IFS}-"(kıvırcık parantezler çizgileri değişken adından ayırır).
sonraki duyuruya kadar duraklatıldı.

1
Hala aynı sonucu aldım (sadece noktayı göstermek için tire işaretleri koydum… echo $IFSaynı şeyi yapıyor.
David Wolever

41
Bununla birlikte, bu hala işe yarıyor gibi görünüyor… Yani, Bash ile ilgili çoğu şey gibi, sanki anlıyorum ve hayatımla devam ediyormuş gibi davranacağım.
David Wolever

2
Bir "-" değişken adı için geçerli bir karakter değildir, bu nedenle $ IFS- kullandığınızda kabuk doğru olanı yapar, $ {IFS} - (linux ve solaris'te bash, ksh, sh ve zsh) gerekmez ayrıca katılıyorum).
Idelic

2
Eko ile Dennis'in arasındaki farkı yok et, çift tırnak kullanmasıdır. IFS içeriği 'girişte' sözcük ayırıcı karakterlerin bildirimi olarak kullanılır - böylece her zaman tırnak işaretleri olmadan boş bir satır alırsınız.
martin clayton

30

Şaşırtıcı bir şekilde benim çözüm henüz verilmemiş :) Bu benim için en basit yol. Bir fonksiyona ihtiyaç duymaz:

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

Not: Bu çözümün POSIX olmayan modda iyi çalıştığı gözlemlendi. In POSIX modunda , elemanlar hala düzgün katıldı ancak IFS=,kalıcı hale gelir.


ne yazık ki sadece tek karakterli sınırlayıcılar için çalışıyor
maoizm

24

İşte işi yapan% 100 saf Bash işlevi:

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

Bak:

$ 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


$

Bu, sondaki yeni satırları bile korur ve işlevin sonucunu elde etmek için alt kabuğa ihtiyaç duymaz. Eğer böyle yapmazsanız printf -vve bir değişken adı geçen (? Neden böyle olmaz), tabii ki döndürülen dize için küresel değişkeni kullanabilirsiniz:

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
Son çözümünüz çok iyi, ancak join_retyerel bir değişken oluşturarak ve daha sonra onu tekrarlayarak temizlenebilir . Bu, join () yönteminin olağan kabuk komut dosyası biçiminde kullanılmasına izin verir, örneğin $(join ":" one two three)ve genel bir değişken gerektirmez.
James Sneeringer

1
@JamesSneeringer Bu tasarımı bilerek alt kabuklardan kaçınmak için kullandım. Kabuk kodlamada, diğer birçok dilden farklı olarak, bu şekilde kullanılan global değişkenler mutlaka kötü bir şey değildir; özellikle de alt kabuklardan kaçınmak için buradalarsa. Dahası, $(...)sondaki satırsonlarını düzeltir; dizinin son alanı sondaki satırsonu içeriyorsa, bunlar kesilir (tasarımımla kesilmedikleri demoya bakın).
gniourf_gniourf

Bu, beni mutlu eden çok karakterli ayırıcılar ile çalışır ^ _ ^
spiffytech

"Printf -v'yi neden istemezsiniz?" Konusunu ele almak için: Bash'te yerel değişkenler gerçekte işlev-yerel değildir, bu yüzden böyle şeyler yapabilirsiniz. (F1 yerel değişkeni ile f1 işlevini çağırır, bu da x'i değiştiren f2 işlevini çağırır - bu da f1'in kapsamı içinde yerel olarak bildirilir) Ancak yerel değişkenlerin çalışması gerektiği gibi değildir. Yerel değişkenler gerçekten yerel ise (veya hem bash hem de ksh üzerinde çalışması gereken bir komut dosyasında olduğu varsayılırsa), bu bütünün sorunlarına "bu ada sahip değişken içinde depolayarak bir değer döndür" şemasına neden olur.
tetsujin

15

Bu, mevcut çözümlerden çok farklı değildir, ancak ayrı bir işlev kullanmaktan kaçınır IFS, üst kabukta değişiklik yapmaz ve hepsi tek bir satırdadır:

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

sonuçlanan

a,b,c

Sınırlama: ayırıcı bir karakterden uzun olamaz.


13

Harici komut kullanma:

$ 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

Uyarı, öğelerin boşluklara sahip olmadığını varsayar.


4
Bir ara değişken kullanmak istemiyorsanız, daha da kısa yapılabilir:echo ${FOO[@]} | tr ' ' ','
jesjimher

2
Olumsuz oyları anlamıyorum. Bu, burada yayınlanan diğerlerine göre çok kompakt ve okunabilir bir çözümdür ve boşluklar olduğunda işe yaramadığı konusunda açıkça uyarılmaktadır.
jesjimher

12

Dizeyi bir dize olarak yankılandı, sonra boşlukları satır beslemelerine dönüştürür ve daha sonra pasteböyle bir satırdaki her şeyi birleştirmek için kullanılır :

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

Sonuçlar:

a,b,c

Bu benim için en hızlı ve en temiz gibi görünüyor!


$FOOdizinin sadece ilk öğesidir. Ayrıca, boşluk içeren dizi öğeleri için bu kesilir.
Benjamin W.

9

@ 'Nin yeniden kullanılması önemli bir çözüm değildir, ancak $ {: 1} alt bölümünden ve bir aracı değişken gereksiniminden kaçınarak tek bir ifadeyle.

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

printf 'Biçim dizesi, bağımsız değişkenleri karşılamak için gerektiği sıklıkta yeniden kullanılıyor.' man sayfalarında, dizelerin birleşimleri belgelenir. Sonra, püf noktası, son toplayıcıyı kesmek için LIST uzunluğunu kullanmaktır, çünkü kesim alanlar sayıldıkça yalnızca LIST uzunluğunu koruyacaktır.


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

8
Cevabınızı etmelisiniz.
joce

En iyisi. Teşekkürler!!
peter pan gz

4
Keşke bu cevabı küçümseyebilseydim çünkü bir güvenlik açığı açar ve elementlerdeki boşlukları yok eder.
eel ghEEz

1
@bxm gerçekten boşlukları koruyor gibi görünüyor ve yankı argümanları bağlamından kaçmaya izin vermiyor. @QEklemenin, bir birleştirici olduğunda birleştirilen değerlerin yanlış yorumlanmasından kaçabileceğini düşündüm :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 '
Eklemenin

1
Gerekmedikçe alt kabukları kullanan çözümlerden kaçının.
konsolebox

5

Herhangi bir uzunluktaki ayırıcıları kabul eden printf çözümü (@ temel alınarak cevap önemli değildir)

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

Bu, önde gelen virgülle çıktı üretir.
Mark Renouf

Son çubuk = $ {bar: $ {# sep}} ayırıcıyı kaldırır. Sadece bir bash kabuğuna kopyalayıp yapıştırdım ve işe yarıyor. Hangi kabuğu kullanıyorsun?
Riccardo Galli

2
Herhangi bir printf format belirleyici (örn. %sİstemeden $sep
içeri girilmesi

sepsanitize edilebilir ${sep//\%/%%}. Çözümünüzü daha iyi ${bar#${sep}}ya da ${bar%${sep}}(alternatif) seviyorum . Bu, sonucu __değil, genel bir değişkene depolayan bir işleve dönüştürüldüğünde hoş olur echo.
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

Bu en üstte olmalıdır.
Eric Walker

6
Bu en üstte olmamalı: Ya eğer HISTSIZE=0?
har-wradim

@ har-wradim, hile paste -sd,tarihin kullanımı ile ilgili değil.
Veda

@Veda Hayır, bu kombinasyonun kullanımı ile ilgili ve eğer işe yaramazsa HISTSIZE=0- deneyin.
har-wradim

4

Üst cevabın daha kısa versiyonu:

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

Kullanımı:

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

1
: Daha uzun versiyonu, ama bir dizi değişkene bağımsız değişkenlerin bir dilim bir kopyasını yapmak gerekjoin_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Rockallite

Yine Başka Bir Sürüm: join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } Bu kullanımla çalışır: join_strings 'delim' "${array[@]}"veya join_strings 'delim' ${array[@]}
sıralanmamış

4

Şu ana kadar tüm dünyaların en iyilerini aşağıdaki fikirle birleştirin.

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

Bu küçük şaheser

  • % 100 saf bash (IFS ile geçici olarak ayarlanmamış parametre genişletme, harici arama yok, baskı yok ...)
  • kompakt, eksiksiz ve kusursuz (tek ve çok karakterli sınırlayıcılarla çalışır, beyaz boşluk, satır kesmeleri ve diğer kabuk özel karakterlerini içeren sınırlayıcılarla çalışır, boş sınırlayıcı ile çalışır)
  • verimli (alt kabuk yok, dizi kopyası yok)
  • basit ve aptal ve bir dereceye kadar güzel ve öğretici de

Örnekler:

$ 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
Bu hoş değil: en az 2 sorun: 1. join_ws ,(argüman olmadan) yanlış çıktılar ,,. 2. join_ws , -eyanlış hiçbir şey çıktı (bunun echoyerine yanlış kullandığınız için printf). Aslında echobunun yerine neden reklamını yaptığınızı bilmiyorum printf: echoherkesin bildiği gibi kırılmış ve printfsağlam bir yapı.
gniourf_gniourf

1

Şu anda kullanıyorum:

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

Hangi çalışır, ancak (genel durumda) dizi öğelerinin içinde bir boşluk varsa korkunç bir şekilde kırılacaktır.

(İlgilenenler için bu, pep8.py etrafındaki bir paket betiğidir )


Bu dizi değerlerini nereden alıyorsunuz? Eğer bu şekilde kod yazıyorsanız, neden sadece foo = "a, b, c" değil?
ghostdog74

Bu durumda aslında am değerleri sabit bir şekilde kodlanması, ama ben tek tek her yorum yapabilirsiniz böylece onları bir diziye koymak istiyorum. Ne demek istediğimi göstermek için cevabı güncelledim.
David Wolever

Aslında bash kullanıyorsanız varsayarsak, bu daha iyi çalışabilir: ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Operatör $()backtics'ten daha güçlüdür ($() ve"" ). ${TO_IGNORE[@]}Çift tırnak işareti ile sarmak da yardımcı olacaktır.
kevinarpe

1

Denemem.

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

1

Çok karakterli ayırıcılar için perl kullanın:

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

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

Veya tek bir satırda:

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

benim için çalışıyor, joinadı biraz saçma ile çakışmasına rağmen OS X.. ben onu çağırır conjoined, ya da belki jackie_joyner_kersee?
Alex Gray

1

Şimdiye kadarki en iyi dünya kombinasyonum hakkında detaylı yorumlar için @gniourf_gniourf teşekkürler. Kod iyice tasarlanmış ve test edilmemiş posta gönderme için üzgünüm. İşte daha iyi bir deneme.

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

Bu güzellik anlayışı ile

  • (hala)% 100 saf bash (printf'in de bir yerleşik olduğunu açıkça belirttiğiniz için teşekkürler. Daha önce bunun farkında değildim ...)
  • çok karakterli sınırlayıcılarla çalışır
  • daha kompakt ve daha eksiksizdir ve bu kez dikkatli bir şekilde düşünülmüş ve kabuk komut dosyalarından rastgele alt dizelerle diğerlerinin arasında rastgele alt dizelerle test edilmiştir, kabuk özel karakterlerinin veya kontrol karakterlerinin kullanımını veya ayırıcı ve / veya parametrelerde ve kenar kutularında karakter içermez ve köşe davaları ve hiç tartışmaya benzemeyen diğer tartışmalar. Bu, artık hata olmadığını garanti etmez, ancak bir tane bulmak biraz daha zor olacaktır. BTW, şu anda en çok oy alan cevaplar ve ilgili -e hata gibi şeylerden muzdarip ...

Ek örnekler:

$ 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

Birleştirmek istediğiniz öğelerin yalnızca boşlukla ayrılmış bir dize olmaması durumunda, şöyle bir şey yapabilirsiniz:

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

Örneğin, benim kullanım durumum bazı dizeleri benim kabuk komut dosyasında geçirilir ve bir SQL sorgusu üzerinde çalıştırmak için bunu kullanmanız gerekir:

./my_script "aa bb cc dd"

My_script içinde "SELECT * FROM table WHERE name IN ('aa', 'bb', 'cc', 'dd') yapmam gerekiyor. Sonra yukarıdaki komut yararlı olacaktır.


printf -v bar ...Printf döngüsünü bir alt kabukta çalıştırmak ve çıktıyı yakalamak yerine kullanabilirsiniz .
codeforester

yukarıdaki tüm süslü upvoted çözümleri işe yaramadı ama kaba olanı benim için yaptı (Y)
ishandutta2007

1

POSIX uyumlu kabukların çoğunun desteklediği bir özellik:

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[@]}"
}

It yolunda Bash kodu, ancak POSIX yoktur diziler (veya localhiç).
Anders Kaseorg

@Anders: Evet bunu son zamanlarda zor yoldan öğrendim :( POSIX uyumlu kabukların çoğunun dizileri desteklediği göründüğü için
şimdilik bırakacağım

1

Doğrudan bir diziye başvurmak için değişken dolaylı kullanımı da çalışır. Adlandırılmış referanslar da kullanılabilir, ancak yalnızca 4.3'te kullanılabilir hale geldi.

Bir fonksiyonun bu biçimini kullanmanın avantajı, ayırıcıyı isteğe bağlı (varsayılan olarak varsayılan ilk karakter olan IFSboşluk anlamına gelir; belki de isterseniz boş bir dize haline getirebilirsiniz) ve değerleri iki kez genişletmekten kaçınmasıdır (önce parametre olarak geçtiğinde "$@"ve fonksiyonun içindeki gibi ikinci ).

Bu çözüm, kullanıcının başka bir değişkene atanan bir dizenin birleşik bir sürümünü almak için, işlevi bir alt kabuk çağrısı yapan bir komut ikamesi içinde çağırmasını gerektirmez.

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.

İşlev için daha rahat bir ad kullanmaktan çekinmeyin.

Bu 3.1 ila 5.0-alfa çalışır. Gözlemlendiği gibi, değişken dolaylama sadece değişkenlerle değil, diğer parametrelerle de çalışır.

Parametre, değerleri depolayan bir varlıktır. Bir ad, bir sayı veya aşağıda Özel Parametreler altında listelenen özel karakterlerden biri olabilir. Değişken, bir adla gösterilen bir parametredir.

Diziler ve dizi öğeleri de parametrelerdir (değeri depolayan varlıklar) ve dizilere yapılan başvurular teknik olarak parametrelere de başvurulur. Ve özel parametre gibi @,array[@] aynı zamanda geçerli bir gönderme yapmaktadır.

Referansı parametrenin kendisinden saptıran değiştirilmiş veya seçici genişletme biçimleri (alt dize genişletmesi gibi) artık çalışmaz.

Güncelleme

Bash 5.0'ın yayın sürümünde, değişken dolaylamaya zaten dolaylı genişleme denir ve davranışı kılavuzda zaten açıkça belgelenmiştir:

Parametrenin ilk karakteri bir ünlem işareti (!) İse ve parametre bir aderef değilse, bir dolaylı aktarma düzeyi sağlar. Bash, parametrenin geri kalanını yeni parametre olarak genişleterek oluşan değeri kullanır; bu daha sonra genişletilir ve bu değer, orijinal parametrenin genişletilmesinden ziyade, genişletmenin geri kalanında kullanılır. Buna dolaylı genişleme denir.

Belgelerinde bu not alarak ${parameter}, parameterşu şekilde ifade edilir "tarif edildiği gibi (içinde) gibi bir kabuk parametresi PARAMETRELER veya dizi referans ". Ve dizilerin dokümantasyonunda, "Bir dizinin herhangi bir elemanına ${name[subscript]}" atıfta bulunulabilir " denir . Bu yapar__r[@] bir dizi başvurusu .

Bağımsız değişken sürümüne katıl

Benim Bkz yorumunu içinde Riccardo Galli cevap .


2
__Değişken adı olarak kullanmak için belirli bir neden var mı ? Kodu gerçekten okunmaz yapar.
PesaThe

@PesaThe Bu sadece bir tercih. Bir dönüş değişkeni için genel adları kullanmayı tercih ederim. Diğer jenerik olmayan adlar kendilerini belirli işlevlere bağlar ve ezber gerektirir. Farklı değişkenlerde değer döndüren birden fazla işlevi çağırmak kodu takip etmeyi daha az kolaylaştırabilir. Genel bir ad kullanmak, komut dosyasını, çakışmayı önlemek için değeri dönüş değişkeninden uygun değişkene aktarmaya zorlar ve döndürülen değerlerin gittiği yerde açık hale geldiği için kodun daha okunabilir olmasını sağlar. Yine de bu kural için birkaç istisna yapıyorum.
konsolebox

0

Bu yaklaşım, değerler içindeki boşluklarla ilgilenir, ancak bir döngü gerektirir:

#!/bin/bash

FOO=( a b c )
BAR=""

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

0

Diziyi bir döngü içinde oluşturursanız, işte basit bir yol:

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

0

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

Bunu yapmanın en kısa yolu budur.

Misal,

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

1
Bu, boşluklu dize için düzgün çalışmaz: `t = (a" b c "d); echo $ {t [2]} ("b c" yazdırır); echo $ {"$ {t [*]}" // /,} (a, b, c, d yazdırır)
kounoupis

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

0

Belki partiye geç kaldım, ama bu benim için işe yarıyor:

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

  echo "${output}"
}

-1

Belki de barh bir şeyi kaçırıyorum, çünkü bash / zsh'ın tamamında yeniyim, ama bana hiç kullanmanıza gerek yokmuş gibi görünüyor printf. Ayrıca onsuz yapmak çok çirkin olmaz.

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

En azından benim için şimdiye kadar sorunsuz çalıştı.

Örneğin join \| *.sh, dizinimde olduğumu ~varsayalım, çıktılarutilities.sh|play.sh|foobar.sh . Benim için yeterince iyi.

DÜZENLEME: Bu temel olarak Nil Geisweiller'in cevabıdır , ancak bir fonksiyonda genelleştirilmiştir.


1
Ben downvoter değilim, ama bir fonksiyonda bir küresel manipüle oldukça tuhaf görünüyor.
tripleee

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

Bu, sonunda ekstra virgülle de ilgilenir. Ben bash uzmanı değilim. Sadece 2c'im, çünkü bu daha basit ve anlaşılır


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

veya

$ a=(1 "a b" 3)
$ b=$(IFS=, ; echo "${a[*]}")
$ echo $b
1,a b,3
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.