Cách đọc dòng hoàn chỉnh trong vòng lặp 'for' với dấu cách


130

Tôi đang cố gắng chạy một forvòng lặp cho tập tin và tôi muốn hiển thị toàn bộ dòng. Nhưng thay vào đó nó chỉ hiển thị từ cuối cùng. Tôi muốn dòng hoàn chỉnh.

for j in `cat ./file_wget_med`

do
echo $j

done

kết quả sau khi chạy:

Found.

Đây là dữ liệu của tôi:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.

Câu trả lời:


179

forvòng lặp phân tách khi nhìn thấy bất kỳ khoảng trắng nào như khoảng trắng, tab hoặc dòng mới. Vì vậy, bạn nên sử dụng IFS (Dấu tách trường nội bộ) :

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!

5
Và quan tâm đến các trích dẫn đơn trên IFS vì IFS = $ "\ n" sẽ phân chia các chuỗi "nn". Tôi thấy thiết lập IFS đáng tin cậy hơn là sử dụng các cú pháp hoặc hàm phức tạp hơn.
erm3nda

23
rõ ràng nó được khuyên là unset IFSsau đó, để các lệnh khác không bị ảnh hưởng trong cùng lớp vỏ này.
Boris Däppen

2
$nghĩa là IFS=$'\n'gì trong ?
Ren

2
$làm cho ngắt dòng hoạt động bên trong chuỗi. Điều này cũng nên được thực hiện với local IFS=$'\n'bên trong một chức năng.
Andy Ray

1
@Renn đó là $'...'tiếng " ANSI-C Trích dẫn "
αғsнιη

82

forcác vòng lặp phân chia trên bất kỳ khoảng trắng (không gian, tab, dòng mới) theo mặc định; cách dễ nhất để làm việc trên một dòng tại một thời điểm là sử dụng một while readvòng lặp thay thế, phân tách trên dòng mới:

while read i; do echo "$i"; done < ./file_wget_med

Tôi hy vọng lệnh của bạn sẽ nhổ ra một từ trên mỗi dòng (đó là những gì đã xảy ra khi tôi kiểm tra nó bằng một tệp của riêng tôi). Nếu có điều gì khác đang xảy ra, tôi không chắc điều gì có thể gây ra nó.


8
Đây cũng là một thay thế tốt cho for i in `ls`; do echo $1; done, bằng cách chuyển đầu ra sang lệnh while : ls|while read i; do echo $i; done. Điều này hoạt động trên tên tệp / thư mục có khoảng trắng, mà không cần thay đổi các biến môi trường. Câu trả lời rất hay.
jishi

trong khi đó không xử lý các dòng đầu tiên và cuối cùng, không phải là vòng lặp for với IFS
Grantbow

@jishi Vì nó chủ yếu được thiết kế để hiển thị và đáp ứng với kích thước cửa sổ đầu cuối và như vậy, lskhông được coi là an toàn để sử dụng với |(người vận hành đường ống). findlinh hoạt hơn ngoài việc an toàn hơn cho mục đích này.
SeldomNeedy

3
Đây là một câu trả lời TUYỆT VỜI, tôi đã sử dụng nó để biến một danh sách tôi có thành các mục PLIST cho một ứng dụng IOS tôi đang phát triển trên mac OSX. Tôi đã thay thế đầu vào tệp bằng cách đặt clipboad bằng pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rich

3
ls -1có thể được sử dụng với |để đảm bảo đầu ra không được định dạng một dòng
AndreyS Scherbakov

19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Đã xác minh làm việc, không phải là một lớp lót bạn có thể muốn nhưng không thể thực hiện một cách thanh lịch như vậy.


1
một chuỗi ở đây! thanh lịch nhất trong tất cả các giải pháp khác IMO
Eliran Malka

5

Đây là một phần mở rộng nhỏ cho câu trả lời của Mitchell Currie, mà tôi thích vì phạm vi tác dụng phụ nhỏ, tránh việc phải đặt một biến:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"

1
Bạn có thể loại bỏ -name '*'và nó sẽ giống nhau, không?
wjandrea

1

Tôi sẽ viết nó như thế này:

cat ./file_wget_med | while read -r j
do
    echo $j
done

vì nó yêu cầu ít thay đổi nhất trong tập lệnh gốc (ngoại trừ giải pháp sử dụng IFS, nhưng nó sửa đổi bashhành vi không chỉ cho câu lệnh điều khiển vòng lặp này).


1
Ống và catkhông cần thiết ở đây. whileloop chấp nhận chuyển hướng tốt, đó là lý do tại sao điều này thường được viết làwhile IFS= read -r line; do...done < input.txt
Sergiy Kolodyazhnyy

0

Mapfile là một cách thuận tiện để đọc các dòng từ tệp vào một mảng được lập chỉ mục, không dễ mang theo như đọc nhưng nhanh hơn một chút. Bằng cách sử dụng vòng lặp for, bạn tránh tạo ra một subshell.

#!/bin/bash

mapfile -t < file.txt

for line in "${MAPFILE[@]}"; do
    echo $line
done

Hãy ghi nhớ khi sử dụng các đường ống, nó sẽ đặt vòng lặp while trong một khung con. Các thay đổi bên trong vòng lặp while như các biến sẽ không lan truyền đến phần bên ngoài của tập lệnh.

Thí dụ:

#!/bin/bash

a=0
printf %s\\n {0..5} | while read; do
  ((a++))
done
echo $a # 'a' will always be 0.

(Giải pháp tốt hơn):

#!/bin/bash

b=0
while read; do
  ((b++))
done < <(printf %s\\n {0..5})

echo $b # 'b' equal to 6 (works as expected).

-1

Dandalf đã thực sự gần với một giải pháp chức năng, nhưng người ta KHÔNG nên cố gắng gán kết quả của số lượng đầu vào không xác định (nghĩa là find ~/.gdfuse -name '*') cho các biến! Hoặc, ít nhất là đang cố gắng thực hiện một điều như vậy thông qua một biến mảng; nếu bạn cứ khăng khăng như vậy! Vì vậy, đây là giải pháp của Dandalf được thực hiện mà không cần thao tác nguy hiểm; và tất cả trong một dòng

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'

Xin chào @odoncaoa, tôi đã thử thực hiện lệnh find mà không có dấu ngoặc kép, nhưng nó làm cho tất cả các kết quả xuất hiện dưới dạng một dòng. Tôi bối rối về "số lượng đầu vào không xác định" với lệnh này và những nguy hiểm liên quan. Bạn có thể cung cấp một ví dụ về những nguy hiểm và làm thế nào chúng có thể xảy ra trên lý thuyết? Tôi thực sự không muốn lười biếng về điều đó, tôi chỉ thoải mái hơn trong các ngôn ngữ lập trình khác.
Dandalf
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.