Đọc từng ký tự với bash đọc


8

Tôi đã cố gắng sử dụng bash để đọc một ký tự tệp theo ký tự.

Sau nhiều thử nghiệm và sai sót, tôi đã phát hiện ra rằng điều này hoạt động:

exec 4<file.txt 
declare -i n
while read -r ch <&4; 
     n=0
     while [ ! $n -eq ${#ch} ]
           do  echo -n "${ch:$n:1}"
               (( n++ ))
          done
     echo "" 
     done

Tức là tôi có thể đọc từng dòng một và sau đó lặp qua từng dòng char theo char.

Trước khi làm điều này, tôi đã thử: exec 4<file.txt && while read -r -n1 ch <&4; do; echo -n "$ch"; done nhưng nó sẽ bỏ qua tất cả các khoảng trắng trong tệp .

Bạn có thể vui lòng giải thích tại sao? Có cách nào để làm cho chiến lược thứ hai (tức là đọc char bằng char với bash's read) không?


4
Đặt IFSthành không có gì để có khoảng trắng tồn tại trong quá trình phân tách từ.
manatwork

Đã thử điều đó với IFS = '', nhưng tôi đoán nó phải chỉ là IFS =. Cảm ơn!
PSkocik

Câu trả lời:


12

Bạn cần xóa các ký tự khoảng trắng khỏi $IFStham số readđể dừng bỏ qua các ký tự đầu và cuối (với -n1ký tự khoảng trắng nếu có cả hai đầu và cuối, do đó bỏ qua):

while IFS= read -rn1 a; do printf %s "$a"; done

Nhưng ngay cả sau đó bash readsẽ bỏ qua các ký tự dòng mới, mà bạn có thể làm việc với:

while IFS= read -rn1 a; do printf %s "${a:-$'\n'}"; done

Mặc dù bạn có thể sử dụng IFS= read -d '' -rn1thay thế hoặc thậm chí tốt hơn IFS= read -N1(được thêm vào 4.1, được sao chép từ ksh93(thêm vào o)) là lệnh để đọc một ký tự.

Lưu ý rằng bash's readkhông thể đối phó với các ký tự NUL. Và ksh93 có vấn đề tương tự như bash.

Với zsh:

while read -ku0 a; do print -rn -- "$a"; done

(zsh có thể đối phó với các ký tự NUL).

Lưu ý rằng những người read -k/n/Nđọc một số ký tự , không phải byte . Vì vậy, đối với các ký tự đa nhân, họ có thể phải đọc nhiều byte cho đến khi một ký tự đầy đủ được đọc. Nếu đầu vào chứa các ký tự không hợp lệ, bạn có thể kết thúc bằng một biến chứa chuỗi byte không tạo thành các ký tự hợp lệ và lớp vỏ có thể sẽ được tính là nhiều ký tự . Ví dụ: trong miền địa phương UTF-8:

$ printf '\375\200\200\200\200ABC' | bash -c '
    IFS= read  -rN1 a; echo "${#a}"'
6

Điều đó \375sẽ giới thiệu một ký tự UTF-8 6 byte. Tuy nhiên, cái thứ 6 ( A) ở trên không hợp lệ cho ký tự UTF-8. Bạn vẫn kết thúc với \375\200\200\200\200Ain $a, được bashtính là 6 ký tự mặc dù 5 ký tự đầu tiên không thực sự là ký tự, chỉ có 5 byte không tạo thành một phần của bất kỳ ký tự nào.


Cảm ơn. Đơn giản và đẹp. Tôi thực sự đã thử một cái gì đó cho đến cuối này (sửa đổi biến IFS), nhưng nó không có tác dụng với tôi nên tôi đã kết thúc với pha chế đó của tôi (Chơi không cần thiết với mô tả tệp, v.v.).
PSkocik

1
Thật thú vị, có vẻ như sử dụng read -rN1thay vì giải quyết vấn đề dòng mới và do đó loại bỏ việc cần phải cung cấp một dòng mới như mặc định khi in $a.
krb686

Chỉ cần FTR tôi đang đọc tệp 411 MB dòng 20 MB. Sử dụng read -n1(char by char) mất 4 phút 51 giây và làm nóng máy tính xách tay lên 90 độ. Sử dụng read -r(theo từng dòng) mất 1,3 giây và máy tính xách tay ở mức 54 độ với quạt kép im lặng.
WinEunuuchs2Unix 17/11/18

2

Đây là một ví dụ đơn giản sử dụng cut, một forvòng lặp & wc:

bytes=$(wc -c < /etc/passwd)
file=$(</etc/passwd)

for ((i=0; i<bytes; i++)); do
    echo $file | cut -c $i
done

KISS phải không?


Nếu đó là KISS, thì bashgiải pháp thuần túy là gì : file="$(</etc/passwd)"; bytes="${#file}"; for ((i=0;i<bytes;i++)); do echo "${file:i:1}"; done?
manatwork

Cảm ơn cả hai nha. Vâng, nếu tôi phải dùng đến việc lấy các ký tự đó từ các dòng, tôi cũng có thể lấy chúng từ toàn bộ tệp. Mặc dù vậy, tôi thấy giải pháp của sch là KISS nhất.
PSkocik

@manatwork Đó là một giải pháp tốt, đơn giản. Mặc dù vậy, có vẻ như tôi thích câu trả lời ở trên bằng cách sử dụng vòng lặp đọc nhanh hơn một chút vì một số lý do. Có lẽ chất nền trong bash khá chậm?
krb686

@ krb686, thực ra là toàn bộ bashnhững thứ quá lớn và quá chậm. theo phần BUGS của trang người đàn ông của nó. Nhưng ngay cả như vậy, vẫn nhanh hơn để cắt một chuỗi trong bộ nhớ hơn là đọc một tệp nhiều lần cho mỗi ký tự. Ít nhất trên máy của tôi: pastebin.com/zH5trQQs
manatwork
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.