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
.
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:
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
konsolebox
Stili 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
$d
biç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.
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
printf -v bar ",%s" "${foo[@]}"
. Bir fork
daha az (aslında clone
). Hatta bir dosyayı okumak için çatallı printf -v bar ",%s" $(<infile)
.
$separator
içermiyor %s
ya da varsa siz yapabilirsiniz printf
sağlam: printf "%s%s" "$separator" "${foo[@]}"
.
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.
printf "%s" "${foo[@]/#/$separator}"
.
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.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
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.
*
. Bash man sayfasında, "Special Parameters" ı (Özel Parametreler) arayın ve yanındaki açıklamaya bakın.*
:
Belki, örneğin,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(kıvırcık parantezler çizgileri değişken adından ayırır).
echo $IFS
aynı şeyi yapıyor.
Ş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.
İş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 -v
ve 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}"
}
join_ret
yerel 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.
$(...)
sondaki satırsonlarını düzeltir; dizinin son alanı sondaki satırsonu içeriyorsa, bunlar kesilir (tasarımımla kesilmedikleri demoya bakın).
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.
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.
echo ${FOO[@]} | tr ' ' ','
Dizeyi bir dize olarak yankılandı, sonra boşlukları satır beslemelerine dönüştürür ve daha sonra paste
bö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!
$FOO
dizinin sadece ilk öğesidir. Ayrıca, boşluk içeren dizi öğeleri için bu kesilir.
@ '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.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
Eklemenin, 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 '
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
printf
format belirleyici (örn. %s
İstemeden $sep
sep
sanitize 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
.
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,
tarihin kullanımı ile ilgili değil.
HISTSIZE=0
- deneyin.
Üst cevabın daha kısa versiyonu:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Kullanımı:
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' "${@}"; }
Bu kullanımla çalışır: join_strings 'delim' "${array[@]}"
veya join_strings 'delim' ${array[@]}
Ş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
Ö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
join_ws ,
(argüman olmadan) yanlış çıktılar ,,
. 2. join_ws , -e
yanlış hiçbir şey çıktı (bunun echo
yerine yanlış kullandığınız için printf
). Aslında echo
bunun yerine neden reklamını yaptığınızı bilmiyorum printf
: echo
herkesin bildiği gibi kırılmış ve printf
sağlam bir yapı.
Ş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 )
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.
Ç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
join
adı biraz saçma ile çakışmasına rağmen OS X
.. ben onu çağırır conjoined
, ya da belki jackie_joyner_kersee
?
Ş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
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 $
$
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 .
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[@]}"
}
local
hiç).
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 IFS
boş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.
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 .
Benim Bkz yorumunu içinde Riccardo Galli cevap .
__
Değişken adı olarak kullanmak için belirli bir neden var mı ? Kodu gerçekten okunmaz yapar.
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[*]}
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
bash: ${"${arr[*]}"// /,}: bad substitution
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.
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