Lực hấp dẫn so với khả năng đọc: Một nền tảng trung gian
Như bạn đã thấy, vấn đề này thừa nhận các giải pháp dài vừa phải và hơi lặp đi lặp lại nhưng rất dễ đọc ( câu trả lời bash của terdon và AB ), cũng như các giải pháp rất ngắn nhưng không trực quan và ít tài liệu hơn ( python của Tim và bash câu trả lời và câu trả lời perl của glenn jackman ). Tất cả những cách tiếp cận này đều có giá trị.
Bạn cũng có thể giải quyết vấn đề này bằng mã ở giữa tính liên tục giữa tính gọn nhẹ và khả năng đọc. Cách tiếp cận này gần như dễ đọc như các giải pháp dài hơn, với độ dài gần với các giải pháp bí truyền nhỏ.
#!/usr/bin/env bash
read -erp 'Enter numeric grade (q to quit): '
case $REPLY in [qQ]) exit;; esac
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; exit; }
done
echo "Grade out of range."
Trong giải pháp bash này, tôi đã bao gồm một số dòng trống để tăng cường khả năng đọc, nhưng bạn có thể xóa chúng nếu bạn muốn nó thậm chí còn ngắn hơn.
Bao gồm các dòng trống, cái này thực sự chỉ ngắn hơn một chút so với một biến thể nhỏ gọn, vẫn dễ đọc của giải pháp bash của AB . Ưu điểm chính của nó so với phương pháp đó là:
- Nó trực quan hơn.
- Dễ dàng thay đổi ranh giới giữa các lớp (hoặc thêm các lớp bổ sung).
- Nó tự động chấp nhận đầu vào với các khoảng trắng ở đầu và cuối (xem bên dưới để được giải thích về cách thức
((
))
hoạt động).
Tất cả ba ưu điểm này phát sinh do phương pháp này sử dụng đầu vào của người dùng làm dữ liệu số thay vì kiểm tra thủ công các chữ số cấu thành của nó.
Làm thế nào nó hoạt động
- Đọc đầu vào từ người dùng. Để họ sử dụng các phím mũi tên để di chuyển xung quanh trong văn bản họ đã nhập (
-e
) và không diễn giải \
thành ký tự thoát ( -r
).
Kịch bản này không phải là một giải pháp giàu tính năng - xem bên dưới để biết cách tinh chỉnh - nhưng những tính năng hữu ích đó chỉ làm cho nó dài hơn hai ký tự. Tôi khuyên bạn nên luôn luôn sử dụng -r
với read
, trừ khi bạn biết bạn cần để cho người dùng \
thoát ra.
- Nếu người dùng viết
q
hoặc Q
, thoát.
- Tạo một mảng kết hợp ( ). Dân số với cấp số cao nhất được liên kết với mỗi cấp chữ cái.
declare -A
- Lặp lại các cấp chữ cái từ thấp nhất đến cao nhất, kiểm tra xem số do người dùng cung cấp có đủ thấp để rơi vào phạm vi số của từng cấp thư không.
Với ((
))
đánh giá số học, tên biến không cần phải được mở rộng với $
. (Trong hầu hết các tình huống khác, nếu bạn muốn sử dụng giá trị của biến thay cho tên của nó, bạn phải thực hiện việc này .)
- Nếu nó rơi vào phạm vi, in lớp và thoát .
Để cho ngắn gọn, tôi sử dụng ngắn mạch và toán tử ( &&
) chứ không phải là if
- then
.
- Nếu vòng lặp kết thúc và không có phạm vi nào được khớp, giả sử số đã nhập quá cao (trên 100) và cho người dùng biết nó nằm ngoài phạm vi.
Cách cư xử này, với đầu vào lạ
Giống như các giải pháp ngắn khác được đăng, tập lệnh đó không kiểm tra đầu vào trước khi cho rằng đó là một số. Đánh giá số học ( ((
))
) tự động loại bỏ khoảng trắng hàng đầu và dấu kiểm, do đó, không có vấn đề gì, nhưng:
- Đầu vào trông không giống một con số nào được hiểu là 0.
- Với đầu vào trông giống như một số (nghĩa là, nếu nó bắt đầu bằng một chữ số) nhưng chứa các ký tự không hợp lệ, tập lệnh sẽ phát ra lỗi.
- Đầu vào nhiều chữ số bắt đầu bằng
0
được hiểu là ở dạng bát phân . Ví dụ: tập lệnh sẽ cho bạn biết 77 là C, trong khi 077 là D. Mặc dù một số người dùng có thể muốn điều này, nhưng hầu hết có thể không và nó có thể gây nhầm lẫn.
- Về mặt tích cực, khi được đưa ra một biểu thức số học, tập lệnh này sẽ tự động đơn giản hóa nó và xác định loại chữ liên quan. Chẳng hạn, nó sẽ cho bạn biết 320/4 là B.
Phiên bản mở rộng, đầy đủ tính năng
Vì những lý do đó, bạn có thể muốn sử dụng một cái gì đó như tập lệnh mở rộng này để kiểm tra để đảm bảo đầu vào tốt và bao gồm một số cải tiến khác.
#!/usr/bin/env bash
shopt -s extglob
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
case $REPLY in # allow leading/trailing spaces, but not octal (e.g. "03")
*( )@([1-9]*([0-9])|+(0))*( )) ;;
*( )[qQ]?([uU][iI][tT])*( )) exit;;
*) echo "I don't understand that number."; continue;;
esac
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
Đây vẫn là một giải pháp khá nhỏ gọn.
Những tính năng này thêm gì?
Các điểm chính của tập lệnh mở rộng này là:
- Xác nhận đầu vào. terdon của kiểm tra kịch bản đầu vào với , vì vậy tôi cho thấy một cách khác, mà hy sinh một số ngắn gọn nhưng là mạnh mẽ hơn, cho phép người dùng nhập vào ở đầu và đuôi không gian và từ chối cho phép một biểu thức mà có thể hoặc có thể không được dự định như bát phân (trừ khi đó là zero) .
if [[ ! $response =~ ^[0-9]*$ ]] ...
- Tôi đã sử dụng
case
với tính năng mở rộng thay vì [[
với toán tử =~
khớp biểu thức chính quy (như trong câu trả lời của terdon ). Tôi đã làm điều đó để chỉ ra rằng (và làm thế nào) nó cũng có thể được thực hiện theo cách đó. Globs và regexps là hai cách chỉ định các mẫu phù hợp với văn bản và một trong hai phương pháp đều tốt cho ứng dụng này.
- Giống như tập lệnh bash của AB , tôi đã bao gồm toàn bộ nội dung trong một vòng lặp bên ngoài (ngoại trừ việc tạo
cutoffs
mảng ban đầu ). Nó yêu cầu số và đưa ra các cấp chữ cái tương ứng miễn là đầu vào thiết bị đầu cuối có sẵn và người dùng đã không yêu cầu nó thoát. Đánh giá bởi do
... done
xung quanh mã trong câu hỏi của bạn, có vẻ như bạn muốn điều đó.
- Để làm cho việc bỏ thuốc dễ dàng, tôi chấp nhận bất kỳ biến thể không phân biệt chữ hoa chữ thường
q
hoặc quit
.
Kịch bản này sử dụng một vài cấu trúc có thể xa lạ với người mới; chúng chi tiết dưới đây.
Giải thích: Sử dụng continue
Khi tôi muốn bỏ qua phần còn lại của cơ thể của while
vòng lặp bên ngoài , tôi sử dụng continue
lệnh. Điều này đưa nó trở lại lên đầu vòng lặp, để đọc thêm đầu vào và chạy một lần lặp khác.
Lần đầu tiên tôi làm điều này, vòng lặp duy nhất tôi tham gia là while
vòng lặp bên ngoài , vì vậy tôi có thể gọi continue
mà không có đối số. (Tôi đang ở trong một case
cấu trúc, nhưng điều đó không ảnh hưởng đến hoạt động của break
hoặc continue
.)
*) echo "I don't understand that number."; continue;;
Tuy nhiên, lần thứ hai, tôi ở trong một for
vòng lặp bên trong , chính nó được lồng bên trong while
vòng lặp bên ngoài . Nếu tôi sử dụng continue
không có đối số, điều này sẽ tương đương continue 1
và sẽ tiếp tục for
vòng lặp bên trong thay vì while
vòng lặp bên ngoài .
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Vì vậy, trong trường hợp đó, tôi sử dụng continue 2
để thực hiện bash find và tiếp tục vòng lặp thứ hai.
Giải thích: case
Nhãn có Globs
Tôi không sử dụng case
để tìm ra thùng thư nào mà một số rơi vào (như trong câu trả lời bash của AB ). Nhưng tôi sử dụng case
để quyết định xem có nên xem xét đầu vào của người dùng không:
- một số hợp lệ,
*( )@([1-9]*([0-9])|+(0))*( )
- lệnh bỏ,
*( )[qQ]?([uU][iI][tT])*( )
- bất cứ điều gì khác (và do đó đầu vào không hợp lệ),
*
Đây là những quả cầu vỏ .
- Mỗi cái được theo sau bởi một
)
cái không khớp với bất kỳ mở nào (
, đó là case
cú pháp để tách một mẫu khỏi các lệnh chạy khi nó được khớp.
;;
là case
cú pháp để chỉ ra sự kết thúc của các lệnh để chạy cho khớp trường hợp paticular (và không có trường hợp nào tiếp theo nên được kiểm tra sau khi chạy chúng).
Globing shell thông thường cung cấp *
để khớp 0 hoặc nhiều ký tự, ?
để khớp chính xác một ký tự và các lớp / phạm vi ký tự trong [
]
ngoặc. Nhưng tôi đang sử dụng Globing mở rộng , vượt xa điều đó. Mở rộng toàn cầu được bật theo mặc định khi sử dụng bash
tương tác, nhưng bị tắt theo mặc định khi chạy tập lệnh. Các shopt -s extglob
lệnh ở phía trên cùng của kịch bản biến nó trên.
Giải thích: Globbing mở rộng
*( )@([1-9]*([0-9])|+(0))*( )
, kiểm tra đầu vào số , khớp với một chuỗi:
- Không hoặc nhiều khoảng trắng (
*( )
). Cấu *(
)
trúc khớp với 0 hoặc nhiều mẫu trong ngoặc đơn, ở đây chỉ là một khoảng trắng.
Thực tế, có hai loại khoảng trắng ngang, khoảng trắng và tab và thường thì cũng mong muốn khớp với các tab. Nhưng tôi không lo lắng về điều đó ở đây, bởi vì tập lệnh này được viết cho thủ công, đầu vào tương tác và -e
cờ để read
cho phép đọc đường dẫn GNU. Điều này là để người dùng có thể di chuyển qua lại trong văn bản của họ bằng các phím mũi tên trái và phải, nhưng nó có tác dụng phụ là thường ngăn các tab được nhập theo nghĩa đen.
- Một lần xuất hiện (
@(
)
) của một trong hai ( |
):
- Một chữ số khác không (
[1-9]
) theo sau là 0 hoặc nhiều hơn ( *(
)
) của bất kỳ chữ số nào ( [0-9]
).
- Một hoặc nhiều (
+(
)
) của 0
.
- Không hoặc nhiều khoảng trắng (
*( )
), một lần nữa.
*( )[qQ]?([uU][iI][tT])*( )
, kiểm tra lệnh thoát , khớp với một chuỗi:
- Không hoặc nhiều khoảng trắng (
*( )
).
q
hoặc Q
( [qQ]
).
- Tùy chọn - tức là không hoặc một lần xuất hiện (
?(
)
) - của:
u
hoặc U
( [uU]
) theo sau i
hoặc I
( [iI]
) theo sau t
hoặc T
( [tT]
).
- Không hoặc nhiều khoảng trắng (
*( )
), một lần nữa.
Biến thể: Xác thực đầu vào bằng biểu thức chính quy mở rộng
Nếu bạn muốn kiểm tra đầu vào của người dùng dựa trên biểu thức chính quy thay vì toàn cầu shell, bạn có thể thích sử dụng phiên bản này, hoạt động tương tự nhưng sử dụng [[
và =~
(như trong câu trả lời của terdon ) thay vì case
và toàn cầu hóa mở rộng.
#!/usr/bin/env bash
shopt -s nocasematch
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
# allow leading/trailing spaces, but not octal (e.g., "03")
if [[ ! $REPLY =~ ^\ *([1-9][0-9]*|0+)\ *$ ]]; then
[[ $REPLY =~ ^\ *q(uit)?\ *$ ]] && exit
echo "I don't understand that number."; continue
fi
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
Ưu điểm có thể có của phương pháp này là:
Trong trường hợp cụ thể này, cú pháp đơn giản hơn một chút, ít nhất là trong mẫu thứ hai, nơi tôi kiểm tra lệnh thoát. Điều này là do tôi có thể đặt nocasematch
tùy chọn shell, và sau đó tất cả các biến thể trường hợp q
và quit
được bao phủ tự động.
Đó là những gì shopt -s nocasematch
lệnh làm. Các shopt -s extglob
lệnh được bỏ qua như globbing không được sử dụng trong phiên bản này.
Các kỹ năng diễn đạt thông thường là phổ biến hơn là thành thạo các biểu hiện của bash.
Giải thích: Biểu thức chính quy
Đối với các mẫu được chỉ định ở bên phải của =~
toán tử, đây là cách các biểu thức chính quy đó hoạt động.
^\ *([1-9][0-9]*|0+)\ *$
, kiểm tra đầu vào số , khớp với một chuỗi:
- Bắt đầu - tức là cạnh trái - của dòng (
^
).
- Không hoặc nhiều hơn (
*
, hậu tố được áp dụng). Một không gian thông thường không cần phải được định \
nghĩa trong một biểu thức thông thường, nhưng điều này là cần thiết [[
để ngăn ngừa lỗi cú pháp.
- Một chuỗi con (
(
)
) là một hoặc khác ( |
) của:
[1-9][0-9]*
: một chữ số khác ( [1-9]
) theo sau là 0 hoặc nhiều hơn ( *
, hậu tố được áp dụng) của bất kỳ chữ số nào ( [0-9]
).
0+
: một hoặc nhiều ( +
, hậu tố được áp dụng) của 0
.
- Không hoặc nhiều khoảng trắng (
\ *
), như trước đây.
- Kết thúc - tức là cạnh phải - của dòng (
$
).
Không giống như case
các nhãn khớp với toàn bộ biểu thức đang được kiểm tra, =~
trả về giá trị true nếu bất kỳ phần nào trong biểu thức bên trái của nó khớp với mẫu được đưa ra dưới dạng biểu thức bên phải của nó. Đây là lý do tại sao ^
và $
các neo, chỉ định điểm bắt đầu và kết thúc của dòng, là cần thiết ở đây, và không tương ứng về mặt cú pháp với bất cứ điều gì xuất hiện trong phương thức với case
và extglobs.
Các dấu ngoặc đơn là cần thiết để thực hiện ^
và $
liên kết với sự phân biệt của [1-9][0-9]*
và 0+
. Mặt khác, nó sẽ là sự phân biệt của ^[1-9][0-9]*
và 0+$
, và khớp với bất kỳ đầu vào nào bắt đầu bằng một chữ số khác hoặc kết thúc bằng một 0
(hoặc cả hai, vẫn có thể bao gồm các chữ số không ở giữa).
^\ *q(uit)?\ *$
, kiểm tra lệnh thoát , khớp với một chuỗi:
- Đầu dòng (
^
).
- Không hoặc nhiều khoảng trắng (
\ *
, xem phần giải thích ở trên).
- Bức thư
q
. Hoặc Q
, kể từ khi shopt nocasematch
được kích hoạt.
- Tùy chọn - tức là không hoặc một lần xuất hiện (postfix
?
) - của chuỗi con ( (
)
):
u
, theo sau i
, tiếp theo t
. Hoặc, vì shopt nocasematch
được kích hoạt u
có thể được U
; độc lập, i
có thể I
; và độc lập, t
có thể T
. (Đó là, các khả năng không giới hạn ở uit
và UIT
.)
- Không hoặc nhiều khoảng trắng nữa (
\ *
).
- Kết thúc dòng (
$
).