Почему установка переменной перед командой разрешена в bash?

68

Я только что столкнулся с несколькими ответами, такими как разбор текстового файла с разделителями ... который использует конструкцию:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

где IFSпеременная устанавливается перед readкомандой.

Я читал ссылку на bash, но не могу понять, почему это законно.

Я пытался

$ x="once upon" y="a time" echo $x $y

из командной строки bash, но ничего не получил. Может кто-то указать мне, где этот синтаксис определен в ссылке, которая позволяет переменной IFS быть установленным таким образом? Это особый случай или я могу сделать что-то подобное с другими переменными?

Майк Липперт
источник
Смотрите также Когда я могу использовать временный IFS для разделения поля?
Жиль "ТАК - перестань быть злым"

Ответы:

69

Это под Грамматика Shell, Простые команды (выделение добавлено):

Простая команда представляет собой последовательность необязательных назначений переменных, за которыми следуют слова и перенаправления , разделенные пробелами , и завершаются оператором управления. Первое слово указывает команду , которая будет выполнена, и передается в качестве аргумента нуль. Остальные слова передаются в качестве аргументов вызываемой команды.

Таким образом, вы можете передать любую переменную, какую захотите. Ваш echoпример не работает, потому что переменные передаются команде, а не задаются в оболочке. Оболочка раскрывается $xи $y до вызова команды. Это работает, например:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
источник
Спасибо! Я много гуглил, прежде чем спрашивать, и пытался выяснить, где сказано, что в ссылке, без удачи. Я пытаюсь стать лучше при написании сценариев bash, и ваш ответ помогает.
Майк Липперт
1
Хм, я думаю, мне нужно найти лучший справочник, мой (см. Ссылку выше) не говорит, что у него нет раздела грамматики оболочки.
Майк Липперт
1
@MikeLippert См. 3.7.4 в этом справочнике («Среда для любой простой команды или функции может быть временно расширена путем добавления префикса к назначению параметра»). Я думаю, что ссылка от старой версии Bash. Я только что побежал man bashв своей системе ...
Дероберт
29

Определенные переменные становятся похожими на переменные среды в разветвленном процессе.

Если вы бежите

A="b" echo $A

затем колотить первым расширяется $Aв , ""а затем запускает

A="b" echo

Вот правильный путь:

x="once upon" y="a time" bash -c 'echo $x $y'

Обратите внимание на одинарные кавычки, в bash -cпротивном случае у вас та же проблема, что и выше.

Так что ваш пример цикла допустим, потому что встроенная команда bash 'read' будет искать IFS в своих переменных окружения и находит ,. Следовательно,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

распечатает TEST is and I is test

Наконец, что касается синтаксиса, в цикле for ожидается строка. Поэтому я должен был использовать обратные кавычки, чтобы превратить это в команду. Однако в то время как циклы ожидают командного синтаксиса, такого как IFS=, read xx yy zz.

Майк Фэрхерст
источник
1
Спасибо, я узнал немного больше, чем я просил из вашего ответа. Я бы тоже проголосовал за ваш ответ, но мне пока не разрешили, и я пометил принятый ответ на первом.
Майк Липперт
1
Спасибо, я на это и надеялся. И голосование менее важно, чем услышать вашу оценку!
Майк Фэйрхерст
Чтобы уточнить ваш комментарий в первой строке кода: Да, с неустановленной Aпеременной bash расширяется $Aдо пустой строки, но во избежание путаницы я бы не стал использовать, ""потому что код не эквивалентен A="b" echo "". Там не будет никаких аргументов echo.
Пабук
8

man bash

ОКРУЖАЮЩАЯ ОБСТАНОВКА

[...] Среду для любой простой команды или функции можно временно дополнить, добавив в нее префикс назначения параметров, как описано выше в PARAMETERS. Эти операторы присваивания влияют только на среду, видимую этой командой.

Переменные раскрываются перед назначением переменной. По очевидной причине, var=xэто сработало бы и другим способом, но var=$othervarне сработало. Т.е. ваш $xнужен прежде чем он станет доступен. Но это не главная проблема. Основная проблема заключается в том, что командная строка может быть изменена только средой оболочки, но назначение не становится частью среды оболочки.

Вы смешиваетесь с функциями: вы хотите заменить командную строку, но поместите определение переменной в командную среду. Замены командной строки должны быть сделаны оболочкой. Окружение должно быть явно использовано вызываемой командой. То, как и как это сделать, зависит от команды.

Преимущество такого использования заключается в том, что вы можете установить среду для подпроцесса, не влияя на среду оболочки.

x="once upon" y="a time" bash -c 'echo $x $y'

работает так, как вы ожидаете, потому что в этом случае обе функции объединяются: замена командной строки выполняется не вызывающей оболочкой, а оболочкой подпроцесса.

Хауке Лагинг
источник
1
Это немного более тонко, чем это, потому что пример также работает, x="once upon" y="a time" eval 'echo $x $y'когда не задействован подпроцесс, поскольку он evalявляется встроенным. Я думаю, что соответствующая цитата из справочной страницы The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Рассматривая пример вопроса, он должен быть таким, поскольку readон также является встроенным и работает с временно измененным состоянием IFS.
Дэвид Онгаро
4

Команда вы предоставите отличается тем , что $xи $yрасширена до того в echoзапуске команды, поэтому их значение в текущей оболочке используется, а не значение , которые echoбудут видеть в своей среде , если бы это было смотреть.

chepner
источник
Как что-то в командной строке может быть развернуто после запуска команды ...?
Хауке Лагинг
Предварительные назначения команд xи yпредназначены для среды, в которой echoвыполняется, а не для среды, в которой echoраскрываются аргументы . Ибо IFS=, read xx yy zzвся строка читается, не разбирается readкомандой. Затем , эта строка разделена в соответствии со значением IFS, с соответствующими частями , присвоенных xx, yyи zz.
chepner
Я просто хотел отметить, что формулировка «развернуто до запуска команды» не имеет особого смысла, потому что после запуска команды ничего больше не расширяется. Более того: Вы ни разу не взглянули на мой ответ или, тем не менее, считаете ли вы, что мне нужно объяснение того, что происходит ...?
Хауке Лагинг
1
Я не утверждал, что вам нужно объяснение или что что-либо может быть расширено после выполнения команды. Тем не менее, верно, что bashсначала анализируется данная командная строка, распознается, что есть две переменные назначения для применения к среде поступающей команды, идентифицируется команда для run ( echo), расширяются все параметры, найденные в аргументах, затем запускается команда echoс расширенными аргументами.
chepner
В случае echoя не был уверен, сможет ли он «увидеть» переменную, поскольку это встроенная команда, и поэтому она не запускается в подоболочке, которая может иметь свою собственную среду. Но я попробовал его, evalкоторый также является встроенным, и он действительно знает об этом. Например, попытка, a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $aкоторая показывает, что aустанавливается только внутри, evalдаже если pid одинаков и среда не изменяется во время eval! (Чтобы получить доступ, /procвам нужно запустить это под Linux.) Кажется, bash делает здесь дополнительную магию.
Дэвид Онгаро
2

Я собираюсь для большей картины " почему это законно"

Ответ: чтобы вы могли вызывать или вызывать программу, и для этого вызова используйте только переменную с определенной переменной.

Например: у вас есть параметр для соединения с базой данных, называемый 'db_connection', и обычно вы передаете 'test' в качестве имени для вашего тестового соединения с базой данных. На самом деле вы можете даже установить его как значение по умолчанию, которое вам не нужно передавать явно. Однако иногда вы хотите работать с базой данных ci. Таким образом, вы передаете параметр как 'ci', а затем вызываемая программа использует этот параметр базы данных в качестве имени базы данных для использования во всех вызовах базы данных. Для следующего запуска, если вы не повторяете подход и просто вызываете программу, переменная вернется к своему предыдущему значению по умолчанию.

Майкл Даррант
источник
0

Вы также можете использовать ;. Он будет оценен раньше, потому что это разделитель команд.

x="once upon" y="a time"; echo $x $y
camabeh
источник
1
Это создает две переменные оболочки и не создает переменных среды в данной утилите. Это совсем другая вещь. Есть также побочный эффект, что текущая оболочка теперь содержит новые переменные.
Кусалананда