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


632

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

После небольшого поиска я нашел следующий синтаксис:

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

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

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

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

Что <<именно делает?

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


26
Это, вероятно, бесполезное использование cat. Попробуйте psql ... << EOF ... см. Также «здесь строки». mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
Приостановлено до дальнейшего уведомления.

1
Я удивлен, что это работает с кошкой, но не с эхом. cat должен ожидать имя файла как stdin, а не как строку символов. PSQL << EOF звучит логично, но не иначе. Работает с кошкой, но не с эхом. Странное поведение. Есть какие-нибудь подсказки по этому поводу?
Алекс

Отвечаю себе: cat без параметров выполняет и реплицирует на вывод все, что отправляет через ввод (stdin), следовательно, используя свой вывод для заполнения файла через>. На самом деле имя файла, считываемое как параметр, не является потоком стандартного ввода.
Алекс

@Alex echo просто печатает свои аргументы командной строки, в то время как catчитает stding (при передаче по нему) или читает файл, который соответствует его аргументам командной строки
The-null-Pointer-

Ответы:


519

Это называется форматом heredoc для предоставления строки в стандартный ввод. См. Https://en.wikipedia.org/wiki/Here_document#Unix_shells для получения дополнительной информации.


От man bash:

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

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

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

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

          <<[-]word
                  here-document
          delimiter

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

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


12
Мне было труднее всего отключить расширение переменных / параметров. Все, что мне нужно было сделать, это использовать «двойные кавычки», и это исправило это! Спасибо за информацию!
Xeoncross

11
Что касается, <<-пожалуйста, обратите внимание, что удаляются только начальные символы вкладки, а не символы программной вкладки. Это один из тех редких случаев, когда вам действительно нужен символ табуляции. Если в остальной части документа используются мягкие вкладки, убедитесь, что отображаются невидимые символы и (например) скопируйте и вставьте символ вкладки. Если вы все сделаете правильно, ваша подсветка синтаксиса должна правильно поймать конечный разделитель.
trkoch

1
Я не вижу, как этот ответ более полезен, чем приведенные ниже. Он просто срыгивает информацию, которую можно найти в других местах (которые, вероятно, уже были проверены)
BrDaHa

@ BrDaHa, возможно это не так. Почему вопрос? из-за голосов? это был единственный за несколько лет. это видно по сравнению дат.
Алексей

500

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.


1. 1 и 3 можно сделать без кота; 2. Пример 1 можно выполнить с помощью простой многострочной строки
Даниэль Алдер,

270

В вашем случае, «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

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

5
@edelans, вы должны добавить, что при <<-использовании начальная вкладка не помешает распознаванию тега
The-null-Pointer- 01.01.18

1
ваш ответ нажал мне кнопку «вы собираетесь ввести многострочную строку»
исчисление

79

POSIX 7

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

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

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

[n]<<word
    here-document
delimiter

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

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

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

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

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

Примеры

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

Кавычки препятствуют расширению параметров

Без кавычек:

a=0
cat <<EOF
$a
EOF

Вывод:

0

С цитатами:

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

или (некрасиво, но верно):

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

Выходы:

$a

Дефис удаляет ведущие вкладки

Без дефиса:

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отступы. Хлоп.


В последнем примере, обсуждаемом <<-и <tab>a, следует отметить, что цель состояла в том, чтобы обеспечить нормальный отступ кода в скрипте, при этом текст heredoc, представляемый процессу получения, начинается в столбце 0. Это не слишком часто встречаемая функция и немного больше контекста может помешать много почесать голову ...
Дэвид С. Ранкин

1
Как мне избежать затрат, если часть контента между моими тегами EOF должна быть расширена, а часть нет?
Дженмикель Кот

2
... просто используйте обратную косую черту перед$
Jeanmichel Cote

@JeanmichelCote Я не вижу лучшего варианта :-) С обычными строками вы также можете подумать о смешивании кавычек "$a"'$b'"$c", но здесь нет аналога AFAIK.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

25

Используя тройник вместо кошки

Не совсем как ответ на первоначальный вопрос, но я все равно хотел поделиться этим: мне нужно было создать файл конфигурации в каталоге, который требовал наличия прав 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

в вашем случае используйте sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # мой конфигурационный файл foo = bar EOF'
likewhoa

5

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

cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfs
EOF

Это позволяет вам fstabне беспокоиться о случайном изменении любого его содержимого.


1

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

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

создаст тот же файл, что и:

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

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


2
какая оболочка? Я тестировал с bash 4.4 на Ubuntu 18.04, а также с bash 3.2 на OSX. Оба создали пустой файл при использовании <<testбез cat <<test.
wisbucky

Это сработало для меня на LInux Mint 19 Tara в Zsh
Джефф Лангендерфер

0

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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.