Bagaimana saya bisa bergabung dengan elemen array di Bash?

416

Jika saya memiliki array seperti ini di Bash:

FOO=( a b c )

Bagaimana cara saya bergabung dengan elemen dengan koma? Misalnya memproduksi a,b,c.

David Wolever
sumber

Jawaban:

571

Solusi penulisan ulang oleh Pascal Pilz sebagai fungsi dalam Bash 100% murni (tanpa perintah eksternal):

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

Sebagai contoh,

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

Atau, kita dapat menggunakan printf untuk mendukung pembatas multi-karakter, menggunakan ide oleh @gniourf_gniourf

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

Sebagai contoh,

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
Nicholas Sushkin
sumber
9
Gunakan ini untuk pemisah multicharacter: function join {perl -e '$ s = shift @ARGV; print join ($ s, @ARGV); ' "$ @"; } bergabunglah ',' abc # a, b, c
Daniel Patru
4
@dpatru tetap membuat bash murni itu?
CMCDragonkai
4
@ puchu Yang tidak berfungsi adalah pemisah multi-karakter. Mengatakan "ruang tidak berfungsi" membuatnya terdengar seperti bergabung dengan ruang tidak berfungsi. Itu benar.
Eric
6
Ini mempromosikan subkulit pemijahan jika menyimpan output ke variabel. Gunakan konsoleboxgaya :) function join { local IFS=$1; __="${*:2}"; }atau function join { IFS=$1 eval '__="${*:2}"'; }. Kemudian gunakan __setelah itu. Ya, saya yang mempromosikan penggunaan __sebagai variabel hasil;) (dan variabel iterasi umum atau variabel sementara). Jika konsepnya sampai ke situs wiki Bash yang populer, mereka menyalin saya :)
konsolebox
6
Jangan letakkan ekspansi $ddalam penentu format printf. Anda pikir Anda aman karena Anda "lolos" %tetapi ada peringatan lain: ketika pembatas berisi backslash (misalnya, \n) atau ketika pembatas dimulai dengan tanda hubung (dan mungkin orang lain saya tidak bisa memikirkan sekarang). Tentu saja Anda dapat memperbaikinya (ganti backslash dengan double backslash dan gunakan printf -- "$d%s"), tetapi pada titik tertentu Anda akan merasa bahwa Anda berjuang melawan shell alih-alih bekerja dengannya. Itu sebabnya, dalam jawaban saya di bawah ini, saya menambahkan pembatas pada persyaratan yang harus diikuti.
gniourf_gniourf
206

Solusi lain:

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

echo $bar

Sunting: sama tetapi untuk pemisah panjang variabel multi-karakter:

#!/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
tidak masalah
sumber
7
+1. Bagaimana dengan printf -v bar ",%s" "${foo[@]}". Ini satu forkkurang (sebenarnya clone). Hal ini bahkan forking membaca file: printf -v bar ",%s" $(<infile).
TrueY
14
Alih-alih berdoa $separatortidak mengandung %satau seperti itu, Anda dapat membuat Anda printfkuat: printf "%s%s" "$separator" "${foo[@]}".
musiphil
5
@musiphil Salah Dari bash man: "Format digunakan kembali seperlunya untuk menggunakan semua argumen. Menggunakan dua format placeholder seperti dalam printf "%s%s"akan menggunakan separator pada contoh pertama HANYA mengatur output, dan kemudian hanya menyatukan sisa argumen.
AnyDev
3
@AndrDevEK: Terima kasih telah menangkap kesalahannya. Sebaliknya saya akan menyarankan sesuatu seperti printf "%s" "${foo[@]/#/$separator}".
musiphil
2
@musiphil, Terima kasih. Iya! Kemudian printf menjadi redundan dan garis itu dapat dikurangi menjadi IFS=; regex="${foo[*]/#/$separator}". Pada titik ini, ini pada dasarnya menjadi jawaban gniourf_gniourf yang IMO lebih bersih dari awal, yaitu menggunakan fungsi untuk membatasi cakupan perubahan IFS dan temp vars.
AnyDev
145
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
Pascal Pilz
sumber
3
Kutipan ganda luar dan tanda kutip ganda di sekitar titik dua tidak diperlukan. Hanya tanda kutip ganda yang diperlukan:bar=$( IFS=, ; echo "${foo[*]}" )
ceving
8
+1 untuk solusi paling ringkas yang tidak memerlukan loop, yang tidak membutuhkan perintah eksternal dan yang tidak memaksakan batasan tambahan pada set karakter dari argumen.
ceving
22
saya suka solusinya, tetapi hanya berfungsi jika IFS adalah satu karakter
Jayen
8
Adakah gagasan mengapa ini tidak berhasil jika menggunakan @alih-alih *, seperti pada $(IFS=, ; echo "${foo[@]}")? Saya dapat melihat bahwa *sudah mempertahankan spasi putih dalam elemen, sekali lagi tidak yakin bagaimana, karena @biasanya diperlukan untuk kepentingan ini.
haridsv
10
Saya menemukan jawaban untuk pertanyaan saya sendiri di atas. Jawabannya adalah IFS hanya dikenali *. Di halaman bash man, cari "Parameter Khusus" dan cari penjelasan di sebelah *:
haridsv
66

Mungkin, misalnya,

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

echo "$FOOJOIN"
martin clayton
sumber
3
Jika Anda melakukannya, itu berpikir bahwa IFS- adalah variabel. Yang harus Anda lakukan echo "-${IFS}-"(kurung kurawal memisahkan tanda hubung dari nama variabel).
Dijeda sampai pemberitahuan lebih lanjut.
1
Masih mendapat hasil yang sama (saya hanya menempatkan tanda hubung untuk menggambarkan titik ... echo $IFSmelakukan hal yang sama.
David Wolever
41
Yang mengatakan, ini sepertinya masih berfungsi ... Jadi, seperti kebanyakan hal dengan Bash, aku akan berpura-pura seperti aku memahaminya dan melanjutkan hidupku.
David Wolever
2
"-" bukan karakter yang valid untuk nama variabel, jadi shell melakukan hal yang benar ketika Anda menggunakan $ IFS-, Anda tidak perlu $ {IFS} - (bash, ksh, sh dan zsh di linux dan solaris juga setuju).
Idelic
2
@Membuat perbedaan antara gema Anda dan Dennis adalah bahwa ia telah menggunakan kutipan ganda. Konten IFS digunakan 'pada input' sebagai pernyataan karakter pemisah kata - sehingga Anda akan selalu mendapatkan baris kosong tanpa tanda kutip.
martin clayton
30

Anehnya solusi saya belum diberikan :) Ini adalah cara paling sederhana untuk saya. Tidak perlu fungsi:

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

Catatan: Solusi ini diamati bekerja dengan baik dalam mode non-POSIX. Dalam mode POSIX , elemen-elemen masih bergabung dengan benar, tetapi IFS=,menjadi permanen.

konsolebox
sumber
sayangnya hanya berfungsi untuk pembatas karakter tunggal
maoizm
24

Berikut adalah fungsi Bash 100% murni yang berfungsi:

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

Lihat:

$ 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


$

Ini bahkan mempertahankan baris baru yang tertinggal, dan tidak perlu subkulit untuk mendapatkan hasil dari fungsinya. Jika Anda tidak menyukai printf -v(mengapa Anda tidak menyukainya?) Dan memberikan nama variabel, tentu saja Anda dapat menggunakan variabel global untuk string yang dikembalikan:

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}"
}
gniourf_gniourf
sumber
1
Solusi terakhir Anda sangat bagus, tetapi bisa dibuat lebih bersih dengan membuat join_retvariabel lokal, dan kemudian menggemakannya di akhir. Ini memungkinkan join () untuk digunakan dengan cara scripting shell yang biasa, misalnya $(join ":" one two three), dan tidak memerlukan variabel global.
James Sneeringer
1
@ JamesSneeringer Saya sengaja menggunakan desain ini untuk menghindari subkulit. Dalam skrip shell, tidak seperti dalam banyak bahasa lain, variabel global yang menggunakan cara itu tidak selalu merupakan hal yang buruk; terutama jika mereka ada di sini untuk membantu menghindari subkulit. Selain itu, $(...)trim mengikuti baris baru; jadi jika bidang terakhir array berisi baris baru, ini akan dipangkas (lihat demo di mana mereka tidak dipangkas dengan desain saya).
gniourf_gniourf
Ini bekerja dengan pemisah multi-karakter, yang membuat saya senang ^ _ ^
spiffytech
Untuk mengatasi "mengapa Anda tidak suka printf -v?": Di Bash, variabel lokal tidak benar-benar berfungsi-lokal, sehingga Anda dapat melakukan hal-hal seperti ini. (Panggil fungsi f1 dengan variabel lokal x, yang pada gilirannya memanggil fungsi f2 yang memodifikasi x - yang dinyatakan lokal dalam ruang lingkup f1) Tapi sebenarnya bukan bagaimana variabel lokal seharusnya bekerja. Jika variabel lokal benar-benar lokal (atau diasumsikan, misalnya dalam skrip yang harus bekerja pada bash dan ksh) maka itu menyebabkan masalah dengan keseluruhan ini "mengembalikan nilai dengan menyimpannya dalam variabel dengan skema nama ini".
tetsujin
15

Ini tidak terlalu berbeda dari solusi yang ada, tetapi menghindari menggunakan fungsi terpisah, tidak memodifikasi IFSdi shell induk dan semuanya dalam satu baris:

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

yang menghasilkan

a,b,c

Batasan: pemisah tidak boleh lebih dari satu karakter.

Benjamin W.
sumber
13

Tidak menggunakan perintah eksternal:

$ 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

Peringatan, ini mengasumsikan elemen tidak memiliki spasi putih.

Nil Geisweiller
sumber
4
Jika Anda tidak ingin menggunakan variabel perantara, itu bisa dilakukan lebih pendek:echo ${FOO[@]} | tr ' ' ','
jesjimher
2
Saya tidak mengerti suara negatif. Ini adalah solusi yang jauh lebih ringkas dan mudah dibaca daripada yang diposting di sini, dan jelas diperingatkan bahwa itu tidak berfungsi ketika ada ruang.
jesjimher
12

Saya akan menggema array sebagai string, kemudian mengubah spasi menjadi feed garis, dan kemudian gunakan pasteuntuk bergabung dengan semuanya dalam satu baris seperti ini:

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

Hasil:

a,b,c

Ini sepertinya yang tercepat dan terbersih untukku!

Yanick Girouard
sumber
$FOOhanya elemen pertama dari array. Juga, ini memecah untuk elemen array yang berisi spasi.
Benjamin W.
9

Dengan menggunakan kembali solusi @ tidak penting, tetapi dengan satu pernyataan dengan menghindari substitusi $ {: 1} dan kebutuhan variabel perantara.

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

printf memiliki 'String format digunakan kembali sesering yang diperlukan untuk memenuhi argumen.' di halaman manualnya, sehingga rangkaian string didokumentasikan. Maka triknya adalah menggunakan panjang LIST untuk memotong sperator terakhir, karena cut hanya akan mempertahankan panjang LIST saat field menghitung.

Koper kecil
sumber
7
s=$(IFS=, eval 'echo "${FOO[*]}"')
belut ghEEz
sumber
8
Anda harus menyempurnakan jawaban Anda.
joce
Yang terbaik. Terima kasih!!
peter pan gz
4
Saya berharap saya bisa menurunkan suara jawaban ini karena membuka lubang keamanan dan karena itu akan menghancurkan ruang dalam elemen.
belut ghEEz
1
@ BXM memang, tampaknya mempertahankan ruang dan tidak memungkinkan melarikan diri dari konteks argumen gema. Saya membayangkan bahwa menambahkan @Qbisa lepas dari nilai-nilai yang digabungkan dari kesalahan interpretasi ketika mereka memiliki sebuah joiner di dalamnya: foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"keluaran'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
belut ghEEz
1
Hindari solusi yang memanfaatkan subkulit kecuali diperlukan.
konsolebox
5

solusi printf yang menerima pemisah dengan panjang berapa pun (berdasarkan @ tidak penting jawab)

#/!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
Riccardo Galli
sumber
Ini menghasilkan output dengan koma terkemuka.
Mark Renouf
Bilah terakhir = $ {bilah: $ {# sep}} menghapus pemisah. Saya hanya menyalin dan menempel di bash shell dan itu berhasil. Shell apa yang Anda gunakan?
Riccardo Galli
2
Setiap printf %s$sep
penentu
sepdapat dibersihkan dengan ${sep//\%/%%}. Saya suka solusi Anda lebih baik daripada ${bar#${sep}}atau ${bar%${sep}}(alternatif). Ini bagus jika dikonversi ke fungsi yang menyimpan hasil ke variabel generik seperti __, dan bukan echoitu.
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
Steven Penny
sumber
Ini harus di atas.
Eric Walker
6
Ini tidak boleh di atas: bagaimana jika HISTSIZE=0?
har-wradim
@ har-wradim, triknya paste -sd,bukan tentang penggunaan sejarah.
Veda
@ Veda Tidak, ini tentang penggunaan kombinasi, dan itu tidak akan berhasil jika HISTSIZE=0- coba saja.
har-wradim
4

Versi pendek dari jawaban teratas:

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

Pemakaian:

joinStrings "$myDelimiter" "${myArray[@]}"
Camilo Martin
sumber
1
Versi yang lebih panjang, tetapi tidak perlu membuat salinan sepotong argumen ke variabel array:join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Rockallite
Namun Versi Lain: join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } Ini berfungsi dengan penggunaan: join_strings 'delim' "${array[@]}"atau tidak join_strings 'delim' ${array[@]}
dikutip
4

Kombinasikan yang terbaik dari semua dunia sejauh ini dengan mengikuti ide.

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

Karya kecil ini adalah

  • 100% pure bash (ekspansi parameter dengan IFS sementara tidak disetel, tidak ada panggilan eksternal, tidak ada printf ...)
  • ringkas, lengkap dan tanpa cacat (bekerja dengan pembatas karakter tunggal dan multi-karakter, bekerja dengan pembatas yang berisi ruang putih, penghentian garis dan karakter khusus shell lainnya, bekerja dengan pembatas kosong)
  • efisien (tidak ada subkulit, tidak ada salinan array)
  • sederhana dan bodoh dan, pada tingkat tertentu, cantik dan instruktif juga

Contoh:

$ 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
tamu
sumber
1
Tidak menyenangkan: setidaknya 2 masalah: 1. join_ws ,(tanpa argumen) salah output ,,. 2. join_ws , -esalah menghasilkan apa-apa (itu karena Anda salah menggunakan, echobukan printf). Saya sebenarnya tidak tahu mengapa Anda mengiklankan penggunaan echoalih-alih printf: echoterkenal rusak, dan printfmerupakan bawaan yang kuat.
gniourf_gniourf
1

Saat ini saya sedang menggunakan:

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

Yang berhasil, tetapi (dalam kasus umum) akan pecah mengerikan jika elemen array memiliki ruang di dalamnya.

(Bagi mereka yang tertarik, ini adalah skrip pembungkus di sekitar pep8.py )

David Wolever
sumber
dari mana Anda mendapatkan nilai-nilai array? jika Anda melakukan hardcoding seperti itu, mengapa tidak hanya foo = "a, b, c".?
ghostdog74
Dalam hal ini saya benar - benar mengkodekan nilai-nilai, tetapi saya ingin meletakkannya dalam array sehingga saya dapat mengomentari masing-masing secara individual. Saya telah memperbarui jawaban untuk menunjukkan kepada Anda apa yang saya maksud.
David Wolever
Dengan asumsi Anda benar-benar menggunakan bash, ini mungkin bekerja lebih baik: ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Operator $()lebih kuat daripada backtics (memungkinkan bersarang $()dan ""). Membungkus ${TO_IGNORE[@]}dengan tanda kutip ganda juga akan membantu.
kevinarpe
1

Usaha saya.

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

Gunakan perl untuk pemisah multicharacter:

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

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

Atau dalam satu baris:

perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
Daniel Patru
sumber
bekerja untuk saya, walaupun joinnamanya bertentangan dengan beberapa omong kosong di OS X.. saya akan menyebutnya conjoined, atau mungkin jackie_joyner_kersee?
Alex Grey
1

Terima kasih @gniourf_gniourf untuk komentar terperinci tentang kombinasi dunia terbaik saya sejauh ini. Maaf untuk kode pengiriman yang tidak dirancang dan diuji secara menyeluruh. Ini adalah upaya yang lebih baik.

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

Kecantikan ini menurut konsepsi adalah

  • (masih) 100% bash murni (terima kasih telah secara eksplisit menunjukkan bahwa printf adalah builtin juga. Saya tidak mengetahui hal ini sebelumnya ...)
  • bekerja dengan pembatas multi-karakter
  • lebih kompak dan lebih lengkap dan kali ini dipikirkan dengan seksama dan stress-test jangka panjang dengan substring acak dari skrip shell antara lain, meliputi penggunaan karakter khusus shell atau karakter kontrol atau tidak ada karakter di kedua pemisah dan / atau parameter, dan kasus tepi , dan memojokkan kasus dan quibble lainnya seperti tanpa argumen sama sekali. Itu tidak menjamin tidak ada bug lagi, tetapi akan sedikit lebih sulit untuk menemukannya. BTW, bahkan jawaban terpilih saat ini dan yang terkait menderita dari hal-hal seperti itu -e bug ...

Contoh tambahan:

$ 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 $ 
$
tamu
sumber
1

Jika elemen yang ingin Anda gabungkan bukan array hanya string yang dipisahkan spasi, Anda bisa melakukan sesuatu seperti ini:

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

misalnya, kasus penggunaan saya adalah bahwa beberapa string dilewatkan dalam skrip shell saya dan saya perlu menggunakan ini untuk berjalan pada query SQL:

./my_script "aa bb cc dd"

Dalam my_script, saya perlu melakukan "SELECT * FROM table WHERE name IN ('aa', 'bb', 'cc', 'dd'). Kemudian perintah di atas akan berguna.

Dexin Wang
sumber
Anda dapat menggunakan printf -v bar ...daripada harus menjalankan loop printf di subkulit dan menangkap output.
codeforester
semua solusi upvoted yang mewah di atas tidak bekerja tetapi yang kasar Anda lakukan untuk saya (Y)
ishandutta2007
1

Inilah salah satu yang didukung sebagian besar kerang 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[@]}"
}
pengguna541686
sumber
Ini kode Bash baik-baik saja, tetapi POSIX tidak memiliki array (atau local) sama sekali.
Anders Kaseorg
@Anders: Ya, saya baru-baru ini mempelajari cara yang sulit :( Saya akan membiarkannya untuk sekarang karena sebagian besar kerang yang kompatibel dengan POSIX tampaknya mendukung array.
user541686
1

Menggunakan tipuan variabel untuk merujuk langsung ke array juga berfungsi. Referensi yang dinamai juga dapat digunakan, tetapi hanya tersedia dalam 4.3.

Keuntungan menggunakan bentuk fungsi ini adalah Anda dapat memiliki pemisah opsional (default ke karakter pertama default IFS, yang merupakan spasi; mungkin menjadikannya string kosong jika Anda mau), dan menghindari perluasan nilai dua kali (pertama ketika dilewatkan sebagai parameter, dan kedua sebagai"$@" di dalam fungsi).

Solusi ini juga tidak mengharuskan pengguna untuk memanggil fungsi di dalam substitusi perintah - yang memanggil subkulit, untuk mendapatkan versi gabungan dari string yang ditugaskan ke variabel lain.

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.

Jangan ragu untuk menggunakan nama yang lebih nyaman untuk fungsi ini.

Ini bekerja dari 3.1 hingga 5.0-alpha. Seperti yang diamati, tipuan variabel tidak hanya bekerja dengan variabel tetapi dengan parameter lain juga.

Parameter adalah entitas yang menyimpan nilai. Itu bisa berupa nama, angka, atau salah satu karakter khusus yang tercantum di bawah di bawah Parameter Khusus. Variabel adalah parameter yang dilambangkan dengan nama.

Array dan elemen array juga merupakan parameter (entitas yang menyimpan nilai), dan referensi ke array juga secara teknis merujuk ke parameter. Dan sangat mirip dengan parameter khusus @,array[@] juga membuat referensi yang valid.

Bentuk ekspansi yang diubah atau selektif (seperti ekspansi substring) yang menyimpang referensi dari parameter itu sendiri tidak lagi berfungsi.

Memperbarui

Dalam versi rilis Bash 5.0, indirection variabel sudah disebut ekspansi tidak langsung dan perilakunya sudah secara eksplisit didokumentasikan dalam manual:

Jika karakter pertama dari parameter adalah tanda seru (!), Dan parameter bukan nameref, itu akan memperkenalkan tingkat tipuan. Bash menggunakan nilai yang dibentuk dengan memperluas sisa parameter sebagai parameter baru; ini kemudian diperluas dan nilai tersebut digunakan di sisa ekspansi, bukan ekspansi dari parameter asli. Ini dikenal sebagai ekspansi tidak langsung.

Mencatat bahwa dalam dokumentasi ${parameter}, parameterdisebut sebagai "parameter shell seperti yang dijelaskan (dalam) PARAMETER atau referensi array ". Dan dalam dokumentasi array, disebutkan bahwa "Setiap elemen array dapat dirujuk menggunakan ${name[subscript]}". Ini membuat __r[@]referensi array.

Bergabung dengan versi argumen

Lihat komentar saya dalam jawaban Riccardo Galli .

konsolebox
sumber
2
Apakah ada alasan khusus untuk digunakan __sebagai nama variabel? Membuat kode benar-benar tidak dapat dibaca.
PesaThe
@PesaThe Ini hanya preferensi. Saya lebih suka menggunakan nama generik untuk variabel kembali. Nama-nama non-generik lainnya menghubungkan diri mereka dengan fungsi-fungsi tertentu, dan itu membutuhkan penghafalan. Memanggil beberapa fungsi yang mengembalikan nilai pada variabel yang berbeda dapat membuat kode lebih mudah diikuti. Menggunakan nama generik akan memaksa scripter untuk mentransfer nilai dari variabel kembali ke variabel yang tepat untuk menghindari konflik, dan itu membuat kode akhirnya menjadi lebih mudah dibaca karena menjadi eksplisit di mana nilai yang dikembalikan pergi. Saya membuat beberapa pengecualian untuk aturan itu.
konsolebox
0

Pendekatan ini menangani ruang di dalam nilai, tetapi membutuhkan loop:

#!/bin/bash

FOO=( a b c )
BAR=""

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

Jika Anda membuat array dalam satu lingkaran, berikut adalah cara sederhana:

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

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

Ini adalah cara terpendek untuk melakukannya.

Contoh,

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5
pengguna31986
sumber
1
Ini tidak berfungsi dengan benar untuk string dengan spasi: `t = (a" b c "d); echo $ {t [2]} (mencetak "b c"); echo $ {"$ {t [*]}" // /,} (mencetak a, b, c, d)
kounoupis
7
bash: ${"${arr[*]}"// /,}: bad substitution
Cameron Hudson
0

Mungkin terlambat untuk pesta, tetapi ini berhasil untuk saya:

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

  echo "${output}"
}
TacB0sS
sumber
-1

Mungkin saya kehilangan sesuatu yang jelas, karena saya adalah pemula dalam hal bash / zsh, tetapi bagi saya sepertinya Anda tidak perlu menggunakan printfsama sekali. Juga tidak akan benar-benar jelek untuk dilakukan tanpanya.

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

Setidaknya, itu telah bekerja untuk saya sejauh ini tanpa masalah.

Sebagai contoh,, join \| *.shyang, katakanlah saya di ~direktori saya , outpututilities.sh|play.sh|foobar.sh . Cukup baik untukku.

EDIT: Ini pada dasarnya adalah jawaban Nil Geisweiller , tetapi digeneralisasi menjadi suatu fungsi.

Yordania
sumber
1
Saya bukan downvoter, tetapi memanipulasi global dalam suatu fungsi tampaknya cukup aneh.
tripleee
-2
liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

Ini menangani koma ekstra di akhir juga. Saya bukan ahli bash. Hanya 2c saya, karena ini lebih sederhana dan dapat dimengerti

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

atau

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