Bash döngüsündeki sayaç artışı çalışmıyor


125

Bir döngü çalıştırdığım ve bir COUNTER. Sayacın neden güncellenmediğini anlayamıyorum. Yaratılan alt kabuk yüzünden mi? Bunu potansiyel olarak nasıl düzeltebilirim?

#!/bin/bash

WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' | awk -F ', ' '{print $2,$4,$0}' | awk '{print "http://domain.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' | awk -F '&end=1' '{print $1"&end=1"}' |
(
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
)

echo $COUNTER # output = 0


Alt kabuğa dönmeye gerek yok. Döngü sırasında etrafındaki parantezleri kaldırmanız yeterlidir, bu yeterlidir. Ya da alt kabuğa döngüye sokmanız gerekiyorsa, daha sonra bittikten sonra, sayacı bir kez geçici dosyaya boşaltın ve bu dosyayı alt kabuğun dışına geri yükleyin. Size cevap olarak son prosedürü hazırlayacağım.
Znik

Yanıtlar:


156

Birincisi, sayacı artırmıyorsunuz. Değişen COUNTER=$((COUNTER))içine COUNTER=$((COUNTER + 1))veya COUNTER=$[COUNTER + 1]onu artacaktır.

İkinci olarak, tahmin ettiğiniz gibi, alt kabuk değişkenlerini aranan uca geri yaymak daha zordur. Alt kabuktaki değişkenler alt kabuğun dışında kullanılamaz. Bunlar, alt süreç için yerel değişkenlerdir.

Bunu çözmenin bir yolu, ara değeri depolamak için geçici bir dosya kullanmaktır:

TEMPFILE=/tmp/$$.tmp
echo 0 > $TEMPFILE

# Loop goes here
  # Fetch the value and increase it
  COUNTER=$[$(cat $TEMPFILE) + 1]

  # Store the new value
  echo $COUNTER > $TEMPFILE

# Loop done, script done, delete the file
unlink $TEMPFILE

30
$ [...] kullanımdan kaldırıldı.
chepner

1
@chepner Kullanımdan $[...]kaldırıldı diyen bir referansınız var mı? Alternatif bir çözüm var mı?
blong

9
$[...]POSIX kabuğu tarafından daha bashönce $((...))benimsenmişti. Resmi olarak kullanımdan kaldırıldığından emin değilim, ancak bashman sayfasında bundan bahsedemiyorum ve yalnızca geriye dönük uyumluluk için destekleniyor gibi görünüyor.
chepner

Ayrıca, $ [...) tercih edilir...
Lennart Rolland

7
@blong İşte kullanımdan kaldırmayı tartışan ve referans veren $ [...] ile $ ((...)) arasındaki bir SO sorusu: stackoverflow.com/questions/2415724/…
Ogre Mezmur33

87
COUNTER=1
while [ Your != "done" ]
do
     echo " $COUNTER "
     COUNTER=$[$COUNTER +1]
done

TEST EDİLMİŞ BASH: Centos, SuSE, RH


1
@kroonwijk köşeli parantezin önünde bir boşluk olması gerekir (resmi olarak 'kelimeleri sınırlandırmak için'). Bash, aksi takdirde önceki ifadenin sonunu göremez.
EdwardGarson

1
sorular bir süredir boru ile ilgiliydi, bu nedenle bir alt kabuğun oluşturulduğu yerde cevabınız doğrudur ancak boru kullanmıyorsunuz, bu yüzden soruyu cevaplamıyor
chrisweb

2
Chepner'ın başka bir cevap hakkındaki yorumuna göre, $[ ]sözdizimi kullanımdan kaldırılmıştır. stackoverflow.com/questions/10515964/…
Mark Haferkamp

bu ana soruyu çözmez, ana döngü alt kabuğun altına yerleştirilir
Znik

42
COUNTER=$((COUNTER+1)) 

modern programlamada oldukça beceriksiz bir yapıdır.

(( COUNTER++ ))

daha "modern" görünüyor. Ayrıca kullanabilirsiniz

let COUNTER++

Okunabilirliği artırdığını düşünüyorsanız. Bazen, Bash bir şeyler yapmanın çok fazla yolunu verir - Perl felsefesi - belki de Python "bunu yapmanın tek bir doğru yolu vardır" daha uygun olabilir. Varsa bu tartışmalı bir ifade! Her neyse, amacın (bu durumda) sadece bir değişkeni artırmak değil, (genel kural) başka birinin anlayabileceği ve destekleyebileceği bir kod yazmak olduğunu öneriyorum. Uygunluk, bunu başarmak için uzun bir yol kat ediyor.

HTH


Bu, (alt süreç) döngüsünü sonlandırdıktan SONRA sayaçta güncellenmiş d değerinin nasıl alınacağı orijinal soruyu ele almıyor
Luis Vazquez

16

Kullanmaya çalışmak

COUNTER=$((COUNTER+1))

onun yerine

COUNTER=$((COUNTER))

8
ya da sadecelet "COUNTER++"
nullpotent

2
Üzgünüm, bir Yazım hatasıydı. Aslında ((SAYAÇ + 1))
Sparsh Gupta

8
@AaronDigulla: (( COUNTER++ ))(dolar işareti yok)
sonraki duyuruya kadar duraklatıldı.

2
Neden olduğundan emin değilim, (( COUNTER++ ))ancak kullanırken bir komut dosyamın defalarca başarısız olduğunu görüyorum, ancak COUNTER=$((COUNTER + 1))ona geçtiğimde işe yaradı. GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
Steven Lu

Belki de hash patlama çizginiz / bin / bash yerine / bin / sh olarak bash çalıştırır?
Maksimum

12

Sanırım bu tek awk çağrısı sizin grep|grep|awk|awk ardışık : lütfen test edin. Son awk komutunuz hiçbir şeyi değiştirmiyor gibi görünüyor.

COUNTER ile ilgili sorun, while döngüsünün bir alt kabukta çalışmasıdır, bu nedenle değişkendeki herhangi bir değişiklik, alt kabuk çıkıldığında kaybolur. Aynı alt kabukta COUNTER değerine erişmeniz gerekir. Veya @ DennisWilliamson'ın tavsiyesine uyun, bir işlem ikamesi kullanın ve alt kabuktan tamamen kaçının.

awk '
  /GET \/log_/ && /upstream timed out/ {
    split($0, a, ", ")
    split(a[2] FS a[4] FS $0, b)
    print "http://example.com" b[5] "&ip=" b[2] "&date=" b[7] "&time=" b[8] "&end=1"
  }
' | {
    while read WFY_URL
    do
        echo $WFY_URL #Some more action
        (( COUNTER++ ))
    done
    echo $COUNTER
}

1
Teşekkürler, son awk temelde bitiş = 1 sonrasındaki her şeyi kaldıracak ve sonuna yeni bir uç = 1 koyacaktır (böylece bir dahaki sefere ondan sonra eklenen her şeyi kaldırabiliriz).
Sparsh Gupta

1
@SparshGupta, önceki awk "end = 1" den sonra hiçbir şey yazmaz.
glenn jackman

Bu, soru komut dosyası için çok iyi bir gelişme sağlar, ancak alt kabuk içindeki sayacın artmasıyla ilgili sorunu
çözmez


11

Geçici bir dosya kullanmak yerine, whilesüreç ikamesi kullanarak döngü etrafında bir alt kabuk oluşturmaktan kaçınabilirsiniz .

while ...
do
   ...
done < <(grep ...)

Bu arada, hepsini grep, grep, awk, awk, awktek bir kişiye dönüştürebilmelisin awk.

Bash 4.2'den başlayarak, bir lastpipeseçenek vardır:

mevcut kabuk bağlamında bir işlem hattının son komutunu çalıştırır. İş kontrolü etkinleştirilirse, son boru seçeneğinin hiçbir etkisi yoktur.

bash -c 'echo foo | while read -r s; do c=3; done; echo "$c"'

bash -c 'shopt -s lastpipe; echo foo | while read -r s; do c=3; done; echo "$c"'
3

İşlem ikamesi, döngü içindeki bir sayacı artırmak ve bittiğinde onu dışarıda kullanmak istiyorsanız harikadır, işlem ikameleriyle ilgili sorun, yürütülen komutun durum kodunu da almanın bir yolunu bulamamamdır, ki bu bir boru kullanırken mümkündür $ {PIPESTATUS [*]} kullanarak
chrisweb

@chrisweb: hakkında bilgiler ekledim lastpipe. Bu arada, muhtemelen kullanmalısınız "${PIPESTATUS[@]}"(yıldız yerine at).
sonraki duyuruya kadar duraklatıldı.

doğrularını. bash'de (önceden yanlışlıkla yazdığım için perl'de değil) çıkış kodu bir tablodur, daha sonra boru zincirindeki tüm çıkış kodlarını ayrı ayrı kontrol edebilirsiniz. Test etmeden önce adımınız bu tabloyu kopyalamak olmalıdır, aksi takdirde ilk komuttan sonra tüm değerleri kaybedersiniz.
Znik

Bu, benim için işe yarayan ve değişkenin değerini saklamak için harici bir dosya kullanmadan, benim görüşüme göre çok yaya olan bir çözüm.
Luis Vazquez

8

minimalist

counter=0
((counter++))
echo $counter

Basit olan :-). Teşekkürler @geekzspot
Hussain K

örneğin söz konusu çalışmıyor, çünkü alt kabuk var
Znik

3

Tek yapmanız gereken bu:

$((COUNTER++))

İşte bash Kabuğunu Öğrenmek , 3. Baskı, s. 147, 148'den bir alıntı :

bash aritmetik ifadeleri, Java ve C dillerindeki muadillerine eşdeğerdir. [9] Öncelik ve ilişkilendirilebilirlik C'deki ile aynıdır. Tablo 6-2, desteklenen aritmetik operatörleri göstermektedir. Bunlardan bazıları özel karakterler olsa da (veya içerse), onlardan ters eğik çizgiden kaçınmaya gerek yoktur, çünkü bunlar $ ((...)) sözdizimi içindedir.

..........................

++ ve - operatörleri, bir değeri birer birer artırmak veya azaltmak istediğinizde kullanışlıdır. [11] Java ve C'de olduğu gibi çalışırlar, örneğin değer ++ , değeri 1 artırır . Buna artım sonrası denir ; ayrıca bir ön artış vardır : ++ değeri . Fark, bir örnekle açıkça ortaya çıkıyor:

$ i=0
$ echo $i
0
$ echo $((i++))
0
$ echo $i
1
$ echo $((++i))
2
$ echo $i
2

Bkz. Http://www.safaribooksonline.com/a/learning-the-bash/7572399/


İhtiyacım olan versiyon budur, çünkü onu bir ififade koşulunda kullanıyordum : if [[ $((needsComma++)) -gt 0 ]]; then printf ',\n'; fi Doğru veya yanlış, güvenilir bir şekilde çalışan tek versiyon budur.
LS

Bu formla ilgili önemli olan şey, tek bir adımda artım kullanabilmenizdir. i=1; while true; do echo $((i++)); sleep .1; done
Bruno Bronosky

1
@LS: if (( needsComma++ > 0 )); thenveyaif (( needsComma++ )); then
sonraki duyuruya kadar duraklatıldı.

Bash'de "echo $ ((i ++))" kullanarak her zaman "/opt/xyz/init.sh: satır 29: i: komut bulunamadı" Neyi yanlış yapıyorum?
mmo

Bu, sayaç değerini döngünün dışına alma sorusuna hitap etmez.
Luis Vazquez

1

Bu basit bir örnek

COUNTER=1
for i in {1..5}
do   
   echo $COUNTER;
   //echo "Welcome $i times"
   ((COUNTER++));    
done

1
basit bir örnek, ancak sorgulanamaz.
Znik

0

Görünüşe göre counterkomut dosyasını güncellemediniz , kullanıncounter++


Yazım hatası için özür dilerim, aslında çalışmayan komut dosyasında ((SAYAÇ + 1)) kullanıyorum
Sparsh Gupta

+ 1 değeri veya ++ değeri ile artırılmasının önemi yoktur. Alt kabuk sona erdikten sonra, sayaç değeri kaybolur ve bu komut dosyasının başlangıçta ayarlanan ilk 0 değerine geri döner.
Znik

0

İfadenin ((var++))benim için başarısız olmasına neden olan iki koşul vardı :

  1. Ben ise sıkı moda bash set ( set -euo pipefail) ve benim başlarsanız sıfır artışı (0).

  2. Bir (1) ile başlamak iyidir ancak sıfır, katı modda sıfır olmayan bir dönüş kodu hatası olan "++" değerini değerlendirirken artışın "1" döndürmesine neden olur.

Bu davranışı ya kullanabilirim ((var+=1))ya var=$((var+1))da kaçabilirim


0

Kaynak komut dosyasının alt kabukla ilgili bazı sorunları var. İlk örnek, muhtemelen alt kabuğa ihtiyacınız yoktur. Ama "Biraz daha eylem" altında neyin saklı olduğunu bilmiyoruz. En popüler yanıt, I / O'yu artıracak ve alt kabukla çalışmayacak, çünkü döngü içindeki kodlayıcıyı geri yüklediği için gizli hata içeriyor.

'\' İşaretini eklemeyi unutmayın, bu satırın devamı hakkında bash yorumlayıcısını bilgilendirecektir. Umarım size veya herhangi birine yardımcı olur. Ama bana göre bu komut dosyası tamamen AWK betiğine dönüştürülmeli veya regexp veya perl kullanılarak python'a yeniden yazılmalıdır, ancak perl popülaritesi yıllar içinde azalmıştır. Python ile yapsan iyi olur.

Alt kabuksuz Düzeltilmiş Sürüm:

#!/bin/bash
WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' |\
awk -F ', ' '{print $2,$4,$0}' |\
awk '{print "http://example.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' |\
awk -F '&end=1' '{print $1"&end=1"}' |\
#(  #unneeded bracket
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
# ) unneeded bracket

echo $COUNTER # output = 0

Gerçekten gerekliyse alt kabuklu versiyon

#!/bin/bash

TEMPFILE=/tmp/$$.tmp  #I've got it from the most popular answer
WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' |\
awk -F ', ' '{print $2,$4,$0}' |\
awk '{print "http://example.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' |\
awk -F '&end=1' '{print $1"&end=1"}' |\
(
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
echo $COUNTER > $TEMPFILE  #store counter only once, do it after loop, you will save I/O
)

COUNTER=$(cat $TEMPFILE)  #restore counter
unlink $TEMPFILE
echo $COUNTER # output = 0
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.