bash: variabel kehilangan nilai di akhir while read loop


36

Saya memiliki masalah di salah satu skrip shell saya. Tanya beberapa rekan, tetapi mereka semua hanya menggelengkan kepala (setelah beberapa goresan), jadi saya datang ke sini untuk jawaban.

Menurut pemahaman saya, skrip shell berikut harus mencetak "Count is 5" sebagai baris terakhir. Kecuali tidak. Mencetak "Hitung 0". Jika "while read" diganti dengan jenis loop lain, itu berfungsi dengan baik. Ini skripnya:

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

CNT = 0 

cat input.data | saat membaca;
melakukan
  biarkan CNT ++;
  echo "Menghitung $ CNT"
selesai 
gema "Hitung adalah $ CNT"

Mengapa ini terjadi dan bagaimana saya bisa mencegahnya? Saya sudah mencoba ini di Debian Lenny dan Squeeze, hasil yang sama (yaitu bash 3.2.39 dan bash 4.1.5. Saya sepenuhnya mengaku tidak menjadi penyihir skrip shell, jadi petunjuk apa pun akan dihargai.

Jawaban:


30

Lihat argumen @ Bash FAQ entri # 24: "Saya mengatur variabel dalam satu lingkaran. Mengapa mereka tiba-tiba menghilang setelah loop berakhir? Atau, mengapa saya tidak bisa mem-pipe data untuk dibaca?" (paling baru diarsipkan di sini ).

Ringkasan: Ini hanya didukung dari bash 4.2 dan lebih tinggi. Anda perlu menggunakan berbagai cara seperti penggantian perintah daripada pipa jika Anda menggunakan bash.


Anda mendapatkan bonus, karena jawaban Anda memberi saya berbagai pilihan terluas.
wolfgangsz

5
Tautannya sudah mati. Inilah mengapa jawaban hanya tautan buruk. Setidaknya rangkum jawabannya di sini.
rudolfbyker

Ya Tuhan, lain kali di mana ksh jauh lebih baik ... mengapa, mengapa semua orang berbondong-bondong di bash.
Florian Heigl

@FlorianHeigl: Apakah Anda mengklaim bahwa ksh adalah One True Shell?
Ignacio Vazquez-Abrams

@ IgnacioVazquez-Abrams tidak, tetapi saya mengklaim bahwa penanganan loop sementara di bash adalah PITA yang mengerikan. Penanganan loop adalah pelari jangka panjang yang mencegahnya menangkap fungsi tahun 1993. Hal-hal lain adalah penanganan getopt di mana handler handler (juga 1993) sederhana dan mampu, sesuatu yang Anda masih tidak bisa dapatkan kecuali menggunakan ie docopt. Saya mengklaim bahwa bash telah menempatkan dirinya di belakang kurva selama lebih dari 20 tahun, secara terus-menerus dan jumlah waktu yang dihabiskan untuk HAL INI DI SINI atau jutaan penggunaan getop yang buruk tidak dapat diukur - hanya diterima karena kebanyakan orang tidak akan pernah tahu.
Florian Heigl

30

Ini semacam kesalahan 'umum'. Pipa membuat SubShell, sehingga while readberjalan pada shell yang berbeda dari skrip Anda, yang membuat CNTvariabel Anda tidak pernah berubah (hanya yang ada di dalam subkulit pipa).

Kelompokkan yang terakhir echodengan subkulit whileuntuk memperbaikinya (ada banyak cara lain untuk memperbaikinya, ini salah satunya. Iain dan jawaban Ignacio punya yang lain.)

CNT=0

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

Penjelasan panjang:

  1. Anda mendeklarasikan CNTskrip Anda sebagai nilai 0;
  2. SubShell dimulai pada |ke while read;
  3. $CNTVariabel Anda diekspor ke SubShell dengan nilai 0;
  4. SubShell menghitung dan meningkatkan CNTnilainya menjadi 5;
  5. SubShell berakhir, variabel dan nilai dihancurkan (mereka tidak kembali ke proses / skrip panggilan).
  6. Anda nilai echoawal Anda CNT0.

2
Script shell pertama yang pernah saya tulis memberi saya masalah yang sama, membenturkan kepala saya ke dinding untuk sementara waktu sebelum mengetahui bahwa pipa tersebut menghasilkan shell tambahan. Variabel apa pun yang Anda mainkan dalam pipa akan keluar dari cakupan segera setelah pipa berakhir - artinya jika Anda benar-benar ingin melakukan sesuatu dengan variabel di luar pipa yang digunakannya, Anda harus tahan keadaan melalui sesuatu yang funky seperti file sementara.
photoionized

Jawaban luar biasa, sayangnya saya hanya bisa memberikan satu bonus penerimaan. Maaf.
wolfgangsz

10

Ini bekerja

CNT=0 

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

Saya suka itu, cara yang cerdas karena Anda tahu di mana data yang dibutuhkan dan hanya perlu mendapatkannya kembali. Jika Anda tidak tahu solusi keterampilan tinggi, Anda selalu dapat "membaca file" hahahha. +1 untuk Anda.
m3nda

1
Siapa pun yang membaca ini, ketahuilah bahwa solusi yang disediakan oleh Iain hanya berfungsi ketika Anda memiliki skrip Anda secara eksplisit memanggil bash, dengan memiliki baris pertama: #! / Bin / bash dan bahwa: #! / Bin / sh tidak akan berfungsi.
Roadowl

1
Menarik, contoh pertama yang pernah saya lihat di mana Penggunaan Cat yang Tidak Berguna sebenarnya mencegah kode bekerja . Omong-omong @Roadowl, satu-satunya bashism di sini adalah baris let CNT++yang seharusnya CNT="$((CNT+1))"menggunakan ekspansi aritmatika yang sesuai dengan POSIX . Sisanya sudah portabel.
Wildcard

6

Sebaliknya, coba masukkan data dalam sub-shell, seperti file sebelum loop sementara. Ini mirip dengan solusi lain, tetapi menganggap Anda tidak ingin beberapa file terputus-putus:

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