Đọc các dòng từ một tệp có bash: for vs.


36

Tôi đang cố đọc một tệp văn bản và làm một cái gì đó với mỗi dòng, sử dụng tập lệnh bash.

Vì vậy, tôi có một danh sách giống như thế này:

server1
server2
server3
server4

Tôi nghĩ rằng tôi có thể lặp lại điều này bằng cách sử dụng một vòng lặp while, như vậy:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt

Vòng lặp while dừng sau 1 lần chạy, do đó chỉ chạy uname -atrên server1

Tuy nhiên, với vòng lặp for sử dụng cat, nó hoạt động tốt:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done

Điều khó hiểu hơn với tôi là điều này cũng hoạt động:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

Tại sao ví dụ đầu tiên của tôi dừng lại sau lần lặp đầu tiên?

Câu trả lời:


25

Các forvòng lặp là tốt ở đây. Nhưng lưu ý rằng điều này là do tệp chứa tên máy, không chứa bất kỳ ký tự khoảng trắng hoặc ký tự toàn cầu nào. for x in $(cat file); do …không hoạt động để lặp lại các dòng filenói chung, vì trước tiên, shell sẽ tách đầu ra khỏi lệnh ở cat filebất cứ nơi nào có khoảng trắng, và sau đó coi mỗi từ là một mẫu hình cầu nên \[?*được mở rộng thêm. Bạn có thể làm cho for x in $(cat file)an toàn nếu bạn làm việc trên nó:

set -f
IFS='
'
for x in $(cat file); do 

Đọc liên quan: Vòng qua các tập tin có khoảng trắng trong tên? ; Làm thế nào tôi có thể đọc từng dòng từ một biến trong bash? ; Tại sao được while IFS= readsử dụng thường xuyên, thay vì IFS=; while read..? Lưu ý rằng khi sử dụng while read, cú pháp an toàn để đọc các dòng là while IFS= read -r line; do ….

Bây giờ hãy chuyển sang những gì sai với while readnỗ lực của bạn . Chuyển hướng từ tệp danh sách máy chủ áp dụng cho toàn bộ vòng lặp. Vì vậy, khi sshchạy, đầu vào tiêu chuẩn của nó đến từ tập tin đó. Máy khách ssh không thể biết khi nào ứng dụng từ xa có thể muốn đọc từ đầu vào tiêu chuẩn của nó. Vì vậy, ngay khi máy khách ssh nhận thấy một số đầu vào, nó sẽ gửi đầu vào đó đến phía từ xa. Sau đó, máy chủ ssh đã sẵn sàng cung cấp đầu vào đó cho lệnh từ xa, nếu nó muốn. Trong trường hợp của bạn, lệnh từ xa không bao giờ đọc bất kỳ đầu vào nào, vì vậy dữ liệu cuối cùng bị loại bỏ, nhưng phía khách hàng không biết gì về điều đó. Nỗ lực của bạn với echocông việc vì echokhông bao giờ đọc bất kỳ đầu vào nào, nó chỉ để lại đầu vào tiêu chuẩn của nó.

Có một vài cách bạn có thể tránh điều này. Bạn có thể yêu cầu ssh không đọc từ đầu vào tiêu chuẩn, với -ntùy chọn.

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

Các -ntùy chọn trong thực tế nói sshđể chuyển hướng đầu vào của nó từ /dev/null. Bạn có thể làm điều đó ở cấp độ shell và nó sẽ hoạt động với bất kỳ lệnh nào.

while read server; do
  ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt

Một phương pháp hấp dẫn để tránh đầu vào của ssh đến từ tệp là đặt chuyển hướng vào readlệnh : while read server </home/kenny/list_of_servers.txt; do …. Điều này sẽ không hoạt động, bởi vì nó làm cho tệp được mở lại mỗi lần readlệnh được thực thi (vì vậy nó sẽ đọc dòng đầu tiên của tệp nhiều lần). Chuyển hướng cần phải ở trên toàn bộ vòng lặp while để tệp được mở một lần trong suốt thời gian của vòng lặp.

Giải pháp chung là cung cấp đầu vào cho vòng lặp trên một bộ mô tả tệp khác với đầu vào tiêu chuẩn. Shell có cấu trúc để nhập và xuất phà từ số mô tả này sang số khác. Ở đây, chúng tôi mở tệp trên bộ mô tả tệp 3 và chuyển hướng readđầu vào tiêu chuẩn của lệnh từ bộ mô tả tệp 3. Máy khách ssh bỏ qua các bộ mô tả không chuẩn, vì vậy tất cả đều ổn.

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt

Trong bash, readlệnh có một tùy chọn cụ thể để đọc từ một bộ mô tả tệp khác, vì vậy bạn có thể viết read -u3 server.

Đọc liên quan: Mô tả tập tin & kịch bản shell ; Khi nào bạn sẽ sử dụng một mô tả tập tin bổ sung?


5
Man, stackexchange này chắc chắn khác với serverfault. Mọi người ở đây thực sự đi theo cách của họ để không chỉ trả lời, mà còn giáo dục. Cảm ơn rất nhiều.
Kenny Rasschaert

26

Bạn nên sử dụng while, khôngfor . Cách để tránh các lệnh nuốt đầu vào tiêu chuẩn trong một vòng lặp như vậy chỉ đơn giản là sử dụng một mô tả tệp khác :

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

Để biết thêm thông tin, help [r]ead( thực sự ) và một bài viết khác giải thích lý do tại sao .


1
Thật thú vị, tôi chưa bao giờ sử dụng mô tả tập tin trước đây. Không những gì -ucờ làm gì? Tôi không chắc chắn trong trang người đàn ông nào tôi phải tìm kiếm nó.
Kenny Rasschaert

Lệnh đọc là một phần của bash shell, vì vậy nó nằm trong trang man của bash trong phần "SHELL BUILTIN THÔNG TIN" trong đoạn đã đọc. Bạn sẽ tìm thấy "-u fd Đọc đầu vào từ tệp mô tả tệp fd." trong đoạn này
f4m8

6
Ngoài ra help read- helplà lệnh để có được các mantrang tương đương với các nội trang.
l0b0

22

Trong mã đầu tiên của bạn sshsẽ ăn cắp STDIN từ STDIN while. Thêm -ntùy chọn sshđể tránh nó. Từ man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).

Ok, có bất kỳ ý tưởng tại sao điều này không xảy ra với vòng lặp for?
Kenny Rasschaert

7
Bởi vì forvòng lặp nhận dữ liệu dưới dạng tham số, không phải là đầu vào.
manatwork

1
Bạn, quý ngài, một quý ông, và một học giả.
Kenny Rasschaert

0

Việc xử lý đầu vào tiêu chuẩn mặc định sshthoát dòng còn lại từ vòng lặp while.

Để tránh vấn đề này, thay đổi nơi lệnh có vấn đề đọc đầu vào tiêu chuẩn. Nếu không có đầu vào tiêu chuẩn nào được chuyển đến lệnh, hãy đọc đầu vào tiêu chuẩn từ /dev/nullthiết bị đặc biệt .

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.