Để làm điều đó, bạn phải đọc từng ký tự, không phải từng dòng.
Tại sao? Shell rất có thể sử dụng hàm thư viện C tiêu chuẩn read()
để đọc dữ liệu mà người dùng đang nhập và hàm đó trả về số byte thực sự đã đọc. Nếu nó trả về 0, điều đó có nghĩa là nó đã gặp EOF (xem read(2)
hướng dẫn; man 2 read
). Lưu ý rằng EOF không phải là một ký tự mà là một điều kiện, tức là điều kiện "không còn gì để đọc", phần cuối của tệp .
Ctrl+Dgửi một ký tự kết thúc truyền
(mã ký tự EOT, ASCII 4, $'\04'
in bash
) đến trình điều khiển đầu cuối. Điều này có tác dụng gửi bất cứ thứ gì có để gửi đến read()
cuộc gọi chờ của shell.
Khi bạn nhấn Ctrl+Dnửa chừng để nhập văn bản trên một dòng, bất cứ điều gì bạn đã gõ cho đến nay đều được gửi đến trình bao 1 . Điều này có nghĩa là nếu bạn nhập
Ctrl+Dhai lần sau khi gõ một dòng nào đó trên dòng, thì cái đầu tiên sẽ gửi một số dữ liệu và cái thứ hai sẽ không gửi gì , và read()
cuộc gọi sẽ trả về 0 và shell sẽ hiểu là EOF. Tương tự, nếu bạn nhấn Entertheo sau Ctrl+D, shell sẽ nhận EOF ngay lập tức vì không có bất kỳ dữ liệu nào để gửi.
Vậy làm thế nào để tránh phải gõ Ctrl+Dhai lần?
Như tôi đã nói, đọc các ký tự đơn. Khi bạn sử dụng lệnh tích hợp read
shell, nó có thể có bộ đệm đầu vào và yêu cầu read()
đọc tối đa nhiều ký tự từ luồng đầu vào (có thể là 16 kb hoặc hơn). Điều này có nghĩa là shell sẽ nhận được một loạt 16 kb đầu vào, theo sau là một đoạn có thể nhỏ hơn 16 kb, theo sau là byte không (EOF). Khi gặp phải kết thúc của đầu vào (hoặc một dòng mới hoặc một dấu phân cách được chỉ định), điều khiển được trả về tập lệnh.
Nếu bạn sử dụng read -n 1
để đọc một ký tự đơn, shell sẽ sử dụng bộ đệm của một byte đơn trong lệnh gọi của nó read()
, tức là nó sẽ ngồi trong một ký tự đọc chặt chẽ theo ký tự, trả lại quyền điều khiển cho tập lệnh shell sau mỗi ký tự.
Vấn đề duy nhất read -n
là nó đặt thiết bị đầu cuối thành "chế độ thô", có nghĩa là các ký tự được gửi như chúng không có bất kỳ sự giải thích nào. Ví dụ: nếu bạn nhấn Ctrl+D, bạn sẽ nhận được một ký tự EOT theo nghĩa đen trong chuỗi của mình. Vì vậy, chúng tôi phải kiểm tra cho điều đó. Điều này cũng có tác dụng phụ là người dùng sẽ không thể chỉnh sửa dòng trước khi gửi nó tới tập lệnh, ví dụ bằng cách nhấn Backspacehoặc bằng cách sử dụng Ctrl+W(để xóa từ trước đó) hoặc Ctrl+U(để xóa đến đầu dòng) .
Để rút ngắn một câu chuyện dài: Sau đây là vòng lặp cuối cùng mà bash
tập lệnh của bạn
cần thực hiện để đọc một dòng đầu vào, đồng thời cho phép người dùng làm gián đoạn đầu vào bất cứ lúc nào bằng cách nhấn
Ctrl+D:
while true; do
line=''
while IFS= read -r -N 1 ch; do
case "$ch" in
$'\04') got_eot=1 ;&
$'\n') break ;;
*) line="$line$ch" ;;
esac
done
printf 'line: "%s"\n' "$line"
if (( got_eot )); then
break
fi
done
Không đi sâu vào chi tiết về điều này:
IFS=
xóa IFS
biến. Không có điều này, chúng tôi sẽ không thể đọc được không gian. Tôi sử dụng read -N
thay vì read -n
, nếu không chúng tôi sẽ không thể phát hiện dòng mới. Các -r
tùy chọn để read
cho phép chúng tôi để đọc backslashes đúng cách.
Câu case
lệnh tác động lên từng ký tự đọc ( $ch
). Nếu $'\04'
phát hiện EOT ( ), nó đặt got_eot
thành 1 và sau đó rơi vào break
câu lệnh đưa nó ra khỏi vòng lặp bên trong. Nếu một dòng mới ( $'\n'
) được phát hiện, nó sẽ thoát ra khỏi vòng lặp bên trong. Nếu không, nó thêm ký tự vào cuối line
biến.
Sau vòng lặp, dòng được in ra đầu ra tiêu chuẩn. Đây sẽ là nơi bạn gọi tập lệnh hoặc hàm sử dụng "$line"
. Nếu chúng tôi đến đây bằng cách phát hiện EOT, chúng tôi sẽ thoát khỏi vòng lặp ngoài cùng.
1 Bạn có thể kiểm tra điều này bằng cách chạy cat >file
trong một thiết bị đầu cuối và trong một thiết bị đầu cuối tail -f file
khác, sau đó nhập một dòng một phần vào
cat
và nhấn Ctrl+Dđể xem điều gì xảy ra trong đầu ra của tail
.
Đối với ksh93
người dùng: Vòng lặp ở trên sẽ đọc ký tự trả về vận chuyển thay vì ký tự dòng mới ksh93
, điều đó có nghĩa là bài kiểm tra $'\n'
sẽ cần phải thay đổi thành bài kiểm tra $'\r'
. Vỏ cũng sẽ hiển thị những cái này như ^M
.
Để giải quyết vấn đề này:
stty_satted = "$ (stty -g)"
stty -echoctl
# vòng lặp ở đây, với $ '\ n' được thay thế bằng $ '\ r'
stty "$ stty_satted"
Bạn cũng có thể muốn xuất một dòng mới ngay trước khi break
nhận được chính xác hành vi tương tự như trong bash
.
function myfunction { echo "you pressed $1" ; };
và ngay khi tôi nhấn nút điều khiển D kết thúc vòng lặpecho $sentence
được thực hiện và thoát khỏi tập lệnh.