Почему переменная глобальная, когда она установлена ​​с помощью `read variable` внутри цикла while, а локальная, если она установлена ​​с помощью переменной while read?

unset myVariable i; while [ -z "$i" ]; do read myVariable; echo "myVariable : '$myVariable'"; i=foo; done; echo "myVariable : '$myVariable'" 

(здесь unset чтобы можно было повторить команду)

нажмите любую клавишу + ENTER, вы получите:

 myVariable : '[what you typed]' myVariable : '[what you typed]' 

Значение myVariable существует вне цикла while. Теперь попробуйте:

 tmpFile=$(mktemp); echo -e 'foo\nbar\nbaz' >> "$tmpFile" while read myVariable; do otherVariable=whatever; echo "myVariable : '$myVariable', otherVariable : '$otherVariable'"; done < "$tmpFile"; echo "myVariable : '$myVariable', otherVariable : '$otherVariable'"; rm "$tmpFile" 

ты получишь :

 myVariable : 'foo', otherVariable : 'whatever' myVariable : 'bar', otherVariable : 'whatever' myVariable : 'baz', otherVariable : 'whatever' myVariable : '', otherVariable : 'whatever' 

Значение myVariable теряется при выходе из цикла.

Почему существует другое поведение? Есть ли какой-то фокус, о котором я не знаю?

Примечание: работает GNU bash, версия 4.4.12 (1) -релиз (x86_64-pc-linux-gnu)

 while read myVariable; do 

Значение myVariable теряется при выходе из цикла.

Нет, myVariable имеет значение, полученное с момента последнего read . Скрипт читает из файла, пока не доберется до позиции после последней новой строки. После этого последний вызов read ничего не получает из файла, соответственно устанавливает myVariable в пустую строку и завершает работу с ложным значением, поскольку он не видит разделитель (символ новой строки). Тогда цикл заканчивается.

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

 $ printf 'abc\ndef\nxxx' | { while read a; do echo "got: $a"; done; echo "end: $a"; } got: abc got: def end: xxx 

Или используйте, while read a || [ "$a" ]; do ... while read a || [ "$a" ]; do ... while read a || [ "$a" ]; do ... для обработки последнего fragmentа строки в теле цикла.

Поскольку вы включаете цикл while, создается вспомогательная shell для запуска цикла while. Теперь этот дочерний процесс имеет свою собственную копию среды и не может передавать какие-либо переменные своему родительскому процессу (как в любом unix-процессе).

В этом случае redirect done < "$tmpFile" заставляет bash генерировать done < "$tmpFile" оболочку (как для pipe).

Цитируется из области видимости переменной bash при переполнении стека.

Ваш второй пример использует цикл while с перенаправленным вводом.

Он читает с использованием < "$tmpFile" и многие оболочки создают под-оболочку для этого случая. Вы можете попробовать запустить этот скрипт с помощью ksh93 . ksh93 не создает ksh93 оболочку.

В вашем конкретном случае причина совершенно иная:

  • в первом примере вы читаете одну строку из ввода

  • во втором примере вы читаете, пока не будет достигнут EOF

Команда read читает ввод, затем разбивает ввод на символы IFS и затем назначает слова переменным аргументам read .

Если в команде read больше слов, чем переменных, в качестве параметров, последняя переменная получает конкатенацию остальных слов.

Если слов меньше, чем переменных, другим переменным присваивается пустое значение.

Поскольку вы EOF , у вас нет слова read, но есть одна или несколько переменных в качестве аргумента для read . Это заставляет все переменные получать пустое значение.

Так случилось то, чего вы не ожидали: EOF вызывает прерывание цикла while, и вы не видите никакой команды echo из цикла while, а только последнюю команду echo после цикла while в этом случае EOF .

Этот финальный echo теперь печатает содержимое переменной, которая была очищена от нажатия EOF .