Как присвоить значение heredoc переменной в Bash?

374

У меня есть эта многострочная строка (цитаты включены):

abc'asdf"
$(dont-execute-this)
foo"bar"''

Как бы я назначил его переменной, используя heredoc в Bash?

Мне нужно сохранить переводы строк.

Я не хочу избегать символов в строке, это будет раздражать ...

Нил
источник
@JohnM - я только что попробовал присвоение heredoc с одинарными кавычками 'EOF', с экранированными ` in the content: if the second line has переносами строк с помощью команды cd`, я получаю ответ: " .sh: line X: cd: команда не найдена "; но если я приведу двойные кавычки "EOF"; тогда переменные bash ${A}не сохраняются как строки (они раскрываются); но затем, линия-брейки будут сохранены - и я не имею проблемы запуска команды с cdво второй строке ( и как «EOF» и «EOF» , кажется, играют хорошо и с eval, для выполнения набора команд , хранящихся в строковая переменная ). Ура!
Sdaau
1
... и добавить к моему предыдущему комментарию: bash комментарии "#" в "EOF"переменной с двойным кавычкой , если eval $VARвызывается через , приведет к тому, что весь остальной сценарий будет закомментирован, так как здесь $ VAR будет рассматриваться как одна строка ; чтобы иметь возможность использовать #комментарии bash в многострочном скрипте, используйте двойные кавычки и переменную в переменной eval call: «$ VAR» `.
Сдау
@sdaau: у меня были проблемы с evalэтими методами, но я не отслеживал его, поскольку он был частью некоторого пакета, в котором evalнекоторые переменные определены в его конфигурационном файле. Сообщение об ошибке было: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Я просто переключился на другое решение.
Голар Рамблар

Ответы:

515

Вы можете избежать бесполезного использования catи лучше обрабатывать несовпадающие кавычки:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

Если вы не заключаете в кавычки переменную, когда вы выводите ее, новые строки теряются. Цитируя это сохраняет их:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Если вы хотите использовать отступ для удобства чтения в исходном коде, используйте тире после менее чем. Отступы должны быть сделаны с использованием только табуляции (без пробелов).

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Если вместо этого вы хотите сохранить вкладки в содержимом результирующей переменной, вам нужно удалить вкладку из IFS. Маркер терминала для здесь doc ( EOF) не должен иметь отступ.

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

Вкладки могут быть вставлены в командной строке, нажав Ctrl- V Tab. Если вы используете редактор, в зависимости от того, какой из них может также работать, или вам может потребоваться отключить функцию, которая автоматически преобразует вкладки в пробелы.

Приостановлено до дальнейшего уведомления.
источник
117
Я думаю, что стоит упомянуть, что если у вас есть set -o errexit(ака set -e) в вашем скрипте, и вы используете это, то он прервет ваш скрипт, потому что readвозвращает ненулевой код возврата, когда он достигает EOF.
Марк Байерс
14
@MarkByers: это одна из причин, по которой я никогда не пользуюсь set -eи всегда рекомендую против ее использования. Вместо этого лучше использовать правильную обработку ошибок. trapтвой друг. Другие друзья: elseи ||среди других.
Приостановлено до дальнейшего уведомления.
7
Стоит ли избегать catэтого в таком случае? Присвоение heredoc переменной с catпомощью хорошо известной идиомы. Каким-то образом использование readзапутывает вещи для небольших выгод imho.
Григорий Пакош
6
@ulidtko Это потому что у вас нет пробела между dпустой строкой; bashрушится -rd''просто -rdдо того, как readувидит свои аргументы, поэтому VARрассматривается как аргумент -d.
чепнер
6
В этом формате readвернется с ненулевым кодом выхода. Это делает этот метод менее чем идеальным в скрипте с включенной проверкой ошибок (например set -e).
швейцарский
246

Используйте $ (), чтобы назначить вывод catвашей переменной следующим образом:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

Не забудьте ограничить начало END_HEREDOC одинарными кавычками.

Обратите внимание, что конечный разделитель heredoc END_HEREDOCдолжен быть один в строке (следовательно, конечная скобка находится на следующей строке).

Спасибо @ephemientза ответ.

Нил
источник
1
На самом деле, этот позволяет пропустить кавычки при некоторых обстоятельствах. Мне удалось сделать это в Perl легко ...
Нил
32
+1. Это самое читаемое решение, по крайней мере для моих глаз. Он оставляет имя переменной в крайнем левом углу страницы, а не встраивает его в команду чтения.
Клейтон Стэнли,
12
PSA: помните, что переменная должна быть заключена в кавычки, чтобы сохранить переводы строки. echo "$VAR"вместо echo $VAR.
севко
7
Это хорошо с ashOpenWRT, где readне поддерживается -d.
Дэвид Эрманн
3
По причинам, которые я не могу понять, это приводит к ошибке «неожиданный EOF», если в heredoc есть непарный обратный удар.
Радон Росборо
79

это вариант метода Денниса, выглядит более элегантно в сценариях.

определение функции:

define(){ IFS='\n' read -r -d '' ${1} || true; }

Применение:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

наслаждаться

PS сделал версию 'цикла чтения' для оболочек, которые не поддерживают read -d. должны работать с set -euи непарных обратные кавычки , но не протестированы очень хорошо:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }
ТТТ
источник
1
Кажется, это работает только поверхностно. Функция define вернет статус 1, и я не совсем уверен, что нужно исправить.
Fny
1
Это также превосходит принятый ответ, потому что он может быть изменен для поддержки POSIX sh в дополнение к bash ( readцикл в функции, чтобы избежать -d ''bashism, необходимого для сохранения новых строк).
ELLIOTTCABLE
В отличие от опции «кошка в ракушке», это работает с непарными обратными галочками в heredoc. Спасибо!
Радон Росборо
Это решение работает с set -eмножеством, а выбранный ответ - нет. Похоже, что из-заhttp://unix.stackexchange.com/a/265151/20650
ореха
2
@fny возвращение пса статус уже давно был зафиксирован
ТТТ
36
VAR=<<END
abc
END

не работает, потому что вы перенаправляете stdin на что-то, что не заботится об этом, а именно на назначение

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

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

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
l0st3d
источник
Я обновил свой вопрос, чтобы включить $ (исполняемый файл). Кроме того, как вы сохраняете переводы строки?
Нил
2
@ l0st3d: так близко ... используйте $(cat <<'END'вместо этого. @Neil: самый последний символ новой строки не будет частью переменной, но остальное будет сохранено.
Эфимент
1
Кажется, что новые строки не сохраняются. Запустив приведенный выше пример, я вижу: "sdfsdf sdfsdf sdfsfds" ... ах! Но пишите echo "$A"(то есть помещая $ A в двойные кавычки), и вы видите новые строки!
Даррен Кук
1
@Darren: ага! Я заметил проблему с символами новой строки, и использование кавычек вокруг выходной переменной решает проблему. Спасибо!
Джавадба
1
Интересно, что в связи с волей первого примера, в крайнем случае , вы можете использовать его для самодельных блоков комментариев , как это: REM=<< 'REM' ... comment block goes here ... REM. Или более компактно : << 'REM' .... Где «REM» может быть чем-то вроде «NOTES» или «SCRATCHPAD» и т. Д.
Beejor
34

До сих пор нет решения, которое сохранило бы новые строки.

Это неправда - вы, вероятно, просто введены в заблуждение поведением echo:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

patspam
источник
5
На самом деле это поведение того, как цитирование переменной работает. Без кавычек он будет вставлять их как разные параметры, с пробелами, а с кавычками все содержимое переменной будет рассматриваться как один аргумент
Czipperz
11

Ветвление от ответа Нила , вы часто не нужен вар на всех, вы можете использовать функцию во многом таким же образом , как переменные , и это гораздо легче читать , чем инлайн или read-Ы решения.

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''
dimo414
источник
9

Массив является переменной, поэтому в этом случае будет работать mapfile

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

Тогда вы можете напечатать так

printf %s "${y[@]}"
Стивен Пенни
источник
3

присвоить значение heredoc переменной

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

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

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"
бронзовый человек
источник
Когда я попробовал первый метод, между строк, кажется, нет разделителей строк. Должна ли быть какая-то конфигурация на моей машине с Linux?
Кемин Чжоу
Это, вероятно, означает, что когда вы отображали свою переменную, вы не заключали в нее кавычки ... Попробуйте это так:echo "$VAR"
Брэд Паркс
1

Я обнаружил, что должен прочитать строку с NULL, так что вот решение, которое будет читать все, что вы в нее бросаете. Хотя, если вы на самом деле имеете дело с NULL, вам придется иметь дело с этим на шестнадцатеричном уровне.

$ cat> read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

Доказательство:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

Пример HEREDOC (с ^ J, ^ M, ^ I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE
Orwellophile
источник
0

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

пример вывода

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

test.sh

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main
Брэд Паркс
источник
Было бы интересно увидеть в тексте непревзойденную цитату, чтобы увидеть, как она справляется с этим. Может быть, `Не волнуйтесь, есть файлы" $ COUNT ", поэтому апостроф / одиночная кавычка может сделать вещи интересными.
dragon788
-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
Гай Кастенбаум
источник