Почему bash не расширяет эту переменную, когда я добавляю к команде префикс «однократное назначение переменной»

Почему bash не расширяет эту переменную, когда я добавляю к команде префикс «однократное назначение переменной»

Если я запущу эту команду bash и добавлю префикс к оператору, чтобы переменная fruitсуществовала, но только на время выполнения этой команды:

$ fruit=apple echo $fruit

$

Результат — пустая строка. Почему?

Процитирую комментарий от wildcard наэтот вопрос:

Расширение параметров выполняется оболочкой, а переменная «fruit» не является переменной оболочки; это всего лишь переменная окружения в среде команды «echo».

Переменная окружения по-прежнему остается переменной, так что наверняка она должна быть доступна команде echo?

решение1

Проблема в том, что текущая оболочка слишком рано расширяет переменную; она не установлена ​​в своем контексте, поэтому echoкоманда не получает никаких аргументов, то есть команды в итоге выглядят так:

$ fruit=apple echo

Вот обходной путь, при котором переменная не будет расширяться слишком рано из-за одинарных кавычек:

$ fruit=apple sh -c 'echo $fruit'

В качестве альтернативы вы также можете использовать однострочный скрипт оболочки, который демонстрирует, что fruitпеременная правильно передается выполняемой команде:

$ cat /tmp/echof
echo $fruit
$ /tmp/echof

$ fruit=apple /tmp/echof
apple
$ echo $fruit

$

Несколько комментариев, поскольку этот вопрос вызвал неожиданные споры и обсуждения:

  • Тот факт, что переменная fruitуже экспортирована или нет, не влияет на поведение, важно то, каково значение переменной в тот момент, когда оболочка ее расширяет.
$ экспорт фруктов=банан
$ фрукт=яблоко эхо $фрукт
банан
  • Тот факт, что echoкоманда является встроенной, не влияет на проблему OP. Однако есть случаи, когда использование встроенных функций или функций оболочки с этим синтаксисом имеет неожиданные побочные эффекты, например:
$ экспорт фруктов=банан
$ фрукт=apple eval 'echo $фрукт'
яблоко
$ эхо $фрукт
яблоко
  • Хотя есть сходство междувопрос, заданный здесьи тот, это не совсем та же проблема. С тем другим вопросом, временное IFSзначение переменной еще не доступно, когда оболочкаслово разделить другойпеременная $var, пока здесь временное fruitзначение переменной еще не доступно, когда оболочкарасширяется the такой жепеременная.

  • А также естьтот другой вопросгде OP спрашивает о значимости используемого синтаксиса и, точнее, спрашивает «почему это работает?». Здесь OP знает о значимости, но сообщает о неожиданном поведении и спрашивает о его причине, то есть «почему это не работает?». Хорошо, после более внимательного прочтения плохого скриншота, размещенного в другом вопросе, та же ситуация действительно описана там ( BAZ=jake echo $BAZ), так что да, в конце концов, это дубликат

решение2

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

Переменные окружения — это свойство, которое есть у ВСЕХ процессов, используют ли они их внутри или нет. Даже sleep 10когда он запущен, у него есть переменные окружения. Так же, как у всех процессов есть PID (идентификатор процесса), текущий рабочий каталог (cwd), PPID (родительский PID), список аргументов (даже если пустой) и так далее. Так же у всех процессов есть то, что называется «окружением», которое наследуется от родительского процесса, когда он разветвляется.

С точки зрения автора утилиты (того, кто пишет код на языке C), процессы имеют возможность устанавливать, сбрасывать или изменять переменные окружения. Однако с точки зрения автора скрипта большинство инструментов не предлагают такую ​​возможность своим пользователям. Вместо этого вы используете свойоболочкадля изменения среды процесса, которая затем наследуется при выполнении вызванной вами команды (внешнего двоичного файла). (Среда самой вашей оболочки может быть изменена, а изменение унаследовано, или вы можете указать своей оболочке внести изменение после разветвления, но до выполнения вызванной вами команды. В любом случае среда наследуется. Мы рассмотрим оба подхода.)

Переменные оболочки — это другое дело. Хотявнутри оболочкиони ведут себя одинаково, разница в том, что простые «переменные оболочки» не изменяют и не влияют на поведение команд, которые вы вызываете.отваша оболочка. В правильной терминологии различие на самом деле было бы сформулировано немного иначе;экспортированоПеременные оболочки станут частью среды инструментов, которые вы вызываете, тогда как переменные оболочки, которыенетэкспортируется не будет. Однако я считаю более полезным для общения ссылаться на переменные оболочки, которые не экспортируются, как на «переменные оболочки» и переменные оболочки, которыеявляютсяэкспортируются как «переменные среды», поскольку ониявляютсяпеременные среды с точки зрения процессов, ответвленных от оболочки.


Это много текста. Давайте рассмотрим несколько примеров и опишем, что происходит:

$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r--  1 Myname  staff  0 May 29 19:12 myfile
$ 

В этом примере — somevarэто просто переменная оболочки, ничего особенного. Shellрасширение параметра(см. LESS='+/Parameter Expansion' man bash) происходитдоисполняемый файл lsфактически загружен (выполнен), а lsкоманда (процесс) даже невидитстрока "знак доллара somevar". Он видит только строку "myfile", интерпретирует ее как путь к файлу в текущем рабочем каталоге, получает и выводит информацию о нем.

Если мы запустим export somevarперед lsкомандой, то то, что somevar=myfileпоявится всредапроцесса ls, но это ни на что не повлияет, потому что lsкоманда ничего не сделает с этой переменной. Чтобы увидетьэффектпеременной среды, мы должны выбрать переменную среды, которую вызываемый нами процесс будет фактически проверять и с которой что-то делать.


bc: Базовый калькулятор

Может быть, есть пример получше, но это тот, который я придумал, и он не слишком сложен. Сначала вы должны знать, что bcэто базовый калькулятор, который обрабатывает и вычисляет математические выражения. (После обработки содержимого любых входных файлов он обрабатывает свой стандартный ввод. Я не буду использовать его стандартный ввод для своих примеров; я просто нажму Ctrl-D, что не будет отображаться в текстовых фрагментах ниже. Также я использую -qдля подавления вводного сообщения при каждом вызове.)

Переменная окружения, которую я проиллюстрирую, описана в man bc:

   BC_ENV_ARGS
      This is another mechanism to get arguments to bc.  The format is
      the  same  as  the  command line arguments.  These arguments are
      processed first, so any files listed in  the  environment  argu-
      ments  are  processed  before  any  command line argument files.
      This allows the user to set up "standard" options and  files  to
      be  processed at every invocation of bc.  The files in the envi-
      ronment variables would typically contain  function  definitions
      for functions the user wants defined every time bc is run.

Вот оно:

$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$

Это просто для того, чтобы показать, как bcэто работает. В каждом из этих случаев мне приходилось нажимать Ctrl-D, чтобы подать сигнал "конец ввода" для bc.

Теперь давайте передадим переменную окружениянапрямуюк bc:

$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$

Обратите внимание, что мы поместили в эту переменнуюнетвидимым позже из echoкоманды. Помещая назначение как часть той же команды (без точки с запятой, также), мы помещаем это назначение переменной какчастьсреды bc— она оставила саму оболочку, которую мы запускаем, нетронутой.

Теперь давайте установим BC_ENV_ARGSкакоболочкапеременная:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

Здесь вы можете видеть, что наша echoкоманда может видеть содержимое, но оно не является частью среды, bcпоэтому bcне может сделать с ним ничего особенного.

Конечно, если мы поместим саму переменную в bcсписок аргументов, то увидим что-то:

$ bc -q "$BC_ENV_ARGS"
25
$ 

Но здесь этооболочкарасширяет переменную, а затем file1то, что на самом деле появляется в bc'sсписок аргументов. Таким образом, это по-прежнему использование переменной оболочки, а не переменной среды.

Теперь давайте «экспортируем» эту переменную, чтобы она стала и переменной оболочки, и переменной среды:

$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$

И здесь вы можете видеть, что file1обрабатывается до file2, хотя и не упоминается в командной строке здесь. Это часть среды оболочки и становится частью bcсреды , когда вы запускаете этот процесс, поэтому значение этой переменной среды равноунаследованныйи влияет на то, как bcработает.

Мы по-прежнему можем переопределить это значение для каждой команды отдельно, даже установив его пустым значением:

$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25 
42
27
$ 

Но, как вы можете видеть, переменная остается установленной и экспортированной в нашей оболочке, видимой как для самой оболочки, так и для любых последующих bcкоманд, которыенеПереопределить значение. Оно останется таким, пока мы не "отменим экспорт" или "сбросим" его. Я сделаю последнее:

$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$ 

Другой пример, включающий создание еще одной оболочки:

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

# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'

И последний вопрос для большей сложности: можете ли вы предсказать вывод следующих команд, запущенных последовательно? :)

fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"

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

решение3

Потому что расширения в командной строке происходят до присвоения переменных.Стандарт говорит так:

Когда требуется выполнить простую команду, необходимо выполнить следующее:

  1. Слова, распознанные как переменные назначения [...], сохраняются для обработки на шагах 3 и 4.

  2. Слова, не являющиеся переменными назначениями или перенаправлениями, должны быть расширены. [...]

  3. Перенаправления должны выполняться, как описано в разделе «Перенаправление».

  4. Каждое назначение переменной должно быть расширено [...] перед назначением значения.

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

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

Связанный контент