Tại sao `while IFS = read` được sử dụng thường xuyên như vậy, thay vì` IFS =; trong khi đọc..`?


81

Có vẻ như thông lệ bình thường sẽ đặt cài đặt IFS bên ngoài vòng lặp while để không lặp lại cài đặt nó cho mỗi lần lặp ... Đây có phải chỉ là một kiểu "khỉ thấy, khỉ làm", vì nó đã dành cho khỉ này cho đến khi Tôi đọc người đàn ông đọc , hoặc tôi đang thiếu một cái bẫy tinh tế (hoặc rõ ràng rõ ràng) ở đây?

Câu trả lời:


82

Cái bẫy là

IFS=; while read..

thiết lập IFScho toàn bộ môi trường shell bên ngoài vòng lặp, trong khi

while IFS= read

xác định lại nó chỉ cho readlời gọi (ngoại trừ trong vỏ Bourne). Bạn có thể kiểm tra xem làm một vòng lặp như

while IFS= read xxx; ... done

sau đó sau đó vòng lặp, echo "blabalbla $IFS ooooooo"bản in

blabalbla
 ooooooo

trong khi sau

IFS=; read xxx; ... done

phần còn IFS lại được xác định lại: bây giờ echo "blabalbla $IFS ooooooo"in

blabalbla  ooooooo

Vì vậy, nếu bạn sử dụng mẫu thứ hai, bạn phải nhớ đặt lại : IFS=$' \t\n'.


Phần thứ hai của câu hỏi này đã được hợp nhất ở đây , vì vậy tôi đã xóa câu trả lời liên quan từ đây.


Được rồi, có vẻ như một 'cái bẫy' tiềm năng là bỏ qua việc thiết lập lại IFS bên ngoài ... Nhưng tôi tự hỏi liệu còn có thứ gì khác đang diễn ra không ... Tôi đang thử nghiệm mọi thứ ở đây, khá sốt, và tôi lưu ý rằng thiết lập IFS trong khi danh sách lệnh của hành vi khác nhau, tùy thuộc vào việc nó có được theo sau bởi dấu hai chấm hay không. Tôi không hiểu hành vi này (chưa), và bây giờ tôi tự hỏi liệu có sự cân nhắc đặc biệt nào liên quan ở cấp độ này không ... vd. while IFS=X readkhông phân chia lúc X, nhưng while IFS=X; read...
Peter.O

(Bạn có nghĩa là bán ruột, phải không?) Thứ hai whilekhông có ý nghĩa nhiều - điều kiện để while đầu tại dấu chấm phẩy đó, vì vậy không có vòng lặp thực tế ... readtrở thành chỉ lệnh đầu tiên trong vòng một phần tử ... Hoặc không ? Thế còn do.. thì sao?
rozcietrzewiacz

1
Không, chờ đã - bạn nói đúng, bạn có thể có một số lệnh trong whileđiều kiện (trước do).
rozcietrzewiacz

Ồ .. chắc chắn, bạn có thể có chúng ... như bạn đã nhận ra ... nhưng dường như chúng không thích dấu chấm phẩy ... (và vòng lặp sẽ tiếp tục lặp ad-infinitum cho đến khi lệnh cuối cùng trả về không mã thoát -zero) ... Bây giờ tôi đang tự hỏi nếu cái bẫy nằm hoàn toàn trong một lĩnh vực khác; hiểu được làm thế nào trong khi danh sách lệnh của hoạt động, ví dụ. tại sao không IFS=hoạt động, nhưng IFS=Xkhông ... (hoặc có lẽ tôi đã làm điều này trong một thời gian .. cần nghỉ giải lao :)
Peter.O

1
$ rozcietrzewiacz .. Rất tiếc ... Tôi đã không nhận thấy bản cập nhật của bạn, khi tôi di chuyển bản cập nhật của mình (như đã đề cập trong bình luận trước) .. Nó có vẻ thú vị, và nó bắt đầu có ý nghĩa ... nhưng ngay cả trong một đêm- con chim giống tôi, đã rất muộn ... (tôi chỉ nghe thấy tiếng chim buổi sáng :) ... Điều đó nói rằng, tôi đã tập hợp một chút và đọc các ví dụ của bạn ... Tôi nghĩ rằng tôi đã nhận được nó, thực sự tôi Tôi chắc chắn bạn đã có nó, nhưng tôi phải ngủ :) ... Đây gần như là một chiếc Eureka! Khoảnh khắc ... cảm ơn
Peter.O

45

Hãy xem xét một ví dụ, với một số văn bản đầu vào được làm cẩn thận:

text=' hello  world\
foo\bar'

Đó là hai dòng, bắt đầu bằng một khoảng trắng và kết thúc bằng dấu gạch chéo ngược. Trước tiên, hãy xem xét những gì xảy ra mà không có bất kỳ biện pháp phòng ngừa nào xung quanh read(nhưng sử dụng printf '%s\n' "$text"để in cẩn thận $textmà không có bất kỳ rủi ro mở rộng nào). (Dưới đây, $ ‌là dấu nhắc shell.)

$ printf '%s\n' "$text" |
  while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]

readăn các dấu gạch chéo ngược: dấu gạch chéo ngược-dòng mới làm cho dòng mới bị bỏ qua và dấu gạch chéo ngược - bất cứ điều gì bỏ qua dấu gạch chéo ngược đầu tiên. Để tránh dấu gạch chéo ngược được xử lý đặc biệt, chúng tôi sử dụng read -r.

$ printf '%s\n' "$text" |
  while read -r line; do printf '%s\n' "[$line]"; done
[hello  world\]
[foo\bar]

Điều đó tốt hơn, chúng tôi có hai dòng như mong đợi. Hai dòng gần như chứa nội dung mong muốn: khoảng trắng kép giữa helloworldđã được giữ lại, vì nó nằm trong linebiến. Mặt khác, không gian ban đầu đã bị ăn hết. Đó là bởi vì readđọc càng nhiều từ khi bạn chuyển các biến đó, ngoại trừ biến cuối cùng chứa phần còn lại của dòng - nhưng nó vẫn bắt đầu bằng từ đầu tiên, tức là các khoảng trắng ban đầu bị loại bỏ.

Vì vậy, để đọc từng dòng theo nghĩa đen, chúng ta cần đảm bảo rằng không có sự phân tách từ nào đang diễn ra. Chúng tôi làm điều này bằng cách đặt IFSbiến thành một giá trị trống.

$ printf '%s\n' "$text" |
  while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello  world\]
[foo\bar]

Lưu ý cách chúng tôi đặt IFS cụ thể cho thời lượng tích readhợp . Biến IFS= read -r linethiết lập biến môi trường IFS(thành một giá trị trống) đặc biệt để thực hiện read. Đây là một ví dụ của cú pháp lệnh đơn giản chung : một chuỗi (có thể trống) các phép gán biến được theo sau bởi một tên lệnh và các đối số của nó (ngoài ra, bạn có thể ném vào các chuyển hướng tại bất kỳ điểm nào). Vì readđược tích hợp sẵn, biến không bao giờ thực sự kết thúc trong môi trường của quy trình bên ngoài; dù sao giá trị $IFSlà những gì chúng ta chỉ định ở đó miễn readlà thực thi. Lưu ý rằng đó readkhông phải là một tích hợp đặc biệt , vì vậy việc chuyển nhượng chỉ kéo dài trong suốt thời gian của nó.

Do đó, chúng tôi chú ý không thay đổi giá trị của IFScác hướng dẫn khác có thể dựa vào nó. Mã này sẽ hoạt động bất kể mã xung quanh đã được đặt thành IFSgì ban đầu và nó sẽ không gây ra bất kỳ rắc rối nào nếu mã bên trong vòng lặp phụ thuộc vào IFS.

Tương phản với đoạn mã này, tìm kiếm các tệp trong một đường dẫn được phân tách bằng dấu hai chấm. Danh sách tên tệp được đọc từ một tệp, một tên tệp trên mỗi dòng.

IFS=":"; set -f
while IFS= read -r name; do
  for dir in $PATH; do
    ## At this point, "$IFS" is still ":"
    if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
  done
done <filenames.txt

Nếu vòng lặp là while IFS=; read -r name; do …, thì for dir in $PATHsẽ không tách $PATHthành các thành phần được phân tách bằng dấu hai chấm. Nếu mã là IFS=; while read …, nó sẽ rõ ràng hơn nữa mà IFSkhông được đặt :trong thân vòng lặp.

Tất nhiên, nó sẽ có thể khôi phục giá trị IFSsau khi thực hiện read. Nhưng điều đó đòi hỏi phải biết giá trị trước đó, đó là nỗ lực thêm. IFS= readlà cách đơn giản (và, thuận tiện, cũng là cách ngắn nhất).

¹ Và, nếu readbị gián đoạn bởi một tín hiệu bị mắc kẹt, có thể trong khi bẫy được thực hiện - điều này không được xác định bởi POSIX và phụ thuộc vào vỏ trong thực tế.


4
Cảm ơn Gilles .. một chuyến tham quan có hướng dẫn rất hay .. (ý bạn là 'set -f'?) .... Bây giờ, đối với người đọc, để nói lại những gì đã được nói, tôi muốn nhấn mạnh vấn đề đã xảy ra Tôi nhìn nó sai cách. Đầu tiên và quan trọng nhất là thực tế rằng cấu trúc while IFS= read(không có dấu chấm phẩy sau =) không phải là một dạng đặc biệt whilecủa IFShoặc của read.. Cấu trúc này là chung: nghĩa là. anyvar=anyvalue anycommand. Việc thiếu ;sau khi cài đặt anyvarlàm cho phạm vi của anyvar cục bộ thành anycommand.. Vòng lặp while - do / doing không liên quan 100% đến phạm vi cục bộ của any_var.
Peter.O

3

Ngoài các (đã được làm rõ) IFSsự khác biệt Phạm vi giữa while IFS='' read, IFS=''; while readwhile IFS=''; readthành ngữ (mỗi lệnh vs kịch bản / vỏ rộng IFSPhạm vi biến), bài học mang về nhà là bạn mất hàng đầu dấu không gian của một dòng đầu vào nếu biến IFS được đặt thành (chứa a) không gian.

Điều này có thể có hậu quả khá nghiêm trọng nếu đường dẫn tệp đang được xử lý.

Do đó, việc đặt biến IFS thành chuỗi trống là bất cứ điều gì ngoại trừ một ý tưởng tồi vì nó đảm bảo rằng khoảng trắng hàng đầu và dấu kiểm không bị tước bỏ.

Xem thêm: Bash, đọc từng dòng từ tệp, với IFS

(
shopt -s nullglob
touch '  file with spaces   '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)

Trình diễn +1 xuất sắc, dọn dẹp sau với 'rm * tệp * với * dấu cách *'
amdn

0

Lấy cảm hứng từ câu trả lời của Yuzem

Nếu bạn muốn đặt IFSthành một nhân vật thực tế, điều này làm việc cho tôi

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
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.