Как работает "cat << EOF" в bash?

Мне нужно было написать script для ввода многострочного ввода в программу (psql).

После нескольких поисковых запросов, я нашел следующие синтаксические работы:

cat << EOF | psql ---params
BEGIN;

`pg_dump ----something`

update table .... statement ...;

END;
EOF

Это правильно построит многострочную строку (от BEGIN; до END; включительно) и передает ее как вход в psql.

Но я понятия не имею, как/почему это работает, может кто-нибудь объяснить?

Я имею в виду главным образом cat << EOF, я знаю > выходы в файл, >> присоединяется к файлу, < читает ввод из файла.

Что делает << точно?

И есть ли для него справочная страница?

Ответ 1

Это называется heredoc, чтобы предоставить строку в stdin. Подробнее см. https://en.wikipedia.org/wiki/Here_document#Unix_shells.


От man bash:

Здесь Документы

Этот тип перенаправления инструктирует оболочку читать ввод от текущий источник до линии содержащий только слово (без пробелы).

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

Формат здесь-документов:

          <<[-]word
                  here-document
          delimiter

Нет расширения параметров, подстановки команд, арифметического расширения или расширение имени пути выполняется слово. Если любые символы в wordцитируется, разделитель является результатом удаления цитаты на word, а строки в здесь-документе не расширены. Если слово не указано, все строки здесь-документ подвергаются расширению параметров, команда подстановка и арифметика расширение. В последнем случае символьная последовательность \<newline>игнорируется, а \ - для обозначения символов \, $ и `.

Если оператор перенаправления <<-, то все ведущие символы табуляции удаляются из строк ввода и строка, содержащая разделитель. Эта позволяет здесь-документы в сценариях оболочки быть отступом естественным образом.

Ответ 2

Синтаксис cat <<EOF очень полезен при работе с многострочным текстом в Bash, например. при назначении многострочной строки переменной оболочки, файлу или каналу.

Примеры использования синтаксиса cat <<EOF в Bash:

1. Назначить многострочную строку переменной оболочки

$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)

В переменной $sql также содержатся символы новой строки. Вы можете проверить с помощью echo -e "$sql".

2. Передайте многострочную строку в файл в Bash

$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF

Файл print.sh теперь содержит:

#!/bin/bash
echo $PWD
echo /home/user

3. Передайте многострочную строку в канал в Bash

$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF

Файл b.txt содержит строки bar и baz. Тот же вывод печатается на stdout.

Ответ 3

В вашем случае "EOF" известен как "Тег здесь". В основном <<Here говорится оболочке, что вы собираетесь вводить многострочную строку до "тега" Here. Вы можете назвать этот тег так, как хотите, часто EOF или STOP.

Некоторые правила тегов Here:

  1. Тег может быть любой строкой, в верхнем или нижнем регистре, хотя большинство людей используют по умолчанию верхний регистр.
  2. Тег не будет считаться тегом Here, если в этой строке есть другие слова. В этом случае это будет просто рассматриваться как часть строки. Тег должен быть сам по себе на отдельной строке, чтобы считаться тегом.
  3. Тег не должен иметь никаких ведущих или конечных пробелов в этой строке, чтобы считаться тегом. В противном случае он будет рассматриваться как часть строки.

пример:

$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
>  HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string

Ответ 4

POSIX 7

kennytm цитирует man bash, но большая часть из них также POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04:

Операторы перенаправления "< и" < - "оба допускают перенаправление строк, содержащихся в входном файле оболочки, называемом" here-document", на вход команды.

Настоящий документ должен рассматриваться как одно слово, которое начинается после следующего и продолжается до тех пор, пока не будет строка, содержащая только разделитель и a, без символов между ними. Затем начинается следующий документ, если он есть. Формат выглядит следующим образом:

[n]<<word
    here-document
delimiter

где необязательный n обозначает номер дескриптора файла. Если номер опущен, здесь-документ ссылается на стандартный ввод (дескриптор файла 0).

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

Если никакие символы в словах не указаны, все строки этого документа должны быть расширены для расширения параметров, подстановки команд и арифметического расширения. В этом случае вход на входе ведет себя как внутренние двойные кавычки (см. Double-Quotes). Однако символ двойной кавычки ('' ') не обрабатывается специально внутри документа здесь, за исключением случаев, когда двойная кавычка отображается внутри "$()", "`" или "${}".

Если символ переадресации равен "< < -", все ведущие символы <tab> должны быть удалены из строк ввода и строки, содержащей конечный разделитель. Если более чем один "< или "< < -" оператор указан в строке, здесь-документ, связанный с первым оператором, должен быть подан первым приложением и должен сначала считываться оболочкой.

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

Примеры

Некоторые примеры еще не приведены.

Котировки предотвращают расширение параметра

Без кавычек:

a=0
cat <<EOF
$a
EOF

Вывод:

0

С кавычками:

a=0
cat <<'EOF'
$a
EOF

или (уродливый, но действительный):

a=0
cat <<E"O"F
$a
EOF

Выходы:

$a

Hyphen удаляет ведущие вкладки

Без дефиса:

cat <<EOF
<tab>a
EOF

где <tab> является литеральной вкладкой и может быть вставлено с помощью Ctrl + V <tab>

Вывод:

<tab>a

С дефис:

cat <<-EOF
<tab>a
<tab>EOF

Вывод:

a

Это существует, конечно, так что вы можете отступать от cat как окружающий код, который легче читать и поддерживать. Например:

if true; then
    cat <<-EOF
    a
    EOF
fi

К сожалению, это не работает для пробелов: POSIX предпочитает tab отступ здесь. Хлоп.

Ответ 5

Использование tee вместо cat

Не совсем как ответ на исходный вопрос, но я все равно хотел бы поделиться этим: у меня возникла необходимость создать файл конфигурации в каталоге, который требует прав root.

В этом случае не работает следующее:

$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF

потому что перенаправление обрабатывается вне контекста sudo.

В итоге я использовал это:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF

Ответ 6

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

<<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

будет выдавать тот же файл, что и:

cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

Итак, я не вижу смысла использовать команду cat.

Ответ 7

Стоит отметить, что здесь документы также работают в баш-петлях. В этом примере показано, как получить список столбцов таблицы:

export postgres_db_name='my_db'
export table_name='my_table_name'

# start copy 
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name   =:'table_name'  ;
EOF
)
# stop copy , now paste straight into the bash shell ...

output: 
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,

или даже без новой линии

while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne 
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
 SELECT column_name
 FROM information_schema.columns
 WHERE 1=1
 AND table_schema = 'public'
 AND table_name   =:'table_name'  ;
 EOF
 )

 # output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner