Почему присвоение переменной с помощью подстановки команд и последующий вывод этой переменной всегда завершается ошибкой?

Почему присвоение переменной с помощью подстановки команд и последующий вывод этой переменной всегда завершается ошибкой?

Почему следующее не работает в 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 ""

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

  1. ТЕСТ
  2. "="
  3. Привет, мир!
  4. эхо
  5. (пустое значение)

Хорошо, теперь оболочка наконец-то закончила замену, так что она может перейти к следующей задаче — поиску исполняемого кода для запуска.

Он замечает, что командная строка начинается с формы 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 "Привет, мир!" > test.txt
  • ТЕСТ="$(cat test.txt)"
  • эхо "$TEST"
  • Третья часть выполняется только в том случае, если вторая часть (выполняется и) завершается успешно, а вторая часть выполняется только в случае успешного завершения первой команды из-за двух &&операторов.

    Другой вариант может быть таким:

    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 и даже почему мое использование второго &&не является оптимальным выбором.

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