BASH ilişkisel dizi baskısı


17

Tüm öğelerin üzerinde döngü oluşturmadan dizinin tamamını ([key] = değer) yazdırmanın bir yolu var mı?

Bazı öğeler içeren bir dizi oluşturduğumu varsayın:

declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)

Tüm diziyi

for i in "${!array[@]}"
do
echo "${i}=${array[$i]}"
done

Ancak, bash zaten tüm dizi öğelerini bir "git" - nasıl hem anahtarları hem de ${!array[@]}değerleri almak için zaten biliyor gibi görünüyor${array[@]} .

Bu bilgiyi döngü olmadan bash yapmanın bir yolu var mı?

Düzenleme:
typeset -p arrayyapar!
Ancak tek bir ikame hem önek ve sonek kaldıramazsınız:

a="$(typeset -p array)"
b="${a##*(}"
c="${b%% )*}"

Çıktının yalnızca anahtar = değer kısmını almanın / yazdırmanın daha temiz bir yolu var mı?

Yanıtlar:


15

Sanırım orada iki farklı şey soruyorsun.

Bu bilgiyi döngü olmadan bash yapmanın bir yolu var mı?

Evet, ama sadece döngüyü kullanmak kadar iyi değiller.

Çıktının yalnızca anahtar = değer kısmını almanın / yazdırmanın daha temiz bir yolu var mı?

Evet, fordöngü. Harici programlar gerektirmediği, basit olduğu ve kesin çıktı formatını sürprizler olmadan kontrol etmeyi oldukça kolaylaştırdığı avantajları vardır.


declare -p( typeset -p) Çıktısını ele almaya çalışan herhangi bir çözüm, a) parantez veya parantez içeren değişkenlerin kendileriyle ilgilenmek zorundadır, b) declare -pkabuğun çıktısı için geçerli girdi olmasını sağlamak için eklenmesi gereken alıntı .

Örneğin, b="${a##*(}"herhangi bir anahtar / değer bir açılış parantezi içeriyorsa , genişletmeniz değerlerin bazılarını yer. Bunun nedeni ##, en uzun öneki kaldıran kullanmanızdır . Aynı c="${b%% )*}". Tabii ki tarafından basılmış olan kazan plakasını eşleştirebilirsiniz.declare daha tam olarak , yaptığı tüm alıntıları istemiyorsanız yine de zor zamanlarınız olurdu.

İhtiyacınız olmadığı sürece bu çok hoş görünmüyor.

$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'

forDöngü ile çıktı biçimini istediğiniz gibi seçmek daha kolaydır:

# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'

# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'

Oradan , çıktı biçimini değiştirmek de basittir (anahtarın etrafındaki köşeli parantezleri kaldırın, tüm anahtar / değer çiftlerini tek bir satıra koyun ...). Kabuğun kendisinden başka bir şey için alıntı yapmanız gerekiyorsa, yine de bunu kendiniz yapmanız gerekir, ancak en azından üzerinde çalışmak için ham verilere sahipsiniz. (Anahtarlarda veya değerlerde yeni satırlarınız varsa, muhtemelen biraz alıntı yapmanız gerekecektir.)

Mevcut bir Bash (4.4, sanırım) printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"yerine, kullanabilirsiniz printf "%q=%q". Biraz daha hoş bir alıntı biçimi üretir, ancak elbette yazmayı hatırlamak biraz daha fazla iş. (Ve köşe @anahtarı olarak tırnak işareti için %qalıntı yapmaz.)

For döngüsü yazmak için çok yorgun görünüyorsa, bir yere bir işlev kaydedin (burada alıntı yapmadan):

printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ;  }  

Ve sonra sadece bunu kullanın:

$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)

Dizine alınmış dizilerle de çalışır:

$ b=(abba acdc)
$ printarr b
0=abba
1=acdc

printf ...%q...Eğer dizinin @% q anahtarı belirtmediği ve a=([@]=value)sözdizimi hatası olduğu için dizinin bir anahtarı varsa, varyantınızın çıktısının kabuğa yeniden giriş için uygun olmadığını unutmayın bash.
Stéphane Chazelas

@ StéphaneChazelas, görünüşe göre. "${x@Q}"tüm dizeleri tırnak (ve daha güzel görünüyor) çünkü, o da tırnak. bunu kullanma hakkında bir not ekledi.
ilkkachu

Evet, mksh'den kopyalandı. Diğerleri ile birleştirilemeyen farklı bir şekle sahip başka bir operatör. Yine, zshdaha iyi bir tasarım için değişken genişleme bayrakları ile (yine bash'ı on yıl öncesine dayanan ve alıntı stilini seçebileceğiniz: $ {(q) var}, $ {(qq) var} ...) ile bakın. bash, boş dizeyi alıntılamaması nedeniyle mksh ile aynı soruna sahiptir (burada bir sorun değildir, çünkü bash boş anahtarları desteklemez). (Tek teklif dışındaki alıntı stilleri kullanırken Ayrıca ${var@Q}başvurur $'...'bazı değerler için) o kod aynı yerdeki reinput olmak önemlidir.
Stéphane Chazelas

@ StéphaneChazelas, sanırım boş bir dize değil, ayarlanmamış bir değer mi demek istiyorsun? ( x=; echo "${x@Q}"verir '', unset x; echo "${x@Q}"hiçbir şey vermez .) Bash'ın bazı durumlarda gerçekten iyi olabilecek gerçek bir satırsonu @Qtercih ediyor gibi görünüyor $'\n'(ama başkalarının ne tercih ettiğini söyleyemem). Tabii ki bir seçim yapmak kötü olmazdı.
ilkkachu

Ah evet üzgünüm, bunu fark etmemiştim. Bu mksh'den bir fark. $'...'Sözdizimi gibi şeyler potansiyel bir sorundur LC_ALL=zh_HK.big5hkscs bash -c 'a=$'\''\n\u3b1'\''; printf "%s\n" "${a@Q}"'çıkışları olan $'\n<0xa3><0x5c>'ve 0x5cbu alıntı, farklı bir yerel ayarda yorumlanmıştır eğer bir sorun var diye, yalnız ters eğik çizgi olduğunu.
Stéphane Chazelas

9
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'

2 çatal

Belki bu:

printf "%s\n" "${!array[@]}"
a2
a1
f50
zz
b1

printf "%s\n" "${array[@]}"
2
1
abcd
Hello World
bbb

printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t
a2                              2
a1                              1
f50                             abcd
zz                              Hello World
b1                              bbb

3 çatal

veya bu:

paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}")
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Çatal yok

karşılaştırılacak

for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Yürütme sürelerinin karşılaştırılması

Son sözdizimi çatal kullanmadığından, daha hızlı olabilirler:

time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
      5      11      76
real    0m0.005s
user    0m0.000s
sys     0m0.000s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
      5       6      41
real    0m0.008s
user    0m0.000s
sys     0m0.000s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
      5       6      41
real    0m0.002s
user    0m0.000s
sys     0m0.001s

Ancak dizi büyürse bu onaylama doğru olmaz; çatalları azaltmak küçük işlem için verimli ise, özel aletler kullanmak daha büyük işlem için daha verimlidir.

for i in {a..z}{a..z}{a..z};do array[$i]=$RANDOM;done


time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
  17581   35163  292941
real    0m0.150s
user    0m0.124s
sys     0m0.036s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
  17581   17582  169875
real    0m0.140s
user    0m0.000s
sys     0m0.004s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
  17581   17582  169875
real    0m0.312s
user    0m0.268s
sys     0m0.076s

düşünce

Her iki ( çatallı ) çözüm hizalama kullandığından , herhangi bir değişken yeni satır içeriyorsa hiçbiri çalışmaz . Bu durumda, tek yol bir fordöngüdür.


Akıllıca bakarken, her iki yol da a'dan daha az verimlidir for. Bu gerçekten bir utanç.
Satō Katsura

@SatoKatsura Kabul ediyorum, ancak daha yavaşsa, sözdizimi prkullanımı daha kısa ... prBüyük dizilerde bile sözdiziminin daha yavaş kaldığından emin değilim !
F. Hauri

2
@MiniMax Çünkü doğru sonucu üretmiyor (aynı elemanlar, yanlış sipariş). Dizileri sıkıştırmanız gerekir ${!array[@]}ve ${array[@]}önce bunun çalışması için.
Satō Katsura

1
Bu son pasajı ile pasteolan uzun daha fortek satırda yazılı Söz konusu döngü for i in "${!array[@]}"; do echo "$i=${array[$i]}" ; done, ancak iki altkabuklarda ve harici bir program gerektirir. Bu daha neater? Çözüm pr, çıktıyı sayfalandırmaya çalıştığından çok sayıda öğe varsa da kırılır. | pr -2t -l"${#array[@]}"Basit döngü ile karşılaştırıldığında hatırlanması zorlaşan bir şey kullanmanız gerekir ve yine ondan daha uzun.
ilkkachu

1
In bash, cmd1 veya cmd2 veya her ikisi de yerleşik olsa bile 2 çatal cmd1 | cmd2anlamına gelir .
Stéphane Chazelas

2

Daha iyi ilişkilendirilebilir dizi desteğine sahip bir kabuk arıyorsanız, deneyin zsh.

Olarak zsh(birleştirici diziler bash ksh93 ve 2009, 1993 ile karşılaştırıldığında, 1998 ilave edildi olduğu) $varya da ${(v)var}(boş olmayan) için genişler değerleri karma, ${(k)var}(aynı sıra ile) (boş olmayan) tuşlara, ve ${(kv)var}hem anahtarlara hem de değerlere.

Diziler gibi boş değerleri korumak için @bayrağı alıntılamanız ve kullanmanız gerekir .

Anahtarları ve değerleri yazdırmak için,

printf '%s => %s\n' "${(@kv)var}"

Muhtemelen boş bir karmayı hesaba katarak, şunları yapmalısınız:

(($#var)) &&  printf '%s => %s\n' "${(@kv)var}"

Ayrıca zsh'ın ksh93's (kopyalanan bash) ' dan çok daha mantıklı ve kullanışlı bir dizi tanımı sözdizimi kullandığını unutmayın :

typeset -A var
var=(k1 v1 k2 v2 '' empty '*' star)

Bu, ilişkilendirilebilir dizileri kopyalamayı veya birleştirmeyi çok daha kolay hale getirir:

var2=("${(@kv)var1}")
var3+=("${(@kv)var2}")
var4=("${@kv)var4}" "${(@kv)var5}")

(karmayı bir döngü olmadan kolayca kopyalayamazsınız bashve bashşu anda boş anahtarları veya NUL baytlı anahtar / değerleri desteklemediğini unutmayın ).

Ayrıca zshgenellikle ilişkilendirilebilir dizilerle çalışmanız gereken dizi sıkıştırma özelliklerine de bakın :

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})

1

Yana dizgi sadece kendi çıkışını düzenleyemezsiniz neden istediğini yapar?

typeset -p array | sed s/^.*\(// | tr -d ")\'\""  | tr "[" "\n" | sed s/]=/' = '/

verir

a2 = 2  
a1 = 1  
b1 = bbb 

Nerede

array='([a2]="2" [a1]="1" [b1]="bbb" )'

Ayrıntılı ancak biçimlendirmenin nasıl çalıştığını görmek oldukça kolay: sadece ardışık olarak sed ve tr komutlarıyla boru hattını yürütün . Onları güzel baskı zevklerine uyacak şekilde değiştirin.


Bu tür bir ardışık düzen, dizinin bazı anahtarları veya değerleri, parantez, köşeli parantez veya tırnak işareti gibi değiştirdiğiniz karakterlerden herhangi birini içerdiği anda başarısız olur. Ve seds ve tr's boru hattı, bir fordöngüden daha basit değildir printf.
ilkkachu

Ayrıca, trkarakter karakter çevirmenin dizelerle eşleşmediğini biliyor musunuz? tr "]=" " =""]" Mekan ve değişir =bir karşı =olursa olsun pozisyon. Yani muhtemelen üçünü de bir araya getirebilirsiniz tr.
ilkkachu

Bunu ortaya koyan alfanümerik olmayan karakterlerden bazıları için çok doğru. Bununla birlikte, bunları ele alması gereken her şey, daha karmaşık ve daha az okunabilir bir büyüklük sırasına sahiptir, bu nedenle veri feed'inizde bulunmaları için gerçekten iyi bir neden yoksa ve bu, buraya gelmeden önce filtrelendiklerini varsaydığım soruda belirtildiği gibi. Her zaman açık uyarı tho olmalıdır. Bu boru hatlarını, örneğin mükemmel çalışan veya yüzünüzde patlayan bir printf glob'dan daha basit ve hata ayıklama amaçları olarak görüyorum. Burada , öğe başına basit bir değişiklik yaparsınız , test eder, sonra 1 tane daha eklersiniz.
Nadreck

Benim hatam! _Tr_s ve _sed_s tamamen karıştı! En son düzenlemede düzeltildi.
Nadreck

1

Bir seçenek daha, istediğiniz değişken için tüm değişkenleri ve grep'i listelemektir.

set | grep -e '^aa='

Bunu hata ayıklamak için kullanıyorum. Tüm değişkenleri listelediği için çok performanslı olduğundan şüpheliyim.

Bunu sık sık yapıyorsanız, bunu böyle bir işlev haline getirebilirsiniz:

aap() { set | grep -e "^$1="; }

Ne yazık ki performansı zaman kullanarak kontrol ettiğimizde:

$ time aap aa aa=([0]="abc") . real 0m0.014s user 0m0.003s sys 0m0.006s

Bu nedenle, bunu çok sık yapıyorsanız, @ F.Hauri'nin NO FORKS sürümünü istersiniz çünkü çok daha hızlıdır.

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.