Sử dụng nội dung đã đọc của bash mà không có vòng lặp while


8

Tôi đã quen với hàm bashdựng sẵn readtrong các vòng lặp, ví dụ:

echo "0 1
      1 1
      1 2
      2 3" |\
while read A B; do
    echo $A + $B | bc;
done

Tôi đã làm việc trên một số makedự án và việc phân chia các tệp và lưu trữ các kết quả trung gian trở nên khôn ngoan. Kết quả là tôi thường kết thúc việc chia các dòng đơn thành các biến. Trong khi ví dụ sau hoạt động khá tốt,

head -n1 somefile | while read A B C D E FOO; do [... use vars here ...]; done

thật là ngu ngốc, bởi vì vòng lặp while sẽ không bao giờ chạy nhiều hơn một lần. Nhưng không có while,

head -n1 somefile | read A B C D E FOO; [... use vars here ...]

Các biến đọc luôn trống khi tôi sử dụng chúng. Tôi không bao giờ nhận thấy hành vi này read, bởi vì tôi thường sử dụng các vòng lặp để xử lý nhiều dòng tương tự. Làm thế nào tôi có thể sử dụng bashreaddựng sẵn mà không cần một thời gian vòng lặp? Hoặc có cách nào khác (hoặc thậm chí tốt hơn) để đọc một dòng thành nhiều biến (!) Không?

Phần kết luận

Các câu trả lời dạy chúng tôi, đó là một vấn đề phạm vi. Tuyên bố

 cmd0; cmd1; cmd2 | cmd3; cmd4

được giải thích như vậy mà lệnh cmd0, cmd1cmd4được thực hiện trong phạm vi tương tự, trong khi các lệnh cmd2cmd3đang từng đưa subshell riêng của họ, và phạm vi do khác nhau. Shell ban đầu là cha mẹ của cả hai subshells.


Về kết luận của bạn : (1) Dòng lệnh như được trình bày ban đầu là không chính xác về mặt cú pháp. Cụ thể, … cmd1; | cmd2 …là một lỗi. (2) Chúng ta có thể nói điều đó cmd2cmd3mỗi người được đưa ra một nhánh con riêng cmd1; cmd2 | cmd3; cmd4bằng cách thử những thứ như answer=42 | sleep 0hoặc cd other/dir | read foo. Bạn sẽ thấy rằng lệnh đầu tiên trong mỗi đường ống phải nằm trong một lớp con, vì chúng không ảnh hưởng đến quá trình shell chính (cha).
Scott

Câu trả lời:


8

Đó là bởi vì phần bạn sử dụng vars là một bộ lệnh mới. Sử dụng cái này thay thế:

head somefile | { read A B C D E FOO; echo $A $B $C $D $E $FOO; }

Lưu ý rằng, trong cú pháp này, phải có khoảng trắng sau dấu {;dấu chấm phẩy trước dấu }. Cũng -n1không cần thiết; readchỉ đọc dòng đầu tiên.

Để hiểu rõ hơn, điều này có thể giúp bạn; nó làm tương tự như trên:

read A B C D E FOO < <(head somefile); echo $A $B $C $D $E $FOO

Biên tập:

Người ta thường nói rằng hai tuyên bố tiếp theo làm tương tự:

head somefile | read A B C D E FOO
read A B C D E FOO < <(head somefile)

Không hẳn là chính xác lắm. Người đầu tiên là một đường ống từ headđể bash's readdựng sẵn. Stdout của một tiến trình đến stdin khác.

Tuyên bố thứ hai là chuyển hướng và thay thế quá trình. Nó được xử lý bởi bashchính nó. Nó tạo ra một FIFO (tên ống, <(...)) mà headđầu ra của nó được kết nối và chuyển hướng ( <) nó tới readquy trình.

Cho đến nay những điều này có vẻ tương đương. Nhưng khi làm việc với các biến nó có thể quan trọng. Trong cái đầu tiên, các biến không được đặt sau khi thực hiện. Trong cái thứ hai chúng có sẵn trong môi trường hiện tại.

Mỗi vỏ có một hành vi khác trong tình huống này. Xem liên kết mà họ đang có. Trong bashbạn có thể làm việc xung quanh hành vi đó với nhóm lệnh {}, xử lý thay thế ( < <()) hoặc chuỗi ở đây ( <<<).


{}... Tôi chỉ cố gắng (). Tại sao ;trong ví dụ thứ hai của bạn không bắt đầu "một nhóm lệnh mới"? Thật ra tôi không hiểu gì về < <cấu trúc cả. Cần phải suy nghĩ ...
Bananguin

@Bananguin xem bản chỉnh sửa của tôi, hy vọng nó mang lại ánh sáng cho vấn đề đó.
hỗn loạn

2
Tôi nghĩ rằng tôi đã hiểu: Điểm thực tế là KHI SỬ DỤNG PIPES, bash tạo ra các chuỗi con cho mỗi quy trình của đường ống và do phạm vi biến mất, sau đường ống. Việc sử dụng {}làm cho phần còn lại của dòng là một phần tử (một!) Của đường ống, trong khi bên trong đó, các biến có sẵn. Sử dụng thay thế quá trình một là không sử dụng đường ống và do đó bashkhông sinh ra các lớp con.
Bananguin

@Bananguin bạn hiểu rồi ^^
hỗn loạn

1
readchỉ đọc dòng đầu tiên đầu tiên, nó không chỉ -n1là dự phòng mà là toàn bộ headlệnh. Nếu bạn loại bỏ headlệnh, bạn cũng có thể loại bỏ đường ống dẫn. Sau đó, nó trở thành read A B C D E FOO < somefilevà nó sẽ không phải là một nhánh con, vì vậy các biến sẽ vẫn có sẵn cho lệnh tiếp theo.
kasperd

3

Để trích dẫn từ một bài viết rất hữu ích wiki.bash-hackers.org :

Điều này là do các lệnh của đường ống chạy trong các lớp con không thể sửa đổi vỏ cha. Kết quả là các biến của shell cha không được sửa đổi (xem bài viết: Bash và cây quy trình ).

Vì câu trả lời đã được cung cấp một vài lần bây giờ, một cách khác (sử dụng các lệnh không tích hợp ...) là:

$ eval `echo 0 1 | awk '{print "A="$1";B="$2}'`;echo $B $A
$ 1 0

Vấn đề của tôi là tôi nghĩ bash sẽ đặt mọi thứ giữa biểu tượng ống và cuối dòng vào cùng một khung con, nhưng rõ ràng nó chỉ đọc câu lệnh tiếp theo.
Bananguin

Câu hỏi tuyệt vời ...
geedoubleya

0

Như bạn đã lưu ý, vấn đề là một đường ống readđược chạy trong một khung con.

Một câu trả lời là sử dụng một di sản :

numbers="01 02"
read first second <<INPUT
$numbers
INPUT
echo $first
echo $second

Phương pháp này rất hay ở chỗ nó sẽ hoạt động theo cùng một cách trong bất kỳ shell nào giống như POSIX.

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.