Bash'de bir komutun çıktısını bir diziye okuma


111

Komut dosyamdaki bir komutun çıktısını bir diziye okumam gerekiyor. Komut, örneğin:

ps aux | grep | grep | x 

ve çıktıyı şu şekilde satır satır verir:

10
20
30

Komut çıktısındaki değerleri bir diziye okumam gerekiyor ve ardından dizinin boyutu üçten küçükse bazı işler yapacağım.


5
Hey @barp, SORULARINIZI CEVAPLAYIN, türünüzün tüm topluluk için bir yük oluşturmaması için.
James

9
@James sorunu onun sorusunu cevaplamamasıyla ilgili değil ... bu bir Soru / Cevap sitesi. O sadece vermedi işaretlemek cevap olarak. Onları işaretlemeli. İpucu. @ barp
DDPWNAGE

4
Lütfen @barp, soruyu yanıtlandı olarak işaretleyin.
smonff

İlgili: İşlem değiştirme yoluyla bir komutun çıktısını okumak, bir dosyadan okumaya benzer olduğundan Bash'deki bir dosyanın içeriğinde döngü yapmak .
codeforester

Yanıtlar:


162

Diğer cevaplar komutunun çıktısı boşluk içeriyorsa (oldukça sık olan) kırmak ya da benzeri karakterler topak olacaktır *, ?, [...].

Öğe başına bir satır olacak şekilde dizideki bir komutun çıktısını elde etmek için esasen 3 yol vardır:

  1. Bash≥4 kullanımıyla mapfile— bu en verimli:

    mapfile -t my_array < <( my_command )
  2. Aksi takdirde, çıktıyı okuyan bir döngü (daha yavaş, ancak güvenli):

    my_array=()
    while IFS= read -r line; do
        my_array+=( "$line" )
    done < <( my_command )
  3. Charles Duffy'nin yorumlarda önerdiği gibi (teşekkürler!), Aşağıdakiler 2 numaralı döngü yönteminden daha iyi performans gösterebilir:

    IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )

    Lütfen tam olarak bu formu kullandığınızdan emin olun, yani aşağıdakilere sahip olduğunuzdan emin olun:

    • IFS=$'\n' aynı satırda readaçıklamada: bu sadece ortam değişkeni koyacaktır IFS için readdeyimi sadece. Yani betiğinizin geri kalanını hiç etkilemeyecek. Bu değişkenin amacı read, akışı EOL karakterinde kesmeyi söylemektir \n.
    • -r: bu önemli. Ters eğik çizgileri kaçış dizileri olarak yorumlamamayı söyler read .
    • -d '': Lütfen -dseçenek ve argümanı arasındaki boşluğa dikkat edin ''. Burada boşluk bırakmazsanız, Bash ifadeyi ayrıştırdığında alıntı kaldırma adımında ''kaybolacağı için hiçbir zaman görülmez . Bu sıfır baytta okumayı durdurmanızı söyler . Bazıları bunu şöyle yazıyor , ama gerçekten gerekli değil. daha iyi.read-d $'\0'-d ''
    • -a my_arrayakışı okurken readdiziyi doldurmayı söyler my_array.
    • Sen kullanmalıdır printf '\0'deyimi sonra my_command böylece readgetiri 0; Bunu yapmazsanız aslında önemli bir şey değil (sadece bir dönüş kodu alırsınız 1, ki bu kullanmazsanız sorun olmaz set -e- ki yine de yapmamalısınız), ancak bunu aklınızda bulundurun. Daha temiz ve anlamsal olarak daha doğru. Bunun, printf ''herhangi bir çıktı vermeyen durumdan farklı olduğunu unutmayın . orada okumayı mutlu bir şekilde durdurmak için printf '\0'gereken boş bir bayt yazdırır read( -d ''seçeneği hatırlıyor musunuz?).

Yapabiliyorsanız, yani kodunuzun Bash≥4 üzerinde çalışacağından eminseniz, ilk yöntemi kullanın. Ve daha kısa olduğunu da görebilirsiniz.

Kullanmak istiyorsanız read, satırlar okunurken bazı işlemler yapmak istiyorsanız, döngü (yöntem 2) yöntem 3'e göre bir avantaja sahip olabilir: ona doğrudan erişiminiz var ( $lineverdiğim örnekteki değişken aracılığıyla ) ve zaten okunan satırlara da erişebilirsiniz ( ${my_array[@]}verdiğim örnekteki dizi aracılığıyla ).

Not mapfileHer satırda eval'd bir geri arama için bir yol sağlar okumak ve aslında hatta sadece her Bu geri arama aramak için bunu söyleyebilirim N hatları okumak; help mapfileseçeneklere -Cve -coraya bir göz atın . (Bu konudaki fikrim biraz hantal, ancak bazen sadece yapacak basit işleriniz varsa kullanılabilir - bunun neden ilk başta uygulandığını gerçekten anlamıyorum!).


Şimdi size neden aşağıdaki yöntemi anlatacağım:

my_array=( $( my_command) )

boşluk olduğunda bozulur:

$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!

Sonra bazı insanlar IFS=$'\n'düzeltmek için kullanmanızı tavsiye edecek :

$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!

Ama şimdi kürelerle başka bir komut kullanalım :

$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?

Bunun nedeni t, geçerli dizinde çağrılan bir dosyam olması… ve bu dosya adı glob ile eşleşiyor [three four]… bu noktada bazı insanlar set -fgenellemeyi devre dışı bırakmak için kullanmanızı tavsiye eder: ama buna bakın: düzeltmek için değiştirmeniz IFSve kullanmanız set -fgerekir. kırık teknik (ve bunu gerçekten düzeltmiyorsunuz bile)! bunu yaparken gerçekten kabuğa karşı savaşıyoruz , kabukla çalışmıyoruz .

$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'

burada kabuk ile çalışıyoruz!


4
Bu harika, daha mapfileönce hiç duymadım , tam olarak yıllardır özlediğim şey bu. Sanırım son sürümlerinde bashçok güzel yeni özellikler var, sadece birkaç günümü dokümanları okuyup güzel bir kopya kağıdı yazarak geçirmeliyim.
Gene Pavlovsky

6
Btw, bu sözdizimini < <(command)kabuk betiklerinde kullanmak için , shebang satırı #!/bin/bash- eğer çalıştırılırsa #!/bin/sh, bash bir sözdizimi hatasıyla çıkacaktır.
Gene Pavlovsky

1
@ GenePavlovsky'nin faydalı notunu genişleterek, komut dosyası bash my_script.shsh komutu ile değil bash komutuyla da çalıştırılmalıdırsh my_script.sh
Vito

2
@Vito: Aslında, bu yanıt yalnızca Bash içindir, ancak bu bir sorun olmamalıdır, çünkü kesinlikle uyumlu POSIX kabukları dizileri bile uygulamaz ( shve dashdiziler hakkında hiçbir şey bilmiyorlar, tabii ki konumsal parametreler $@dizisi).
gniourf_gniourf

3
Bash 4,0 gerektiren dikkate almaz başka alternatif olarak IFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0')doğru bash 3.x her iki çalışır ve ayrıca gelen başarısız bir çıkış durumuna geçer - my_commandiçin read.
Charles Duffy

86

Kullanabilirsiniz

my_array=( $(<command>) )

komutun çıktısını <command>diziye depolamak için my_array.

Bu dizinin uzunluğuna şunu kullanarak erişebilirsiniz:

my_array_length=${#my_array[@]}

Şimdi uzunluk saklanır my_array_length.


19
Ya $ (command) 'ın çıktısında boşluklar ve boşluklu birden çok satır varsa? "$ (Komut)" ekledim ve tüm satırların tüm çıktılarını dizinin ilk [0] öğesine yerleştirir.
ikwyl6

3
@ ikwyl6 bir geçici çözüm, komut çıktısını bir değişkene atamak ve ardından onunla bir dizi oluşturmak veya bir diziye eklemektir. VAR="$(<command>)"ve sonra my_array=("$VAR")veyamy_array+=("$VAR")
Vito

10

Dosyaları ve dizin adlarını (geçerli klasörün altında) bir diziye koyacağınızı ve öğelerini sayacağınızı hayal edin. Senaryo şöyle olacaktır;

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

Veya aşağıdaki komut dosyasını ekleyerek bu dizi üzerinde yineleme yapabilirsiniz:

for element in "${my_array[@]}"
do
   echo "${element}"
done

Lütfen bunun temel kavram olduğunu ve girdinin daha önce sterilize edilmiş olarak kabul edildiğini unutmayın, yani fazladan karakterleri kaldırmak, boş Dizeleri ele almak, vb. (Bu konu bu konunun dışında).


3
Yukarıdaki cevapta bahsedilen nedenlerden dolayı korkunç fikir
Hubert Grzeskowiak
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.