Tại sao biến cục bộ của tôi trong vòng lặp 'trong khi đọc', nhưng không phải trong vòng lặp có vẻ tương tự khác?


25

Tại sao tôi nhận được các giá trị khác nhau $xtừ các đoạn bên dưới?

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

Câu trả lời:


26

Lời giải thích đúng đã được đưa ra bởi jsbillingsgeekizard , nhưng hãy để tôi mở rộng thêm một chút.

Trong hầu hết các shell, bao gồm bash, mỗi bên của một đường ống chạy trong một lớp con, do đó, bất kỳ thay đổi nào trong trạng thái bên trong của shell (chẳng hạn như các biến cài đặt) vẫn bị giới hạn trong đoạn đường ống đó. Thông tin duy nhất bạn có thể nhận được từ một mạng con là những gì nó xuất ra (đến đầu ra tiêu chuẩn và các mô tả tệp khác) và mã thoát của nó (là một số trong khoảng từ 0 đến 255). Ví dụ: đoạn mã sau in 0:

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

Trong ksh (các biến thể có nguồn gốc từ mã AT & T, không phải các biến thể pdksh / mksh) và zsh, mục cuối cùng trong một đường ống được thực thi trong vỏ cha. (POSIX cho phép cả hai hành vi.) Vì vậy, đoạn trích trên in 2.

Một thành ngữ hữu ích là bao gồm sự tiếp tục của vòng lặp while (hoặc bất cứ điều gì bạn có ở phía bên phải của đường ống, nhưng một vòng lặp while thực sự phổ biến ở đây) trong đường ống:

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}

1
Cảm ơn Gilles .. Đó là a = 0; a = 1 | a = 2 đưa ra một bức tranh rất rõ ràng .. và không chỉ về nội địa hóa trạng thái bên trong, mà cả một đường ống không thực sự cần gửi bất cứ thứ gì qua đường ống (trừ mã thoát (?) .. là một cái nhìn sâu sắc thú vị về một đường ống ... Tôi đã xoay sở để chạy được kịch bản của mình < <(locate -ber ^\.tag$), nhờ vào câu trả lời ban đầu hơi không rõ ràng và sự hài hước của geekizard và glenn jackman .. là khá rõ ràng, đặc biệt là với bình luận theo dõi jsbillings :)
Peter.O

cảm giác như tôi đã chuyển vào một hàm, vì vậy tôi đã di chuyển một số biến và kiểm tra vào bên trong nó và nó hoạt động rất tốt, thx!
Sức mạnh Bảo Bình

8

Bạn đang gặp vấn đề về phạm vi biến. Các biến được xác định trong vòng lặp while nằm ở bên phải của đường ống có bối cảnh phạm vi cục bộ của riêng chúng và các thay đổi đối với biến sẽ không được nhìn thấy bên ngoài vòng lặp. Vòng lặp while về cơ bản là một lớp con nhận được một BẢN SAO của môi trường shell và mọi thay đổi đối với môi trường đều bị mất ở phần cuối của shell. Xem câu hỏi StackOverflow này .

CẬP NHẬT : Tôi đã bỏ qua việc chỉ ra một thực tế quan trọng rằng vòng lặp while với lớp vỏ riêng của nó là do nó là điểm cuối của một đường ống, tôi đã cập nhật điều đó trong câu trả lời.


@jsbillings .. Được rồi, điều đó giải thích hai đoạn cuối cùng, nhưng nó không giải thích điều đầu tiên, trong đó giá trị của $ x được đặt trong vòng lặp, được thực hiện là 55 (vượt quá phạm vi của vòng lặp 'while')
Peter.O

5
@ fred.bear: Nó đang chạy whilevòng lặp như là phần đuôi của một đường ống ném nó vào một khung con.
geekizard

2
Đây là nơi thay thế quá trình bash đi vào chơi. Thay vì blah|blah|while read ..., bạn có thể cówhile read ...; done < <(blah|blah)
glenn jackman

1
@geekizard: cảm ơn vì đã điền vào các chi tiết mà tôi bỏ qua để đưa vào câu trả lời của mình.
jsbillings

1
-1 Xin lỗi nhưng câu trả lời này chỉ sai. Nó giải thích cách thức công cụ này hoạt động trong nhiều ngôn ngữ lập trình nhưng không phải trong vỏ. @Gilles, dưới đây, đã đúng.
jpc

6

Như đã đề cập trong các câu trả lời khác , các phần của một đường ống chạy trong các lớp con, vì vậy các sửa đổi được thực hiện ở đó không thể nhìn thấy vỏ chính.

Nếu chúng ta chỉ xem xét Bash, có hai cách giải quyết khác ngoài cmd | { stuff; more stuff; }cấu trúc:

  1. Chuyển hướng đầu vào từ quá trình thay thế :

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

    Đầu ra từ lệnh in <(...)được tạo để xuất hiện như thể nó là một ống có tên.

  2. Các lastpipetùy chọn, mà làm cho Bash công việc như ksh, và chạy phần cuối của đường ống trong quá trình shell chính. Mặc dù nó chỉ hoạt động nếu điều khiển công việc bị vô hiệu hóa, tức là không có trong vỏ tương tác:

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

    hoặc là

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

Quá trình thay thế tất nhiên là được hỗ trợ trong ksh và zsh. Nhưng vì dù sao họ cũng chạy phần cuối của đường ống trong lớp vỏ chính, nên sử dụng nó như một cách giải quyết không thực sự cần thiết.


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

nó có thể làm việc.

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.