Đọc tệp định hướng dòng có thể không kết thúc bằng dòng mới


11

Tôi có một tệp có tên /tmp/urlFiletrong đó mỗi dòng đại diện cho một url. Tôi đang cố đọc từ tệp như sau:

cat "/tmp/urlFile" | while read url
do
    echo $url
done

Nếu dòng cuối cùng không kết thúc bằng ký tự dòng mới, dòng đó sẽ không được đọc. Tôi đã tự hỏi tại sao?

Có thể đọc tất cả các dòng, bất kể chúng có kết thúc bằng một dòng mới hay không?


8
Nó được thảo luận tại Tại sao sử dụng vòng lặp shell để xử lý văn bản được coi là thực tiễn xấu? (với một số cách để làm điều đó)
Stéphane Chazelas

2
Hah @ Stéphane Tôi thích TBD ở đó ;-).
Stephen Kitt

2
Một cách khác để thêm dòng mới nếu nó bị thiếu; awk 1 /tmp/urlFile.. vì vậyawk 1 /tmp/urlFile | while ...
muru

@muru, đó là một câu trả lời tốt hơn bất kỳ câu trả lời nào khác ở đây.
tự đại diện

1
Vì bạn đang hỏi tại sao nó không được đọc: stackoverflow.com/a/729795/1968
Konrad Rudolph

Câu trả lời:


13

Bạn sẽ làm:

while IFS= read -r url || [ -n "$url" ]; do
  printf '%s\n' "$url"
done < url.list

(hiệu quả, vòng lặp đó thêm lại dòng mới bị thiếu trên dòng cuối cùng (không phải)).

Xem thêm:


Cảm ơn. Tôi đã đọc các bài viết được liên kết và có thể tôi bỏ lỡ điều gì đó, tại sao "vòng lặp đó lại thêm dòng mới bị thiếu trên dòng cuối cùng (không phải)"?
Tim

1
@Tim Điều mà Stephane có nghĩa là nó bổ sung lại dòng mới bị thiếu trong đầu ra vì tất cả các printfcuộc gọi ở đây đều có \n.
Sergiy Kolodyazhnyy

6

Điều này dường như được giải quyết một phần với readarray -t:

readarray -t urls "/tmp/urlFile"
for url in "${urls[@]}"; do
    printf '%s\n' "$url"
done

Tuy nhiên, xin lưu ý rằng mặc dù điều này không hoạt động đối với các tệp có kích thước hợp lý, giải pháp này đưa ra một vấn đề mới tiềm ẩn với các tệp rất lớn - trước tiên, nó đọc tệp thành một mảng mà sau đó phải lặp đi lặp lại. Đối với các tệp rất lớn, điều này có thể tiêu tốn cả thời gian và bộ nhớ, có khả năng đến mức không thành công.


Cảm ơn. Phần nào nó giải quyết và phần nào không?
Tim

Nó giải quyết vấn đề với việc thiếu một dòng mới, nhưng đưa ra một vấn đề mới tiềm ẩn với các tệp rất lớn, vì trước tiên nó đọc tệp thành một mảng mà sau đó phải lặp đi lặp lại.
DopeGhoti

1
@DopeGhoti Đó là thông tin tốt - tôi có thể đề nghị bạn thêm nó trực tiếp vào câu trả lời không?
RJHunter

Tha trả lời đã được sửa đổi.
DopeGhoti

5

Theo định nghĩa , một tệp văn bản bao gồm một chuỗi các dòng. Một dòng kết thúc với một ký tự dòng mới. Do đó, một tệp văn bản kết thúc bằng một ký tự dòng mới, trừ khi nó trống.

Nội dung readchỉ có nghĩa là để đọc các tập tin văn bản. Bạn không truyền tệp văn bản, vì vậy bạn không thể hy vọng nó hoạt động trơn tru. Shell đọc tất cả các dòng - những gì nó bỏ qua là các ký tự phụ sau dòng cuối cùng.

Nếu bạn có một tệp đầu vào có khả năng không đúng định dạng có thể thiếu dòng cuối cùng của nó, bạn có thể thêm một dòng mới vào nó, chỉ để chắc chắn.

{ cat "/tmp/urlFile"; echo; } | 

Các tệp phải là tệp văn bản nhưng thiếu dòng mới cuối cùng thường được tạo bởi các biên tập viên Windows. Điều này thường đi cùng với các kết thúc dòng Windows, đó là CR LF, trái ngược với LF của Unix. Ký tự CR hiếm khi hữu ích ở mọi nơi và không thể xuất hiện trong URL trong mọi trường hợp, vì vậy bạn nên xóa chúng.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | 

Trong trường hợp tệp đầu vào được định dạng tốt và kết thúc bằng một dòng mới, thì echothêm một dòng trống. Vì các URL không thể để trống, chỉ cần bỏ qua các dòng trống.

Cũng lưu ý rằng readkhông đọc các dòng theo cách đơn giản. Nó bỏ qua khoảng trắng hàng đầu và dấu, mà đối với một URL có thể là mong muốn. Nó xử lý dấu gạch chéo ngược ở cuối dòng là một ký tự thoát, làm cho dòng tiếp theo được nối với dấu đầu tiên trừ chuỗi backslash-newline, điều này chắc chắn không được mong muốn. Vì vậy, bạn nên vượt qua -rtùy chọn để read. Nó là rất, rất hiếm readkhi là điều đúng hơn là read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done

3

Chà, readtrả về một giá trị giả nếu nó đáp ứng cuối tập tin trước một dòng mới, nhưng ngay cả khi có, nó vẫn gán giá trị mà nó đọc được. Vì vậy, chúng ta có thể kiểm tra xem cuộc gọi cuối cùng có readtrả về thứ gì khác ngoài một dòng trống hay không và xử lý nó như bình thường. Vì vậy, chỉ thoát khỏi vòng lặp sau khi readtrả về false dòng trống:

#!/bin/sh
while IFS= read -r line || [ "$line" ]; do 
    echo "line: $line"
done

$ printf 'foo\nbar' | sh ./read.sh 
line: foo
line: bar
$ printf 'foo\nbar\n' | sh ./read.sh 
line: foo
line: bar

1

Một cách khác sẽ như thế này:

Khi đọc đến cuối tập tin thay vì cuối dòng, nó đọc dữ liệu và gán nó cho các biến, nhưng nó thoát ra với trạng thái khác không. Nếu vòng lặp của bạn được xây dựng "trong khi đọc, làm công cụ; thực hiện

Vì vậy, thay vì kiểm tra trạng thái thoát trực tiếp, hãy kiểm tra cờ và đặt lệnh đọc đặt cờ đó từ bên trong thân vòng lặp. Theo cách đó, bất kể trạng thái thoát, toàn bộ thân vòng lặp đều chạy, bởi vì read chỉ là một trong danh sách các lệnh trong vòng lặp như bất kỳ lệnh nào khác, không phải là yếu tố quyết định xem vòng lặp có chạy hay không.

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY 
done < /tmp/urlFile

Được giới thiệu từ đây .


1
mèo "/ tmp / urlFile" | trong khi đọc url
làm
    echo $ url
làm xong

Đây là một sử dụng vô dụng củacat .

Trớ trêu thay, bạn có thể thay thế catquy trình ở đây bằng một thứ thực sự hữu ích: một công cụ mà các hệ thống POSIX có để thêm dòng mới bị thiếu và biến tệp thành tệp văn bản POSIX thích hợp.

sed -e '$ a \' "/ tmp / urlFile" | trong khi đọc url -r
làm
    printf "% s \ n" "$ {url}"
làm xong

đọc thêm


1
Hành vi của sed không được chỉ định bởi POSIX khi đầu vào không kết thúc bằng ký tự dòng mới; ngoài ra khi có các dòng lớn hơn LINE_MAX, trong khi hành vi của readđược chỉ định trong các trường hợp đó.
Stéphane Chazelas
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.