bash: değişken okuma döngüsü sonunda değerini kaybeder


36

Shell komut dosyalarımdan birinde bir sorunum var. Birkaç meslektaşına sordum, ancak hepsi sadece başlarını salladı (bir miktar tırmalamadan sonra), bu yüzden buraya bir cevap için geldim.

Anladığım kadarıyla aşağıdaki kabuk betiği son satır olarak "Sayı 5" dir. Dışında değil. "Sayı 0" yazıyor. Eğer "okunurken" başka bir döngü ile değiştirildiyse, iyi çalışır. İşte senaryo:

echo "1"> input.data
echo "2" >> input.data
echo "3" >> input.data
echo "4" >> input.data
echo "5" >> input.data

CNT = 0 

cat input.data | okurken;
yap
  CNT ++ 'a izin verin;
  echo "$ CNT'ye sayıyor"
tamam 
yankı "Sayı $ CNT"

Bu neden oluyor ve nasıl önleyebilirim? Bunu Debian Lenny ve Squeeze'de denedim, aynı sonuç (örn. Bash 3.2.39 ve bash 4.1.5. Bir kabuk betiği sihirbazı olmadığına tamamen itiraf ediyorum, bu yüzden herhangi bir işaretçi takdir edilebilir).

Yanıtlar:


30

Bkz. Argüman @ Bash SSS girişi # 24: "Değişkenleri bir döngüde ayarlıyorum. Döngü sona erdikten sonra neden aniden kayboluyorlar? Veya, neden verileri okumak için veri iletemiyorum?" (en son burada arşivlendi ).

Özet: Bu yalnızca bash 4.2 ve üstü sürümlerde desteklenir. Bash kullanıyorsanız, pipo yerine komut yerine kullanmanız gibi farklı yollar kullanmanız gerekir.


Bonusu alıyorsunuz, çünkü cevabınız bana en geniş seçenek yelpazesini sağladı.
wolfgangsz

5
Bağlantı öldü. Bu nedenle sadece bağlantı cevapları kötüdür. En azından buradaki cevabı özetle.
rudolfbyker

Tanrım, ksh'nin çok daha iyi olduğu bir başka zaman ... neden, herkes neden bash etrafında aktı?
Florian Heigl

@ FlorianHeigl: ksh'ın One True Shell olduğunu mu iddia ediyorsunuz?
Ignacio Vazquez-Abrams,

@ IgnacioVazquez-Abrams hayır, ancak bash'ta döngü işleme sırasında korkunç bir PITA olduğunu iddia ediyorum. Döngü kullanımı, 1993’lerin işlevselliğini yakalamaktan uzak tutan uzun yolcuydu. Diğer şeyler, (ayrıca 1993) yerleşik işleyicinin basit ve yetenekli olduğu getopt işlemesidir, yani kullanmadığınız sürece hala elde edemeyeceğiniz bir şey. Bash'ın 20 yıldan fazla bir süredir ısrarla kendini eğrinin ardına koyduğunu ve BURADA BU ŞEY ya da milyonlarca kötü getopt kullandığı zamanın ölçünün ötesinde olduğunu iddia ediyorum - ancak çoğu insan asla bilmeyecek.
Florian Heigl

30

Bu bir tür 'yaygın' hata. Borular SubShells oluşturur, bu nedenle while readbetiğinizden farklı bir kabuk üzerinde çalışır, bu da CNTdeğişkeninizi asla değiştirmez (yalnızca boru alt kabuğunun içindeki).

Grup geçen echoalt kabuğa sahip whiledüzeltmek için (bunu düzeltmek için birçok yolu vardır, bu bir. Iain'ın ve Ignacio'nun cevapları diğerleri var.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Uzun açıklama:

  1. CNTSenaryonuzda 0 değer olduğunu beyan ediyorsunuz;
  2. Bir kabuktaki üzerinde başlatılır |için while read;
  3. Kişisel $CNTdeğişken değeri 0 ile altkabuk ihraç edilmektedir;
  4. SubShell sayar ve CNTdeğeri 5'e yükseltir ;
  5. SubShell sona erer, değişkenler ve değerler imha edilir (çağrı sürecine / komut dosyasına geri dönemezler).
  6. Sen echoOrijinal CNT0 değeri.

2
Yazdığım ilk kabuk senaryosu da bana aynı sorunları verdi, bu boruların ek kabuklar ürettiğini öğrenmeden önce bir süre kafamı duvara çarptı. Bir boruya karışacağınız herhangi bir değişken, boru sona erdiğinde derhal kapsam dışına çıkar - yani gerçekten, gerçekten kullanılan borunun dışında bir değişkenle bir şey yapmak istiyorsanız, bunu yapmanız gerekir. geçici bir dosya gibi korkak bir şeyle devlet tutun.
17'de

Mükemmel cevap, ne yazık ki sadece bir kabul bonusu verebilirim. Üzgünüm.
wolfgangsz

10

Bu çalışıyor

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

Bunu seviyorum, zekice bir yol çünkü gerekli verilerin nerede olduğunu biliyorsunuz ve sadece geri almanız gerekiyor. Yüksek beceri çözümlerini bilmiyorsanız, her zaman "bir dosyayı okuyabilir" hahahha. +1 sizin için.
erm3nda

1
Bunu okuyan herkes, Iain tarafından sağlanan çözümün yalnızca senaryonuzu açıkça bash çağırdığında, ilk satırı yazarak çalıştığını unutmayın: #! / Bin / bash ve şu: #! / Bin / sh çalışmayacak.
Roadowl

1
İlginç, ilk örnek, Yararsız Cat Kullanımının gerçekten kodun çalışmasını engellediğini gördüm . @Roadowl bu arada, buradaki tek basmizm POSIX uyumlu aritmetik genişlemeden yararlanmak için let CNT++olması gereken satır . Gerisi zaten taşınabilir. CNT="$((CNT+1))"
Joker

6

Verileri, bir döngüden önce bir dosya gibi, bunun yerine bir alt kabuğa geçirmeyi deneyin. Bu lain çözümüne benzer, ancak bazı aralıklı dosyalar istemediğinizi varsayar:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
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.