Kenaikan penghitung di loop Bash tidak berfungsi


125

Saya memiliki skrip sederhana berikut di mana saya menjalankan loop dan ingin mempertahankan file COUNTER. Saya tidak dapat mengetahui mengapa penghitung tidak memperbarui. Apakah karena subkulit yang sedang dibuat? Bagaimana cara memperbaikinya?

#!/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


Anda tidak perlu memasukkan while loop ke subkulit. Cukup hapus tanda kurung di sekitar while loop, itu sudah cukup. Atau jika Anda harus memasukkan loop ke dalam subkulit, kemudian setelah beberapa saat selesai, buang penghitung ke file sementara sekali, dan pulihkan file ini di luar subkulit. Saya akan menyiapkan prosedur terakhir untuk Anda sebagai jawaban.
Znik

Jawaban:


156

Pertama, Anda tidak meningkatkan penghitung. Mengubah COUNTER=$((COUNTER))menjadi COUNTER=$((COUNTER + 1))atau COUNTER=$[COUNTER + 1]akan meningkatkannya.

Kedua, lebih sulit untuk memperbanyak variabel subkulit ke callee seperti yang Anda duga. Variabel dalam subkulit tidak tersedia di luar subkulit. Ini adalah variabel lokal untuk proses anak.

Salah satu cara untuk mengatasinya adalah dengan menggunakan file temp untuk menyimpan nilai antara:

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
$ [...] tidak digunakan lagi.
chepner

1
@chepner Apakah Anda memiliki referensi yang mengatakan $[...]usang? Apakah ada solusi alternatif?
blong

9
$[...]digunakan bashsebelum $((...))diadopsi oleh shell POSIX. Saya tidak yakin itu pernah secara resmi tidak digunakan lagi, tetapi saya tidak dapat menemukan menyebutkannya di bashhalaman manual, dan tampaknya hanya didukung untuk kompatibilitas ke belakang.
chepner

Juga, $ (...) lebih disukai daripada...
Lennart Rolland

7
@blong Berikut adalah pertanyaan SO tentang $ [...] vs $ ((...)) yang membahas dan merujuk pada deprecation: stackoverflow.com/questions/2415724/…
Ogre Mazmur33

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

BASH DIUJI: Centos, SuSE, RH


1
@kroonwijk perlu ada spasi sebelum tanda kurung siku (untuk 'membatasi kata-kata', secara formal). Bash tidak bisa melihat akhir dari ekspresi sebelumnya.
EdwardGarson

1
pertanyaannya tentang beberapa saat dengan pipa, jadi di mana subkulit dibuat, jawaban Anda benar tetapi Anda tidak menggunakan pipa sehingga tidak menjawab pertanyaan
chrisweb

2
Menurut komentar chepner di jawaban lain, $[ ]sintaksnya tidak digunakan lagi. stackoverflow.com/questions/10515964/…
Mark Haferkamp

ini tidak menyelesaikan pertanyaan utama, loop utama ditempatkan di bawah subkulit
Znik

42
COUNTER=$((COUNTER+1)) 

adalah konstruksi yang cukup kikuk dalam pemrograman modern.

(( COUNTER++ ))

terlihat lebih "modern". Anda juga bisa menggunakan

let COUNTER++

jika menurut Anda itu meningkatkan keterbacaan. Kadang-kadang, Bash memberikan terlalu banyak cara untuk melakukan sesuatu - filosofi Perl saya kira - ketika mungkin Python "hanya ada satu cara yang benar untuk melakukannya" mungkin lebih tepat. Itu adalah pernyataan yang bisa diperdebatkan jika memang ada! Bagaimanapun, saya akan menyarankan tujuan (dalam kasus ini) tidak hanya untuk menaikkan variabel tetapi (aturan umum) untuk juga menulis kode yang dapat dipahami dan didukung orang lain. Kesesuaian sangat membantu untuk mencapai itu.

HTH


Ini tidak menjawab pertanyaan asli, yaitu bagaimana mendapatkan nilai yang diperbarui di penghitung SETELAH mengakhiri loop (sub-proses)
Luis Vazquez

16

Coba gunakan

COUNTER=$((COUNTER+1))

dari pada

COUNTER=$((COUNTER))

8
atau hanyalet "COUNTER++"
nullpotent

2
Maaf, itu salah ketik. Ini sebenarnya ((COUNTER + 1))
Sparsh Gupta

8
@AaronDigulla: (( COUNTER++ ))(tidak ada tanda dolar)
Dijeda hingga pemberitahuan lebih lanjut.

2
Saya tidak yakin mengapa tetapi saya melihat skrip saya berulang kali gagal saat menggunakan (( COUNTER++ ))tetapi ketika saya beralih ke COUNTER=$((COUNTER + 1))sana berhasil. GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
Steven Lu

Mungkin garis hash bang Anda menjalankan bash sebagai / bin / sh dan bukan / bin / bash?
Maksimal

12

Saya rasa satu panggilan awk ini setara dengan grep|grep|awk|awkpipeline Anda : silakan coba. Perintah awk terakhir Anda tampaknya tidak mengubah apa pun.

Masalah dengan COUNTER adalah perulangan while berjalan dalam subkulit, jadi setiap perubahan pada variabel menghilang ketika subkulit keluar. Anda perlu mengakses nilai COUNTER di subkulit yang sama. Atau ikuti saran @ DennisWilliamson, gunakan substitusi proses, dan hindari subkulit sama sekali.

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
Terima kasih, awk terakhir pada dasarnya akan menghapus semuanya setelah end = 1 dan meletakkan akhir baru = 1 di akhir (sehingga lain kali kita dapat menghapus semua yang ditambahkan setelahnya).
Sparsh Gupta

1
@SparshGupta, awk sebelumnya tidak mencetak apa pun setelah "end = 1".
glenn jackman

Ini sangat bagus untuk meningkatkan skrip pertanyaan, tetapi tidak menyelesaikan masalah dengan peningkatan penghitung di dalam subkulit
Znik


11

Daripada menggunakan file sementara, Anda dapat menghindari pembuatan subkulit di sekitar whileloop dengan menggunakan substitusi proses.

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

Ngomong-ngomong, Anda harus bisa mengubah semua itu grep, grep, awk, awk, awk menjadi satu awk.

Dimulai dengan Bash 4.2, ada file lastpipe opsi itu

menjalankan perintah terakhir dari pipeline dalam konteks shell saat ini. Opsi pipa terakhir tidak berpengaruh jika kontrol pekerjaan diaktifkan.

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

proses substitusi sangat bagus jika Anda ingin menambah penghitung di dalam loop dan menggunakannya di luar ketika selesai, masalah dengan substitusi proses adalah saya tidak menemukan cara untuk juga mendapatkan kode status dari perintah yang dieksekusi, yang dimungkinkan saat menggunakan pipa dengan menggunakan $ {PIPESTATUS [*]}
chrisweb

@chrisweb: Saya menambahkan informasi tentang lastpipe. Omong-omong, Anda mungkin harus menggunakan "${PIPESTATUS[@]}"(di alih-alih tanda bintang).
Dijeda sampai pemberitahuan lebih lanjut.

ralat. di bash (bukan di perl karena saya tidak sengaja menulis sebelumnya) kode keluar adalah tabel, lalu Anda dapat memeriksa secara terpisah semua kode keluar dalam rantai pipa. sebelum pengujian pertama langkah Anda harus menyalin tabel ini, jika tidak setelah perintah pertama Anda akan kehilangan semua nilai.
Znik

Ini adalah solusi yang berhasil untuk saya dan tanpa menggunakan file eksternal untuk menyimpan nilai variabel yang menurut saya terlalu pejalan kaki.
Luis Vazquez

8

minimalis

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

Sederhana :-). Terima kasih @geekzspot
Hussain K

tidak bekerja misalnya yang dimaksud, karena ada subkulit
Znik

3

Ini semua yang perlu Anda lakukan:

$((COUNTER++))

Berikut kutipan dari Learning the bash Shell , Edisi ke-3, hlm. 147, 148:

ekspresi aritmatika bash setara dengan rekan-rekan mereka dalam bahasa Java dan C. [9] Presedensi dan asosiativitas sama seperti pada C. Tabel 6-2 menunjukkan operator aritmatika yang didukung. Meskipun beberapa di antaranya adalah (atau berisi) karakter khusus, tidak perlu meloloskan diri dari garis miring terbalik, karena mereka berada di dalam sintaks $ ((...)).

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

Operator ++ dan - berguna saat Anda ingin menambah atau mengurangi nilai satu per satu. [11] Mereka bekerja sama seperti di Java dan C, misalnya, nilai ++ menambah nilai sebesar 1. Ini disebut kenaikan pasca ; ada juga pra-increment : ++ nilai . Perbedaannya menjadi jelas dengan contoh:

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

Lihat http://www.safaribooksonline.com/a/learning-the-bash/7572399/


Ini adalah versi yang saya butuhkan, karena saya menggunakannya dalam kondisi ifpernyataan: if [[ $((needsComma++)) -gt 0 ]]; then printf ',\n'; fi Benar atau salah, ini adalah satu-satunya versi yang bekerja dengan andal.
LS

Yang penting tentang formulir ini adalah Anda dapat menggunakan selisih dalam satu langkah. i=1; while true; do echo $((i++)); sleep .1; done
Bruno Bronosky

1
@LS: if (( needsComma++ > 0 )); thenatauif (( needsComma++ )); then
Dijeda hingga pemberitahuan lebih lanjut.

Menggunakan "echo $ ((i ++))" di bash saya selalu mendapatkan "/opt/xyz/init.sh: baris 29: i: perintah tidak ditemukan" Apa yang saya lakukan salah?
mmo

Ini tidak menjawab pertanyaan tentang mendapatkan nilai penghitung di luar loop.
Luis Vazquez

1

Ini adalah contoh sederhana

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

1
contoh sederhana, tetapi tidak berlaku untuk pertanyaan.
Znik

0

Tampaknya Anda tidak memperbarui counterskrip is, gunakancounter++


Maaf atas kesalahan ketik, saya sebenarnya menggunakan ((COUNTER + 1)) dalam skrip yang tidak berfungsi
Sparsh Gupta

tidak peduli itu incremetted oleh nilai + 1, atau dengan nilai ++. Setelah subkulit berakhir, nilai penghitung hilang, dan kembali ke nilai awal 0 yang ditetapkan di awal skrip ini.
Znik

0

Ada dua kondisi yang menyebabkan ekspresi ((var++))gagal untuk saya:

  1. Jika saya menyetel bash ke mode ketat ( set -euo pipefail) dan jika saya memulai kenaikan saya dari nol (0).

  2. Memulai dari satu (1) tidak masalah, tetapi nol menyebabkan kenaikan menghasilkan "1" saat mengevaluasi "++" yang merupakan kegagalan kode pengembalian bukan nol dalam mode ketat.

Saya bisa menggunakan ((var+=1))atau var=$((var+1))menghindari perilaku ini


0

Skrip sumber memiliki masalah dengan subkulit. Contoh pertama, Anda mungkin tidak membutuhkan subkulit. Tapi Kami tidak tahu apa yang tersembunyi di bawah "Beberapa tindakan lagi". Jawaban paling populer memiliki bug tersembunyi, yang akan meningkatkan I / O, dan tidak akan berfungsi dengan subkulit, karena memulihkan couter di dalam loop.

Jangan menambahkan tanda '\', ini akan menginformasikan juru bahasa bash tentang kelanjutan baris. Saya harap ini akan membantu Anda atau siapa pun. Tapi menurut saya skrip ini harus sepenuhnya dikonversi ke skrip AWK, atau ditulis ulang ke python menggunakan regexp, atau perl, tetapi popularitas perl selama bertahun-tahun menurun. Lebih baik lakukan dengan python.

Versi yang Dikoreksi tanpa subkulit:

#!/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

Versi dengan subkulit jika benar-benar dibutuhkan

#!/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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.