Стислість проти читабельності: середина
Як ви вже бачили, це завдання допускає до рішень, які помірно довгі і кілька повторюваних , але дуже читається ( terdon - х і AB в Баш відповіді), а також ті , які є дуже короткими , але не інтуїтивно і набагато менш самодокументірован (Тіма пітон і Баш відповіді і Glenn Джекмана Perl відповідь ). Всі ці підходи цінні.
Ви також можете вирішити цю проблему за допомогою коду в середині континууму між компактністю та читабельністю. Цей підхід є майже таким же читабельним, як і більш довгі рішення, з довжиною ближче до малих езотеричних рішень.
#!/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."
У це рішення bash я включив кілька порожніх рядків для підвищення читабельності, але ви можете їх видалити, якби хотіли ще коротше.
Порожні рядки включені, це насправді лише трохи коротше компактифицированного, все ще досить читається варіант з розчину Баша АБ . Основними його перевагами перед цим методом є:
- Це більш інтуїтивно зрозуміло.
- Простіше змінити межі між оцінками (або додати додаткові оцінки).
- Він автоматично приймає вхід з провідними та кінцевими пробілами (див. Нижче для пояснення того, як
(( ))працює).
Усі ці три переваги виникають тому, що цей метод використовує введення користувача як числові дані, а не вручну вивчаючи його складові цифри.
Як це працює
- Прочитайте вхід від користувача. Нехай вони використовують клавіші зі стрілками для переміщення в тексті, який вони ввели (
-e), а не інтерпретують \як символ втечі ( -r).
Цей скрипт не є багатофункціональним рішенням - див. Нижче для уточнення - але ці корисні функції роблять його на два символи довше. Я рекомендую завжди використовувати -rз read, якщо ви не знаєте , що вам потрібно , щоб дозволити користувачеві харчування \пагонів.
- Якщо користувач написав
qабо Q, вийдіть.
- Створіть асоціативний масив (
declare -A). Населяйте його найвищим числовим класом, пов’язаним із кожним класом букви.
- Проведіть курси через літери від найнижчих до найвищих, перевіряючи, чи вказане користувачем число є достатньо низьким, щоб потрапити в числовий діапазон кожної літери.
За допомогою (( ))арифметичного оцінювання імена змінних не потрібно розширювати $. (У більшості інших ситуацій, якщо ви хочете використовувати значення змінної замість її імені, ви повинні зробити це .)
- Якщо він потрапляє в діапазон, роздрукуйте оцінку та вийдіть .
Для стислості я використовую коротке замикання та оператор ( &&), а не if- then.
- Якщо цикл закінчується, і діапазон не збігається, припустіть, що введене число є занадто високим (понад 100) і повідомте користувачеві, що він був поза діапазоном.
Як це поводиться, із дивним входом
Як і інші опубліковані короткі рішення, цей скрипт не перевіряє вхід, перш ніж вважати, що це число. Арифметичне оцінювання ( (( ))) автоматично знімає пробіли та пробіли, тому це не проблема, але:
- Введення, яке зовсім не схоже на число, інтерпретується як 0.
- З введенням, яке виглядає як число (тобто, якщо воно починається з цифри), але містить недійсні символи, сценарій видає помилки.
- Мульти-значний вхід починаючи з
0буде інтерпретуватися як в вісімковій системі . Наприклад, сценарій скаже вам, що 77 - це C, а 077 - D. Хоча деякі користувачі можуть цього хотіти, швидше за все, це не може, і це може спричинити плутанину.
- З боку плюс, якщо йому дано арифметичний вираз, цей скрипт автоматично спрощує його і визначає пов'язаний з ним літер. Наприклад, він скаже вам, що 320/4 - це B.
Розширена, повністю представлена версія
З цих причин ви можете використовувати щось на кшталт цього розширеного сценарію, який перевіряє, чи є вхід хорошим, і включає деякі інші вдосконалення.
#!/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
Це все ще досить компактне рішення.
Які функції додає це?
Ключові моменти цього розширеного сценарію:
- Перевірка вводу сценарій terdon перевіряє вхід за допомогою
if [[ ! $response =~ ^[0-9]*$ ]] ... , так що я покажу ще один спосіб, який жертвує стислість , але є більш надійним, дозволяючи користувачеві ввести початкові і кінцеві пробіли і відмовляючи вираз , яке може або не може бути призначений як восьмеричні (якщо це не нуль) .
- Я використовував
caseз розширеною підстановкою замість [[з =~ регулярними виразами оператора (як в відповіді terdon в ). Я зробив це, щоб показати, що (і як) це також можна зробити так. Глобуси та регулярні виразки - це два способи визначення шаблонів, що відповідають тексту, і будь-який метод чудово підходить для цієї програми.
- Як і сценарій bash AB , я все це уклав у зовнішній цикл (за винятком початкового створення
cutoffsмасиву). Він запитує цифри і дає відповідні оцінки літер, поки доступний термінальний вхід і користувач не сказав йому виходити. Судячи з do... doneнавколо коду у вашому запитанні, схоже, ви цього хочете.
- Щоб полегшити вихід, я приймаю будь-який нечутливий до випадку варіант
qабо quit.
Цей сценарій використовує кілька конструкцій, які можуть бути незнайомі новачкам; вони детальніше описані нижче.
Пояснення: Використання continue
Коли я хочу пропустити решту тіла зовнішньої whileпетлі, я використовую continueкоманду. Це повертає його вгору до вершини циклу, щоб прочитати більше введення та запустити ще одну ітерацію.
Перший раз, коли я це роблю, єдиний цикл, в якому я перебуваю, - це зовнішній whileцикл, тому я можу зателефонувати continueбез аргументів. (Я в caseконструкції, але це не впливає на роботу breakабо continue.)
*) echo "I don't understand that number."; continue;;
Однак другий раз я перебуваю у внутрішній forпетлі, яка сама вкладена у зовнішню whileпетлю. Якби я continueне мав аргументів, це було б рівнозначно continue 1і продовжувало б внутрішній forцикл замість зовнішнього whileциклу.
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Тож у такому випадку я використовую continue 2для пошуку bash і замість цього продовжую другу петлю.
Пояснення: caseМітки з глобусами
Я не використовую , caseщоб з'ясувати , яка буква класу бен число потрапляє (як в Баш відповідь АБ ). Але я використовую, caseщоб вирішити, чи слід враховувати дані користувача:
- дійсне число,
*( )@([1-9]*([0-9])|+(0))*( )
- команда вийти,
*( )[qQ]?([uU][iI][tT])*( )
- що-небудь інше (і, таким чином, недійсне введення),
*
Це черепашки .
- Кожен супроводжується тим,
)що не відповідає жодному отвору (, який єcase синтаксисом для відокремлення шаблону від команд, які виконуються при його зіставленні.
;;- це caseсинтаксис для вказівки кінця команд для запуску патикулярного відповідника (і що після їх запуску не слід перевіряти наступні випадки).
Звичайна глобальна оболонка оболонки забезпечує узгодження *нуля або більше символів, ?відповідність точно одному символу та класи / діапазони символів у [ ]дужках. Але я використовую розширений глобус , що виходить за рамки цього. Розширений глобул увімкнено за замовчуванням при bashінтерактивному використанні , але відключений за замовчуванням під час запуску сценарію. shopt -s extglobКоманди у верхній частині сценарію перетворює його.
Пояснення: розширене глобування
*( )@([1-9]*([0-9])|+(0))*( ), що перевіряє чисельний ввід , відповідає послідовності:
- Нульовий або більше пробілів (
*( )). У *( )конструкт відповідає нулю або більше шаблону в дужках, що тут є тільки простір.
Насправді існує два види горизонтального простору, пробіли та вкладки, і часто бажано також відповідати вкладкам. Але я не турбуюся про це тут, тому що цей сценарій написаний для ручного, інтерактивного введення, а -eпрапор readдозволяє включити читання GNU. Це так, що користувач може рухатись вперед та назад у тексті за допомогою клавіш зі стрілками вліво та вправо, але це побічний ефект, як правило, запобігає буквальному введенню вкладок.
- Одне виникнення (
@( )) будь-якого ( |):
- Ненульова цифра (
[1-9]) з наступним нулем або більше ( *( )) будь-якої цифри ( [0-9]).
- Один або більше (
+( )) з 0.
- Нуль або більше пробілів (
*( )), знову.
*( )[qQ]?([uU][iI][tT])*( ), яка перевіряє команду quit , відповідає послідовності:
- Нульовий або більше пробілів (
*( )).
qабо Q( [qQ]).
- Необов'язково - тобто нуль або один вхід (
?( )) - з:
uабо U( [uU]), за яким слідує iабо I( [iI]), після tабо T( [tT]).
- Нуль або більше пробілів (
*( )), знову.
Варіант: Підтвердження введення з розширеним регулярним виразом
Якщо ви віддаєте перевагу тестуванню вводу користувача на регулярний вираз, а не на оболонці, ви можете скористатися цією версією, яка працює так само, але використовує [[та =~(як у відповіді тердона ) замість caseта розширеного глобулювання.
#!/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
Можливими перевагами такого підходу є те, що:
У цьому конкретному випадку синтаксис трохи простіший, принаймні, у другому шаблоні, де я перевіряю на команду quit. Це тому, що мені вдалося встановити параметр nocasematchоболонки, а потім усі варіанти регістрів qі quitавтоматично покривалися.
Ось що shopt -s nocasematchробить команда. shopt -s extglobКоманда опущена , як підстановка не використовується в даній версії.
Навички регулярного висловлювання частіше зустрічаються, ніж володіння екзоглобами Баша.
Пояснення: Регулярні вирази
Що стосується шаблонів, зазначених праворуч від =~оператора, ось як працюють ці регулярні вирази.
^\ *([1-9][0-9]*|0+)\ *$, що перевіряє чисельний ввід , відповідає послідовності:
- Початок - тобто лівий край - лінії (
^).
- Нульовий або більше
*пробілів ( застосованих постфіксів). Простір зазвичай не потрібно вказувати \в регулярному виразі, але це потрібно [[для запобігання синтаксичної помилки.
- Підряд ((
( ))), що є одним або іншим ( |) з:
[1-9][0-9]*: ненульова цифра ( [1-9]) з наступним нулем або більше ( *застосований постфікс) будь-якої цифри ( [0-9]).
0+: один або більше ( +, застосований постфікс) з 0.
- Нульовий або більше пробілів (
\ * ), як і раніше.
- Кінець - тобто правий край - лінії (
$).
На відміну від caseміток, які співпадають з усім тестуваним виразом, =~повертає істину, якщо будь-яка частина його лівого виразу відповідає шаблону, заданому як правий вираз. Ось чому тут ^і потрібні $якорі, вказуючи початок і кінець рядка, і не відповідають синтаксично нічому, що з’являється в методі з caseі exglobs.
Дужки потрібні для створення ^та $прив’язки до диз'юнкції [1-9][0-9]*та 0+. В іншому випадку це було б диз'юнкцією ^[1-9][0-9]*та 0+$і збігом будь-якого вводу, починаючи з ненульової цифри або закінчуючи символом 0(або обом, що все ще може включати нецифрові цифри між ними).
^\ *q(uit)?\ *$, яка перевіряє команду quit , відповідає послідовності:
- Початок рядка (
^ ).
- Нульовий або більше пробілів (
\ * див. Пояснення вище).
- Лист
q. Або Q, оскількиshopt nocasematch це ввімкнено.
- За бажанням - тобто нуль або один вхід (постфікс
?) - підрядка (( ) ):
u, а за iними - за ним t. Або, оскільки shopt nocasematchце ввімкнено uможе бути U; незалежно, iможе бути I; і незалежно, tможе бути T. (Тобто можливості не обмежуються uitі UIT.)
- Знову чи більше пробілів (
\ *).
- Кінець рядка (
$).