Mengapa variabel saya lokal dalam satu loop 'sambil membaca', tetapi tidak dalam loop lain yang tampaknya serupa?

25

Mengapa saya mendapatkan nilai berbeda $xdari cuplikan di bawah ini?

#!/bin/bash

x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x 
#    x=55 .. I'd expect this result

x=1
cat junk | while read var ; do x=55 ; done
echo x=$x 
#    x=1 .. but why?

x=1
echo fred | while read var ; do x=55 ; done
echo x=$x 
#    x=1  .. but why?
Peter.O
sumber
Posting serupa pada Stack Overflow: Suatu variabel yang dimodifikasi di dalam loop sementara tidak diingat .
codeforester

Jawaban:

26

Penjelasan yang tepat telah diberikan oleh jsbillings dan geekosaur , tetapi biarkan saya sedikit memperluasnya.

Di sebagian besar shell, termasuk bash, setiap sisi pipa berjalan dalam subkulit, sehingga setiap perubahan dalam kondisi internal shell (seperti variabel pengaturan) tetap terbatas pada segmen pipa tersebut. Satu-satunya informasi yang dapat Anda peroleh dari subkulit adalah apa yang ditampilkannya (ke keluaran standar dan deskriptor file lainnya) dan kode keluarnya (yang merupakan angka antara 0 dan 255). Misalnya, cuplikan berikut mencetak 0:

a=0; a=1 | a=2; echo $a

Dalam ksh (varian yang berasal dari kode AT&T, bukan varian pdksh / mksh) dan zsh, item terakhir dalam sebuah pipa dieksekusi di shell induk. (POSIX memungkinkan kedua perilaku.) Jadi potongan di atas mencetak 2.

Sebuah idiom yang berguna adalah memasukkan kelanjutan loop sementara (atau apa pun yang Anda miliki di sisi kanan pipa, tetapi loop sementara sebenarnya umum di sini) dalam pipa:

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Terima kasih Gilles .. Itu a = 0; a = 1 | a = 2 memberikan gambaran yang sangat jelas .. dan tidak hanya lokalisasi keadaan internal, tetapi juga bahwa pipa tidak benar-benar perlu mengirim apa pun melalui pipa (selain kode keluar (?) .. Dengan sendirinya adalah wawasan yang menarik ke dalam pipa ... Saya berhasil membuat skrip saya berjalan < <(locate -ber ^\.tag$), berkat jawaban yang sedikit tidak jelas dan geekosaurus dan comemnts glenn jackman .. Saya awalnya dalam dilema tentang menerima jawaban, tetapi hasil nett cukup jelas, terutama dengan komentar tindak lanjut
jsbillings
rasanya seperti saya disalurkan ke fungsi, jadi saya memindahkan beberapa variabel dan tes ke dalamnya dan itu bekerja dengan baik, terima kasih!
Aquarius Power
8

Anda mengalami masalah lingkup variabel. Variabel yang didefinisikan dalam loop sementara yang ada di sisi kanan pipa memiliki konteks lingkup lokal mereka sendiri, dan perubahan variabel tidak akan terlihat di luar loop. Loop sementara pada dasarnya adalah sebuah subkulit yang mendapatkan COPY dari lingkungan shell, dan setiap perubahan pada lingkungan akan hilang di akhir shell. Lihat pertanyaan StackOverflow ini .

DIPERBARUI : Saya lalai menunjukkan fakta penting bahwa perulangan while dengan subkulitnya sendiri adalah karena itu merupakan titik akhir dari sebuah pipa, saya telah memperbarui itu dalam jawabannya.

jsbillings
sumber
@ jsbillings .. Oke, itu menjelaskan dua cuplikan terakhir, tetapi tidak menjelaskan yang pertama, di mana nilai $ x yang ditetapkan dalam loop, dijalankan sebagai 55 (di luar ruang lingkup 'while' loop)
Peter.O
5
@ fred.bear: Ini menjalankan whileloop sebagai ujung ekor pipa yang melemparkannya ke subkulit.
geekosaur
2
Di sinilah substitusi proses bash ikut bermain. Alih-alih blah|blah|while read ..., Anda dapat memilikiwhile read ...; done < <(blah|blah)
glenn jackman
1
@geekosaur: terima kasih telah mengisi detail yang saya abaikan untuk dimasukkan dalam jawaban saya.
jsbillings
1
-1 Maaf tapi jawaban ini salah. Ini menjelaskan bagaimana hal ini bekerja dalam banyak bahasa pemrograman tetapi tidak di shell. @Gilles, di bawah, sudah benar.
jpc
6

Seperti disebutkan dalam jawaban lain , bagian-bagian dari pipa berjalan dalam subkulit, sehingga modifikasi yang dilakukan tidak terlihat oleh shell utama.

Jika kami menganggap hanya Bash, ada dua solusi lain selain cmd | { stuff; more stuff; }struktur:

  1. Alihkan input dari substitusi proses :

    while read var ; do x=55 ; done < <(echo fred)
    echo "$x"

    Output dari perintah di <(...)dibuat agar tampak seolah-olah itu adalah pipa bernama.

  2. The lastpipepilihan, yang membuat Bash bekerja seperti ksh, dan menjalankan bagian terakhir dari pipa dalam proses shell utama. Meskipun itu hanya berfungsi jika kontrol pekerjaan dinonaktifkan, yaitu tidak dalam shell interaktif:

    bash -c '
      shopt -s lastpipe
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '

    atau

    bash -O lastpipe -c '
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '

Substitusi proses tentu saja didukung dalam ksh dan zsh juga. Tetapi karena mereka menjalankan bagian terakhir dari pipa di shell utama, menggunakannya sebagai solusi tidak benar-benar diperlukan.

ilkkachu
sumber
0
#!/bin/bash
set -x

# prepare test data.
mkdir -p ~/test_var_global
cd ~/test_var_global
echo "a"> core.1
echo "b"> core.2
echo "c"> core.3


var=0

coreFiles=$(find . -type f -name "core*")
while read -r file;
do
  # perform computations on $i
  ((var++))
done <<EOF
$coreFiles
EOF

echo $var

Result:
...
+ echo 3
3

itu bisa bekerja.

GPS
sumber