bash: biến mất giá trị ở cuối vòng lặp while


36

Tôi có một vấn đề trong một trong các kịch bản shell của tôi. Hỏi một vài đồng nghiệp, nhưng tất cả họ chỉ lắc đầu (sau vài lần gãi), vì vậy tôi đã đến đây để trả lời.

Theo hiểu biết của tôi, tập lệnh shell sau đây sẽ in "Đếm là 5" làm dòng cuối cùng. Ngoại trừ nó không. Nó in "Đếm là 0". Nếu "while read" được thay thế bằng bất kỳ loại vòng lặp nào khác, nó sẽ hoạt động tốt. Đây là kịch bản:

tiếng vang "1"> input.data
tiếng vang "2" >> input.data
tiếng vang "3" >> input.data
tiếng vang "4" >> input.data
tiếng vang "5" >> input.data

CNT = 0 

mèo đầu vào.data | trong khi đọc;
làm
  hãy để CNT ++;
  tiếng vang "Đếm tới $ CNT"
làm xong 
tiếng vang "Đếm là $ CNT"

Tại sao điều này xảy ra và làm thế nào tôi có thể ngăn chặn nó? Tôi đã thử điều này trong Debian Lenny và Squeeze, kết quả tương tự (ví dụ bash 3.2.39 và bash 4.1.5. Tôi hoàn toàn thừa nhận không phải là một trình hướng dẫn kịch bản shell, vì vậy mọi con trỏ đều được đánh giá cao.

Câu trả lời:


30

Xem đối số @ Bash Câu hỏi thường gặp # 24: "Tôi đặt biến trong một vòng lặp. Tại sao chúng đột nhiên biến mất sau khi vòng lặp chấm dứt? Hoặc, tại sao tôi không thể đọc dữ liệu để đọc?" (gần đây nhất được lưu trữ ở đây ).

Tóm tắt: Điều này chỉ được hỗ trợ từ bash 4.2 trở lên. Bạn cần sử dụng các cách khác nhau như thay thế lệnh thay vì đường ống nếu bạn đang sử dụng bash.


Bạn nhận được tiền thưởng, vì câu trả lời của bạn cung cấp cho tôi phạm vi tùy chọn rộng nhất.
wolfgangsz

5
Liên kết đã chết. Đây là lý do tại sao câu trả lời chỉ liên kết là xấu. Ít nhất là tóm tắt câu trả lời ở đây.
rudolfbyker

Chúa ơi, một lần khác, nơi ksh đơn giản là tốt hơn nhiều ... tại sao, tại sao mọi người lại đổ xô đi chơi bash.
Florian Heigl

@FlorianHeigl: Bạn có cho rằng ksh là One True Shell không?
Ignacio Vazquez-Abrams

@ IgnacioVazquez-Abrams không, nhưng tôi cho rằng việc xử lý vòng lặp while trong bash là một PITA khủng khiếp. Việc xử lý vòng lặp là người chạy dài giữ cho nó không bắt kịp chức năng của năm 1993. Những thứ khác là xử lý getopt trong đó trình xử lý dựng sẵn (cũng là năm 1993) rất đơn giản và có khả năng, thứ mà bạn vẫn không thể có được trừ khi sử dụng docopt. Tôi khẳng định rằng bash đã tự đứng sau đường cong trong hơn 20 năm, một cách khăng khăng và lượng thời gian dành cho ĐIỀU NÀY TẠI ĐÂY hoặc hàng triệu lượt sử dụng xấu được đưa ra ngoài tầm kiểm soát - chỉ được chấp nhận vì hầu hết mọi người sẽ không bao giờ biết.
Florian Heigl

30

Đây là một loại sai lầm 'phổ biến'. Các đường ống tạo ra SubShells, do đó, nó while readđang chạy trên một lớp vỏ khác với tập lệnh của bạn, điều đó làm cho CNTbiến của bạn không bao giờ thay đổi (chỉ có một bên trong lớp vỏ ống).

Nhóm cuối cùng echovới lớp con whileđể sửa nó (có nhiều cách khác để sửa nó, đây là một cách. Câu trả lời của Iain và Ignacio có những cách khác.)

CNT=0

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

Giải thích dài dòng:

  1. Bạn tuyên bố CNTtrên tập lệnh của bạn là giá trị 0;
  2. Một subshell được bắt đầu vào |tới while read;
  3. $CNTBiến của bạn được xuất sang SubShell với giá trị 0;
  4. SubShell đếm và tăng CNTgiá trị lên 5;
  5. SubShell kết thúc, các biến và giá trị bị hủy (chúng không quay lại quy trình / tập lệnh gọi).
  6. Bạn có giá trị echoban đầu CNTlà 0.

2
Kịch bản shell đầu tiên tôi từng viết cho tôi những vấn đề tương tự, đập đầu vào tường một lúc trước khi phát hiện ra rằng các đường ống sinh ra các vỏ bổ sung. Bất kỳ biến nào bạn lộn xộn trong một đường ống sẽ ra khỏi phạm vi ngay khi đường ống kết thúc - có nghĩa là nếu bạn thực sự, thực sự muốn làm một cái gì đó với một biến bên ngoài đường ống mà nó được sử dụng, bạn sẽ phải giữ trạng thái thông qua một cái gì đó sôi nổi như một tập tin tạm thời.
quang hóa

Câu trả lời tuyệt vời, tiếc là tôi chỉ có thể đưa ra một phần thưởng chấp nhận. Lấy làm tiếc.
wolfgangsz

10

Những công việc này

CNT=0 

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

Tôi thích điều đó, một cách thông minh bởi vì bạn biết dữ liệu cần thiết ở đâu và chỉ cần lấy lại. Nếu bạn không biết các giải pháp kỹ năng cao, bạn luôn có thể "đọc một tập tin" hahahha. +1 cho bạn.
m3nda

1
Bất cứ ai đọc điều này, hãy lưu ý rằng giải pháp do Iain cung cấp chỉ hoạt động khi tập lệnh của bạn gọi bash một cách rõ ràng, bằng cách có dòng đầu tiên: #! / Bin / bash và rằng: #! / Bin / sh sẽ không hoạt động.
Roadowl

1
Thật thú vị, ví dụ đầu tiên tôi từng thấy khi sử dụng Cat vô dụng thực sự ngăn chặn mã hoạt động . Nhân tiện, @Roadowl, bashism duy nhất ở đây là dòng let CNT++thay vào đó là CNT="$((CNT+1))"sử dụng mở rộng số học tuân thủ POSIX . Phần còn lại của nó là xách tay.
tự đại diện

6

Thay vào đó, hãy thử truyền dữ liệu trong lớp vỏ phụ, giống như đó là một tệp trước vòng lặp while. Điều này tương tự như giải pháp của lain, nhưng giả sử bạn không muốn một số tệp không liên tục:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.