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
Sparsh Gupta
sumber
1
Terkait: stackoverflow.com/questions/13726764/…
Gabriel Devillers
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
bos
sumber
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

Jay Stan
sumber
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

Bill Parker
sumber
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))
dbf
sumber
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
}
glenn jackman
sumber
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
12
count=0   
base=1
(( count += base ))
pkm
sumber
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
Dijeda sampai pemberitahuan lebih lanjut.
sumber
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
geekzspot
sumber
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/

CE Montijo
sumber
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
zwitterion
sumber
1
contoh sederhana, tetapi tidak berlaku untuk pertanyaan.
Znik
0

Tampaknya Anda tidak memperbarui counterskrip is, gunakancounter++

yjshen
sumber
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

Augustus Hill
sumber
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
Znik
sumber