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.
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.
Jawaban:
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
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 :)
$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.
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
printf -v bar ",%s" "${foo[@]}". Ini satu forkkurang (sebenarnya clone). Hal ini bahkan forking membaca file: printf -v bar ",%s" $(<infile).
$separatortidak mengandung %satau seperti itu, Anda dapat membuat Anda printfkuat: printf "%s%s" "$separator" "${foo[@]}".
printf "%s%s"akan menggunakan separator pada contoh pertama HANYA mengatur output, dan kemudian hanya menyatukan sisa argumen.
printf "%s" "${foo[@]/#/$separator}".
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.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@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.
*. Di halaman bash man, cari "Parameter Khusus" dan cari penjelasan di sebelah *:
Mungkin, misalnya,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"(kurung kurawal memisahkan tanda hubung dari nama variabel).
echo $IFSmelakukan hal yang sama.
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.
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}"
}
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.
$(...)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).
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.
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.
echo ${FOO[@]} | tr ' ' ','
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!
$FOOhanya elemen pertama dari array. Juga, ini memecah untuk elemen array yang berisi spasi.
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.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@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 '
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
printf %s$sep
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.
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,bukan tentang penggunaan sejarah.
HISTSIZE=0- coba saja.
Versi pendek dari jawaban teratas:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Pemakaian:
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' "${@}"; } Ini berfungsi dengan penggunaan: join_strings 'delim' "${array[@]}"atau tidak join_strings 'delim' ${array[@]}
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
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
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.
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 )
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Operator $()lebih kuat daripada backtics (memungkinkan bersarang $()dan ""). Membungkus ${TO_IGNORE[@]}dengan tanda kutip ganda juga akan membantu.
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
joinnamanya bertentangan dengan beberapa omong kosong di OS X.. saya akan menyebutnya conjoined, atau mungkin jackie_joyner_kersee?
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
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 $
$
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.
printf -v bar ...daripada harus menjalankan loop printf di subkulit dan menangkap output.
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[@]}"
}
local) sama sekali.
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.
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.
Lihat komentar saya dalam jawaban Riccardo Galli .
__sebagai nama variabel? Membuat kode benar-benar tidak dapat dibaca.
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[*]}
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
bash: ${"${arr[*]}"// /,}: bad substitution
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.
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