Чому в циклі sh немає ";" після "do"?


28

Чому в циклі оболонки немає ;символу після doнаписання в одному рядку?

Ось що я маю на увазі. Коли пишеться в декількох рядках, forцикл виглядає так:

$ for i in $(jot 2)
> do
>     echo $i
> done

І по одному рядку:

$ for i in $(jot 2); do echo $i; done

Все що звалилися лінії отримати ;після них , за винятком для doлінії, і якщо включати ;, це помилка. Хтось, мабуть, чортів набагато розумніший за мене, вирішив, що це правильно зробити з причини, але я не можу зрозуміти, в чому причина. Мені це здається непослідовним.

Те саме з whileпетлями теж.

$ while something
> do
>     anotherthing
> done
$ while something; do anotherthing; done


2
Це узгоджено: після while/ жодної крапки з комою немає for.
Дмитро Григор’єв

Те, що відразу спадає на думку, - це просто не потрібно. В іншому випадку крапки з комою розрізняють висловлювання.
Пол Дрейпер,

Бо це було б зайвим. Без нього немає двозначності.
користувач207421

Відповіді:


29

Це синтаксис команди. Див. Складені команди

for name [ [in [words …] ] ; ] do commands; done

Примітка: do commands

Більшість людей ставлять doі commandsна окремий рядок, щоб полегшити читабельність, але це не обов'язково, ви можете написати:

for i in thing
do something
done

Я знаю, що це питання стосується оболонки, і я пов'язав з посібником по bash. Це не так написано в посібнику з оболонкою, але саме так написано у статті, яку написав Стівен Борн для журналу byte.

Стівен каже:

Список команд є послідовність з одного або декількох простих команд , розділених або закінчуватися символом нового рядка або ;(точка з коми). Крім того, зарезервованим словам, як " робити і робити" , зазвичай передує новий рядок або ;... У свою чергу кожен раз, коли виконується список команд, що виконуються нижче.


5
Також було б цікаво, чому не повинно бути крапки з комою, як for i in thing; do; something; done. Хоча розрив рядка дозволений, крапка з комою не дозволена.
rexkogitans

@gidds: так, точно. Ось так структурована граматика оболонок. Суб'єкт somethingє предметом і doзастосовується до нього. Так само, як в англійській структурі речень.
Пітер Кордес

@gidds Буквальний (або якийсь інший фрагмент синтаксису) необхідний, тому що в умові (у whileсинтаксисі) може входити кілька команд , тому вам потрібно щось диференціювати команди умов від команд тіла циклу. Використання doдля їх розділення - це, мабуть, те, що просто здавалося найбільш придатним.
JoL

@rexkogitans Дійсно, я ніколи не зрозумів, що це синтаксична помилка. Це заголовне питання також дуже підходить і для цього. Мабуть, це стосується того, щоб не пустити порожнє тіло. Однак ви отримуєте дещо іншу помилку синтаксису, створюючи порожнє тіло з новими рядками, і у вашому прикладі немає порожнього тіла.
JoL

@rexkogitans Точка з комою є частиною синтаксису циклу і відрізняється від його іншої ролі як термінатора списку команд. Новий рядок відрізняється тим, що якщо його інакше не потрібно або очікується, він розглядається як звичайний пробіл. Граматика оболонки безладна.
чепнер

12

Я не знаю, в чому полягає початкова причина цього синтаксису, але давайте розглянемо той факт, що whileпетлі можуть приймати кілька команд у розділі стану, наприклад

while a=$(somecmd);
      [ -n "$a" ];
do
    echo "$a"
done

Тут doключове слово необхідно, щоб відокремити розділ стану та основне тіло циклу. doце ключове слово , як while, ifі thendone), і це , здається, відповідно до інших , що він не вимагає крапки з комою або символу нового рядка після нього , але з'являється безпосередньо перед командою.

Альтернатива (узагальнена до ifта while) виглядала б дещо негарно, нам потрібно писати, наприклад

if; [ -n "$a" ]; then
    some command here
fi

Синтаксис forциклів просто подібний.


11

Синтаксис оболонки на основі префіксу. У ньому є пункти, введені спеціальними ключовими словами. Певні пропозиції мають узгоджуватися.

У whileциклі складається одна або кілька команд тестування:

test ; test ; test ; ...

і за допомогою однієї або декількох команд тіла:

body ; body ; body ; ...

Щось повинно сказати оболонці, що починається цикл. Ось мета цього whileслова:

while test ; test ; test ; ...

Але потім справи неоднозначні. Яка команда - початок тіла? Щось повинно вказувати на це, і ось що doробить префікс:

do body ; body ; body ; ...

і, нарешті, щось має вказувати на те, що останнє тіло було видно; спеціальне ключове слово doneробить це.

Ці ключові слова оболонки не вимагають розділення крапкою з комою, навіть у тому ж рядку. Наприклад, якщо ви закриєте кілька вкладених циклів, їх можна просто мати done done done ....

Швидше, крапка з комою знаходиться між ними, ... test ; body ... якщо вони знаходяться на одній лінії. Ця крапка з комою розуміється як термінатор: вона належить до test. Тому, якщо doключове слово вставлено між ними, воно має проходити між крапкою з комою та body. Якби він був з іншого боку крапки з комою, він був би неправильно вбудований у testсинтаксис команди, а не розміщений між командами.

Синтаксис оболонки спочатку був розроблений Стівеном Борном, а його натхненник Алгол . Борн так любив Алгол, що він використав безліч макросів C у вихідному коді оболонки, щоб зробити C схожим на Algol. Ви можете переглянути джерела оболонки, датовані 1979 року, з версії 7 Unix . Макроси є mac.h, і вони використовуються всюди. Наприклад ifзаяви надаються в якості IF... ELSE... ELIF... FI.


Вихідний код вражає.
keithpjolley

Так, це сила турніру cpp.
вкрадений

7

Я б заперечував, що doтут не особливо особливе. Ви отримуєте помилку, оскільки ;не можете запустити список команд. Якби це було, перша команда була б порожньою, а граматика не дозволяє пусті команди (змінні призначення або перенаправлення без команди, так, але абсолютно нічого, ні). Це справедливо в будь-якій кількості місць, де очікується список команд:

$ while true; do ; echo foo; done
dash: 1: Syntax error: ";" unexpected
$ while ; true; do; echo; done
dash: 1: Syntax error: ";" unexpected
$ { ; echo foo; }
dash: 1: Syntax error: ";" unexpected
$ if ; true; then echo foo; fi
dash: 1: Syntax error: ";" unexpected

Навіть:

$ ; ;
dash: 1: Syntax error: ";" unexpected

У всіх цих місцях очікується список команд, і тому ведучий ;несподіваний.


Так - те, як я довільно додав "\ n" після "робити", змусило мене думати, що "робити" - це окремий жетон, а не те, що діяло на наступний блок.
keithpjolley

Як виходить, що не уникнути нового рядка (який, інакше, схоже, поводиться так само, як крапка з комою в синтаксисі оболонки) буде дозволено після того, як doifт.д.), хоча?
Геннінг Макгольм

@Henning Будь-яка кількість нових рядків дозволена перед (і після) списком команд. Нові рядки та крапки з комою поводяться по-різному в інших аспектах. Наприклад, розширення псевдоніму робиться, коли читається цілий рядок вводу (тобто, до наступного нового рядка), а не на одну команду.
муру

3

Синтаксис:

for i in [whitespace separated list of values]
do [newline separated list of commands]
done

Будь-який з нових рядків, як у списку команд, так і до того, як "зробити" або "зроблено", можна замінити на ";".

Новий рядок (але не ";") дозволений після "робити" і перед "в", просто для того, щоб все виглядало приємніше, але не було б сенсу вимагати там нового рядка.


3

if подібний за своїми ключовими словами: це досить читабельно, коли команди короткі:

if   test_commands
then true_commands
else false_commands
fi

Одного разу я зрозумів, що я ввожу довільне \nпісля того, doяк усе це має сенс (до тих пір, поки ви не замислюєтеся про те, щоб не мати змоги вкласти довільне \nміж іншими командами та аргументами).
keithpjolley

Ну do, then, elseНЕ арг - якби вони були, ви б не дозволили використовувати точку з коми перед ними. Це ключові слова оболонки (див. type if then elif else fi)
glenn jackman

Я здогадуюсь, що я не була такою чіткою, як хотіла бути у відповіді.
keithpjolley

Моя думка полягала в тому, що "робити" не схоже на будь-яку "іншу команду". Тож воно підкоряється різним правилам.
glenn jackman

3

Коротка відповідь, оскільки це визначено граматикою оболонки POSIX . Що-небудь міжdo і doneвважається групою висловлювань, тоді як ;є послідовним роздільником:

for_clause       : For name                                      do_group
                 | For name                       sequential_sep do_group
                 | For name linebreak in          sequential_sep do_group
                 | For name linebreak in wordlist sequential_sep do_group

do_group         : Do compound_list Done 

sequential_sep   : ';' linebreak
                 | newline_list

Крім того, у разі ;необхідності стандарт явно вказав би так, і він включатиме sequential_sepпісля Do.


1
@drewbenn Ви маєте на увазі найперший? Це повторюється за допомогою позиційних параметрів. Отже, якщо у вас є сценарій, який називається ./myscript.sh abc, де abc - аргументи, цикл для i; робити відлуння "$ i"; зробив би цикл через abc, хоча я все ще думаю, що для його роботи потрібна крапка з комою
Сергій Колодяжний,

Гаразд, і я також усунув мої сумніви
Сергій Колодяжний,

1

Тому що do - це ключове слово, і що з нього випливає - це по суті параметри. Перекладач Баша знає, що далі випливає. Це схоже на те, як немає ';' слідуючи i в команді for для. Ви можете фактично додати новий рядок після i в для та набрати решту в новому рядку, і він буде добре працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.