Почему следующее не работает в Bash?
# Ensure TEST is unset
export TEST=''
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
По какой-то причине он всегда возвращает пустую строку.
Выполнение этого на отдельных строках дает ожидаемый результат:
# Ensure TEST is unset
export TEST=''
echo "Hello world" > test.txt
TEST=$(cat test.txt)
echo "$TEST"
Hello world!
Как мне сделать это в одну строку?
Мне это нужно, чтобы расшифроватьОПГфайл, содержащий строку со специальными символами, которую я затем передаю вАнсибль.
ANSIBLE_VAULT_PASSWORD="$( gpg -d ~/gpg_encrypted_vault_password_file 2>/dev/null )" ansible-playbook etc etc etc
По какой-то причине разработчики Ansible считают, что хранение пароля хранилища в простом текстовом файле безопасно, а мне не нравится вводить (или копировать и вставлять) длинные безопасные пароли каждый раз при тестировании игры, поэтому использование агента GPG с зашифрованным файлом — единственный безопасный способ решения проблемы, который я могу придумать.
решение1
Не хочу вступать в противоречие с другими ранее существовавшими ответами, но я подумал, что пошаговый анализ может помочь. (ответ u1686_grawityвыглядит точно, но я думал, что так будет понятнее...)
# ensure TEST is unset export TEST=''
Хорошие новости: это, скорее всего, работает так, как вы ожидали. Символ # превращает первую строку в комментарий, а затем следующая строка делает переменную TEST пустой строкой (ноль байтов).
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
Оболочка замечает &&. Она выполнит первую часть кода...
echo "Hello world!" > test.txt
Затем он обрабатывает &&. Он смотрит на возвращаемое значение команды echo и видит, что оно равно нулю. Поэтому он может приступить к выполнению второй команды. Вторая команда:
TEST="$(cat test.txt)" echo "$TEST"
Итак, первое, что оболочка собирается сделать здесь, это заменить переменные. "Подстановка команды" действует здесь как переменная, поэтому запускается команда "cat text.txt". С точки зрения оболочки команда теперь выглядит примерно так:
TEST="Hello world!" echo "$TEST"
Итак, все это, вероятно, выглядит хорошо до сих пор, но вот большая проблема. Оболочка TEST="Hello world!"
пока не начинает выполнять команду ' '. Вместо этого оболочка замечает, что все еще есть ссылка на $TEST
, поэтому оболочка заменяет ее.
Теперь команда, которую оболочка планирует выполнить, выглядит примерно так:
TEST="Hello world!" echo ""
Я полагаю, что если я собираюсь вдаваться в подробности, то укажу, что оболочка выполняет действие по разбиению этой команды на части. Это действие удаляет кавычки. Таким образом, отдельные части выглядят так:
- ТЕСТ
- "="
- Привет, мир!
- эхо
- (пустое значение)
Хорошо, теперь оболочка наконец-то закончила замену, так что она может перейти к следующей задаче — поиску исполняемого кода для запуска.
Он замечает, что командная строка начинается с формы A=B C
(где A
представляет имя переменной, знак равенства указывает, что переменной будет присвоено значение, и B
представляет значение, которое будет присвоено, а затем C
следует необязательная часть, которая представляет команду для выполнения.
Когда оболочка замечает, что командная строка соответствует этому общему шаблону, исполняемый код, который она собирается запустить, — это код, который обрабатывает знак равенства. По сути, знак равенства похож на имя исполняемого файла, в том смысле, что знак равенства в конечном итоге управляет тем, какой код будет выполнять оболочка. Оболочка продолжает выполнять код, чтобы присвоить переменной с именем TEST
значение Hello world!
.
Затем, после этого, оболочка запустит запрошенную команду, которая использует 4-ю и 5-ю части, указанные выше (где 4-я часть — это команда echo
, а 5-я часть — пустая строка).
Причина, по которой я отвечаю (эээ... я имею в виду,ответ sqrt-1) работает, заключается в том, что второй && заставляет оболочку выполнить TEST="$(cat test.txt)"
возвращаемое значение кода, которое, обратите внимание, равно нулю, и только затем приступить к обработке того, что следует после второго &&
(тогда эта echo "$TEST"
часть будет содержать $TEST
обработанную переменную, и теперь все работает).
Обратите внимание, что предоставленный sqrt-1 ответ показывает некоторый код, который немного расточителен, хотя это и понятно, поскольку он напрямую отвечает на заданный вопрос. Что он в основном делает, так это:
Во-первых, разбивает команду на три части:
Третья часть выполняется только в том случае, если вторая часть (выполняется и) завершается успешно, а вторая часть выполняется только в случае успешного завершения первой команды из-за двух &&
операторов.
Другой вариант может быть таким:
echo "Hello world!" > test.txt && ( TEST="$(cat test.txt)" ; echo "$TEST" )
С этой точкой с запятой результаты ' TEST="$(cat test.txt)"
' не будут оцениваться, чтобы определить, является ли ' echo "$TEST"' will be run. When I see a &&
, я стараюсь выяснить, что будет оцениваться, и почему это будет иметь значение. Поэтому, несмотря на дополнительную сложность, связанную с необходимостью использования скобок, эта вариация с точкой с запятой на самом деле кажется мне немного более простой для мысленной обработки.
решение2
Это не работает, потому что $var
расширения выполняются оболочкой до запуска командной строки, т.е.неткомандой «эхо».
Между тем, VAR=value somecommand
это не обычное назначение — оно предназначено только для внедрения переменной среды в новый процесс, но не оказывает никакого влияния на переменные оболочки до создания этого процесса, т. е. оно полностью отличается от VAR=value; somecommand
.
Другими словами, это происходит в порядке, противоположном тому, который описывает заголовок вашего поста. Сначала (stil-empty) "$TEST"
расширяется в ""
,затем echo ""
запускается с дополнительной TEST=...
переменной окружения, которая его не волнует.
Поскольку ваши тестовые команды должны имитировать поведение Ansible, они должны фактически смотреть наихпеременные окружения и не полагаться на предварительное расширение оболочкой; что-то вроде VAR=value env
или VAR=value printenv VAR
было бы гораздо более подходящим вариантом.
решение3
Почему следующее не работает в bash?
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
Если вы запустите его, хотяShellCheck – инструмент анализа скриптов оболочкион расскажет вам почему:
$ shellcheck myscript
Line 2:
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
^-- SC2097 (warning): This assignment is only seen by the forked process.
>> ^-- SC2098 (warning): This expansion will not see the mentioned assignment.
ВидетьShellCheck: SC2097 – Это назначение видно только ответвленному процессу.
решение4
На основании ссылки, предоставленной @DavidPostill, я бы использовал
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" && echo "$TEST"
ИЗМЕНЕНИЕ
Как справедливо отметили многие пользователи, мой ответ далек от полезного: он не позволяет никому узнать что-либо по теме.
Пожалуйста, вместо этого обратитесь к @TOOGAMпочтаниже, который является лучшим ответом (мое личное мнение) и полностью раскрывает, почему код @Peter Mortensen не работает, как работает расширение переменных bash и даже почему мое использование второго &&
не является оптимальным выбором.