it-swarm-vi.com

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

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" theo thói quen, như 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 vi (hoặc rõ ràng rõ ràng) ở đây?

85
Peter.O

Cái bẫy là

IFS=; while read..

đặt IFS cho 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 read invocation (ngoại trừ trong Bourne Shell). Bạn có thể kiểm tra xem làm một vòng lặp như

while IFS= read xxx; ... done

rồi sau vòng lặp như vậy, echo "blabalbla $IFS ooooooo" in

blabalbla
 ooooooo

trong khi sau

IFS=; read xxx; ... done

IFS vẫn được xác định lại : now 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.

86
rozcietrzewiacz

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 dấu cách và kết thúc bằng dấu gạch chéo ngược. Trước tiên, hãy xem 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 $text mà không có bất kỳ rủi ro mở rộng). (Phía dưới, $ ‌ là Lời nhắc của 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ứ thứ gì bỏ qua dấu gạch chéo đầ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 biến line. Mặt khác, không gian ban đầu đã bị ăn hết. Đó là bởi vì read đọc 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 chia tách từ đang diễn ra. Chúng tôi thực hiện điều này bằng cách đặt biến IFS thành 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 của read tích hợp . Các IFS= read -r line đặt biến môi trường IFS (thành một giá trị trống) cụ thể để thực hiện read. Đây là một ví dụ của cú pháp chung lệnh đơn giản cú pháp: một chuỗi các phép gán biến (có thể trống) theo sau là 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 là một tích hợp, nê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ị của $IFS là những gì chúng tôi chỉ định ở đó miễn là read đang thực thi¹. Lưu ý rằng read không phải là tích hợp đặc biệt , vì vậy việc chuyển nhượng chỉ kéo dài trong thời gian của nó.

Do đó, chúng tôi chú ý không thay đổi giá trị của IFS cho cá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 đã đặt IFS thành 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 …, sau đó for dir in $PATH sẽ không chia $PATH thành các thành phần tách biệt ruột kết. Nếu mã là IFS=; while read …, điều rõ ràng hơn nữa là IFS không được đặt thành : trong thân vòng lặp.

Tất nhiên, có thể khôi phục giá trị của IFS sau 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= read là cách đơn giản (và, thuận tiện, cũng là cách ngắn nhất).

¹ Và, nếu read bị gián đoạn bởi tín hiệu bị bẫy, có thể trong khi bẫy đang thực thi - điều này không được POSIX chỉ định và phụ thuộc vào Shell trong thực tế.

Ngoài sự khác biệt (đã được làm rõ) IFS phạm vi giữa while IFS='' read, IFS=''; while readwhile IFS=''; read thành ngữ (mỗi lệnh so với tập lệnh/Shell-wide IFS biến phạm vi), bài học mang về nhà là bạn mất vị trí dẫn đầu  dấu cách của một dòng đầu vào nếu biến IFS được đặt thành (chứa a) khoảng trắng.

Đ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"
)
3
jon

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

Nếu bạn muốn đặt IFS thành một ký tự thực tế, điều này có hiệu quả với tôi

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
1
Steven Penny