Почему в циклах 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
keithpjolley
источник
1
Похожие
страницы
2
Это соответствует: нет точки с запятой после while/ forлибо.
Дмитрий Григорьев
То, что сразу приходит на ум, это то, что это просто не нужно. В других местах точки с запятой различают утверждения.
Пол Дрейпер
Потому что это было бы излишним. Нет двусмысленности без этого.
user207421

Ответы:

29

Это синтаксис команды. Смотрите составные команды

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

Обратите особое внимание: do commands

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

for i in thing
do something
done

Я знаю, что этот вопрос конкретно о оболочке, и я связан с руководством по bash. Так не написано в руководстве по оболочке, но так написано в статье, написанной Стивеном Борном для журнала байтов.

Стивен говорит:

Список команд - это последовательность из одной или нескольких простых команд, разделенных или завершенных новой строкой или ;(точкой с запятой). Кроме того, зарезервированные слова, такие как do и done , обычно начинаются с новой строки или ;... По очереди каждый раз, когда выполняется список команд, следующий за do .

Jesse_b
источник
5
Было бы также интересно, почему не должно быть точки с запятой, как for i in thing; do; something; done. Хотя допускается разрыв строки, точка с запятой не допускается.
Рекскогитанс
@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циклов просто похож.

ilkkachu
источник
11

Синтаксис оболочки основан на префиксах. В нем есть пункты, введенные специальными ключевыми словами. Некоторые пункты должны идти вместе.

whileПетля сделана из одного или более команд тестирования:

test ; test ; test ; ...

и одной или несколькими командами тела:

body ; body ; body ; ...

Что-то должно сказать оболочке, что цикл while начинается. Это цель whileслова:

while test ; test ; test ; ...

Но тогда, вещи неоднозначны. Какая команда является началом тела? Что-то должно указывать на это, и вот что doделает префикс:

do body ; body ; body ; ...

и, наконец, что-то должно указывать на то, что последнее тело было замечено; специальное ключевое слово doneделает это.

Эти ключевые слова оболочки не требуют разделения точки с запятой даже на одной строке. Например, если вы закроете несколько вложенных циклов, вы можете просто иметь done done done ....

Скорее точка с запятой находится между, ... test ; body ... если они находятся на одной строке. Эта точка с запятой понимается как терминатор: она принадлежит test. Поэтому, если doключевое слово вставлено между ними, оно должно быть между точкой с запятой и body. Если бы он был на другой стороне точки с запятой, он был бы неправильно встроен в testсинтаксис команды, а не помещен между командами.

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

Kaz
источник
Исходный код впечатляет.
Keithpjolley
Да, это тур де СРП.
украденный момент
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' после 'do', заставило меня думать, что 'do' был автономным токеном, а не чем-то, что воздействовало на следующий блок.
Keithpjolley
Как это , что не экранирован перевод строки (которые в противном случае , кажется, ведут себя так же , как точка с запятой в оболочке синтаксиса) будет разрешено после того, как doifт.д.), хотя?
Хеннинг Махолм
@Henning Любое количество новых строк разрешено до (и после) списка команд. Новые строки и точки с запятой ведут себя по-разному и в других аспектах. Например, раскрытие псевдонима выполняется, когда читается вся строка ввода (т. Е. До следующей новой строки), а не для каждой команды.
Муру
3

Синтаксис:

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

Любой символ новой строки, либо в списке команд, либо перед «do» или «done» может быть заменен на «;».

Новая строка (но не «;») разрешается после «do» и перед «in», только для того, чтобы все выглядело лучше, но не было бы смысла требовать новой строки там.

Рэй Баттерворт
источник
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)
Гленн Джекман
Я предполагаю, что я не был настолько ясен, как я хотел быть в своем ответе.
Keithpjolley
Моя точка зрения заключалась в том, что «делать» не похоже на «другие команды». Так что он подчиняется другим правилам.
Гленн Джекман
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 Вы имеете в виду самый первый? Это перебирает позиционные параметры. Так что если у вас есть скрипт с именем like ./myscript.sh abc, где abc - аргументы, цикл для i; do echo "$ i"; done будет цикл через abc, хотя я все еще думаю, что для его работы потребуется точка с запятой
Сергей Колодяжный
Итак, мои сомнения тоже прояснились
Сергей Колодяжный
1

Потому что do - это ключевое слово, а то, что следует, по сути являются параметрами. Интерпретатор Bash знает, что еще следует. Это похоже на то, как нет ';' после я в для команды. Вы можете добавить новую строку после i в for и набрать остальное в новой строке, и она будет работать нормально.

Роб Свит
источник