Một số người có quan niệm sai lầm đó read
là lệnh đọc một dòng. Không phải vậy.
read
đọc các từ từ một dòng (có thể là dấu gạch chéo tiếp tục), trong đó các từ được $IFS
phân cách và dấu gạch chéo ngược có thể được sử dụng để thoát các dấu phân cách (hoặc tiếp tục dòng).
Cú pháp chung là:
read word1 word2... remaining_words
read
đọc stdin một byte tại một thời điểm cho đến khi nó tìm thấy một ký tự xuống dòng unescaped (hoặc end-of-input), chia tách mà theo quy định phức tạp và lưu trữ các kết quả của tách đó vào $word1
, $word2
... $remaining_words
.
Ví dụ trên một đầu vào như:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
và với giá trị mặc định là $IFS
, read a b c
sẽ gán:
$a
⇐ foo
$b
⇐ bar baz
$c
⇐ blah blahwhatever whatever
Bây giờ nếu chỉ thông qua một đối số, điều đó sẽ không trở thành read line
. Nó vẫn vậy read remaining_words
. Xử lý dấu gạch chéo ngược vẫn được thực hiện, các ký tự khoảng trắng IFS vẫn bị xóa từ đầu và cuối.
Các -r
tùy chọn loại bỏ các xử lý dấu chéo ngược. Vì vậy, cùng một lệnh ở trên với -r
thay vì gán
$a
⇐ foo
$b
⇐ bar\
$c
⇐ baz bl\ah blah\
Bây giờ, đối với phần tách, điều quan trọng là phải nhận ra rằng có hai loại ký tự cho $IFS
: các ký tự khoảng trắng IFS (cụ thể là khoảng trắng và tab (và dòng mới, mặc dù ở đây không quan trọng trừ khi bạn sử dụng -d), điều này cũng xảy ra ở giá trị mặc định của $IFS
) và các giá trị khác. Cách đối xử cho hai lớp nhân vật đó là khác nhau.
Với IFS=:
( :
là không phải là một nhân vật khoảng trắng IFS), một đầu vào như :foo::bar::
sẽ được chia thành ""
, "foo"
, ""
, bar
và ""
(và thêm ""
với một số hiện thực mặc dù điều đó không thành vấn đề trừ read -a
). Trong khi nếu chúng ta thay thế nó :
bằng không gian, thì việc chia tách chỉ được thực hiện foo
và bar
. Đó là hàng đầu và dấu vết bị bỏ qua, và chuỗi của chúng được coi như một. Có các quy tắc bổ sung khi các ký tự khoảng trắng và không khoảng trắng được kết hợp trong $IFS
. Một số triển khai có thể thêm / xóa điều trị đặc biệt bằng cách nhân đôi các ký tự trong IFS ( IFS=::
hoặc IFS=' '
).
Vì vậy, ở đây, nếu chúng ta không muốn xóa các ký tự khoảng trắng không bị bỏ sót hàng đầu và dấu, chúng ta cần xóa các ký tự khoảng trắng IFS khỏi IFS.
Ngay cả với các ký tự không phải khoảng trắng IFS, nếu dòng đầu vào chứa một (và chỉ một) trong số các ký tự đó và đó là ký tự cuối cùng trong dòng (như IFS=: read -r word
trên đầu vào như foo:
) với các vỏ POSIX (không zsh
phải một số pdksh
phiên bản), đầu vào đó được coi là một foo
từ vì trong các shell đó, các ký tự $IFS
được coi là dấu kết , vì vậy word
sẽ chứa foo
, không foo:
.
Vì vậy, cách chuẩn để đọc một dòng đầu vào với read
nội dung là:
IFS= read -r line
(lưu ý rằng đối với hầu hết các read
triển khai, chỉ hoạt động đối với các dòng văn bản vì ký tự NUL không được hỗ trợ ngoại trừ trong zsh
).
Sử dụng var=value cmd
cú pháp đảm bảo IFS
chỉ được đặt khác nhau trong khoảng thời gian của cmd
lệnh đó .
Ghi chú lịch sử
Nội dung read
được giới thiệu bởi trình bao Bourne và đã đọc các từ , không phải các dòng. Có một vài khác biệt quan trọng với đạn POSIX hiện đại.
Các vỏ Bourne read
không hỗ trợ một -r
tùy chọn (được giới thiệu bởi vỏ Korn), vì vậy không có cách nào để vô hiệu hóa xử lý dấu gạch chéo ngoài việc xử lý trước đầu vào với một cái gì đó giống như sed 's/\\/&&/g'
ở đó.
Shell Bourne không có khái niệm về hai lớp nhân vật (một lần nữa được giới thiệu bởi ksh). Trong trình bao Bourne, tất cả các ký tự trải qua xử lý giống như các ký tự khoảng trắng IFS thực hiện trong ksh, đó là IFS=: read a b c
trên một đầu vào như foo::bar
sẽ gán bar
cho $b
, chứ không phải chuỗi rỗng.
Trong vỏ Bourne, với:
var=value cmd
Nếu cmd
là một tích hợp (như read
là), var
vẫn được đặt thành value
sau khi cmd
đã kết thúc. Điều đó đặc biệt quan trọng $IFS
bởi vì trong vỏ Bourne, $IFS
được sử dụng để phân chia mọi thứ, không chỉ các bản mở rộng. Ngoài ra, nếu bạn loại bỏ ký tự khoảng $IFS
trắng trong shell Bourne, sẽ "$@"
không còn hoạt động.
Trong shell Bourne, việc chuyển hướng một lệnh ghép khiến nó chạy trong một lớp con (trong các phiên bản đầu tiên, ngay cả những thứ như read var < file
hoặc exec 3< file; read var <&3
không hoạt động), do đó, hiếm khi sử dụng shell Bourne read
cho mọi thứ trừ đầu vào của người dùng trên thiết bị đầu cuối (trong đó xử lý tiếp tục dòng có ý nghĩa)
Một số Unice (như HP / UX, cũng có một trong util-linux
) vẫn có line
lệnh đọc một dòng đầu vào (trước đây là lệnh UNIX tiêu chuẩn cho đến phiên bản Đặc tả UNIX đơn 2 ).
Điều đó về cơ bản giống như head -n 1
ngoại trừ việc nó đọc một byte mỗi lần để đảm bảo rằng nó không đọc nhiều hơn một dòng. Trên các hệ thống đó, bạn có thể làm:
line=`line`
Tất nhiên, điều đó có nghĩa là sinh ra một quy trình mới, thực hiện một lệnh và đọc đầu ra của nó thông qua một đường ống, do đó kém hiệu quả hơn nhiều so với ksh IFS= read -r line
, nhưng vẫn trực quan hơn rất nhiều.