Как я могу записать heredoc в файл в скрипте Bash?

Ответы:

1075

Прочитайте Руководство по расширенному написанию сценариев Bash. Глава 19. Здесь документы .

Вот пример, который запишет содержимое в файл в /tmp/yourfilehere

cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
        This line is indented.
EOF

Обратите внимание, что в конечном EOF (The LimitString) не должно быть пробелов перед словом, потому что это означает, что LimitStringон не будет распознан.

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

#!/usr/bin/env bash

if true ; then
    cat <<- EOF > /tmp/yourfilehere
    The leading tab is ignored.
    EOF
fi

Если вы не хотите интерпретировать переменные в тексте, используйте одинарные кавычки:

cat << 'EOF' > /tmp/yourfilehere
The variable $FOO will not be interpreted.
EOF

Чтобы передать heredoc через командный конвейер:

cat <<'EOF' |  sed 's/a/b/'
foo
bar
baz
EOF

Вывод:

foo
bbr
bbz

... или записать свой heredoc в файл, используя sudo:

cat <<'EOF' |  sed 's/a/b/' | sudo tee /etc/config_file.conf
foo
bar
baz
EOF
Стефан Ласевский
источник
11
Вам даже не нужен Bash, эта функция есть и в оболочках Bourne / Korn / POSIX.
Янус Троелсен
5
что <<<, как они называются?
Юрген Пол
18
@PineappleUndertheSea <<<называются «Здесь строки». Код как tr a-z A-Z <<< 'one two three'приведет к строке ONE TWO THREE. Дополнительная информация на en.wikipedia.org/wiki/Here_document#Here_strings
Стефан Ласевский
8
У конечного EOF также не должно быть пробелов после него. По крайней мере, на bash это приводит к тому, что он не распознается как разделитель
carpii
10
Так как этот конкретный heredoc предназначен для буквального содержания, а не содержит замены, он должен быть, <<'EOF'а не <<EOF.
Чарльз Даффи
154

Вместо использования catперенаправления ввода / вывода может быть полезно использовать teeвместо этого:

tee newfile <<EOF
line 1
line 2
line 3
EOF

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

Livven
источник
19
Я бы предложил добавить > /dev/nullв конце первой строки, чтобы содержимое этого файла не отображалось на stdout при его создании.
Джо Кэрролл
11
Верно, но ваше решение привлекло меня из-за его совместимости sudo, а не из-за его краткости :-)
Джо Кэрролл
2
Как бы вы использовали этот метод для добавления в существующий файл?
MountainX
5
@MountainX Проверьте man tee. Используйте -aфлаг, чтобы добавить вместо перезаписи.
Ливвен
3
Для использования в скрипте конфигурации, который мне иногда нужно наблюдать, мне больше нравится этот, потому что он печатает содержимое.
Алоис Махдал
59

Замечания:

Вопрос (как написать здесь документ (он же heredoc ) в файл в скрипте bash?) Имеет (как минимум) 3 основных независимых измерения или подвопроса:

  1. Хотите перезаписать существующий файл, добавить в существующий файл или записать в новый файл?
  2. Ваш пользователь или другой пользователь (например, root ) владеет файлом?
  3. Вы хотите записать содержимое вашего heredoc буквально, или чтобы bash интерпретировал ссылки на переменные внутри вашего heredoc?

(Существуют другие измерения / подвопросы, которые я не считаю важными. Рассмотрите возможность редактирования этого ответа, чтобы добавить их!) Вот некоторые из наиболее важных комбинаций измерений вопроса, перечисленных выше, с различными различными идентификаторами-разделителями - нет ничего святое о EOF, просто убедитесь, что строка, которую вы используете в качестве идентификатора-разделителя, не встречается внутри вашего heredoc:

  1. Чтобы перезаписать существующий файл (или записать в новый файл), которым вы владеете, заменив ссылки на переменные внутри heredoc:

    cat << EOF > /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    EOF
  2. Чтобы добавить существующий файл (или записать в новый файл), которым вы владеете, заменив ссылки на переменные внутри heredoc:

    cat << FOE >> /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    FOE
  3. Чтобы перезаписать существующий файл (или записать в новый файл), которым вы владеете, с помощью буквального содержимого heredoc:

    cat << 'END_OF_FILE' > /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    END_OF_FILE
  4. Чтобы добавить существующий файл (или записать в новый файл), которым вы владеете, с буквальным содержимым heredoc:

    cat << 'eof' >> /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    eof
  5. Чтобы перезаписать существующий файл (или записать в новый файл), принадлежащий пользователю root, подставив ссылки на переменные внутри heredoc:

    cat << until_it_ends | sudo tee /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    until_it_ends
  6. Чтобы добавить существующий файл (или записать в новый файл), принадлежащий user = foo, с буквальным содержимым heredoc:

    cat << 'Screw_you_Foo' | sudo -u foo tee -a /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    Screw_you_Foo
TomRoche
источник
№ 6 самый лучший. Но как перезаписать содержимое существующего файла с # 6?
Александр Маков
2
@ Александр Маков: как перезаписать содержимое существующего файла с # 6? Опустить -a== --append; т.е. tee -a-> tee. Смотрите info tee(я бы процитировал это здесь, но разметка комментариев слишком ограничена.
TomRoche
1
Есть ли польза для №6, если использовать кошку и пайпинг вместо тройника sudo tee /path/to/your/file << 'Screw_you_Foo'?
codemonkee
Почему FOEвместо EOFпримера в приложении?
Becko
2
@becko: просто чтобы проиллюстрировать, что ярлык - это просто ярлык. Обратите внимание, что я использовал разные метки в каждом примере.
TomRoche
41

Чтобы построить ответ @ Livven , вот несколько полезных комбинаций.

  1. подстановка переменных, ведущая вкладка сохранена, файл перезаписи, эхо в стандартный вывод

    tee /path/to/file <<EOF
    ${variable}
    EOF
  2. без подстановки переменных , ведущие вкладки сохраняются, файл перезаписи, эхо в стандартный вывод

    tee /path/to/file <<'EOF'
    ${variable}
    EOF
  3. подстановка переменных, удаление первой вкладки , перезапись файла, вывод на стандартный вывод

    tee /path/to/file <<-EOF
        ${variable}
    EOF
  4. подстановка переменных, ведущая вкладка сохранена, добавление в файл , вывод на stdout

    tee -a /path/to/file <<EOF
    ${variable}
    EOF
  5. подстановка переменных, ведущая вкладка сохранена, файл перезаписи, нет эха на стандартный вывод

    tee /path/to/file <<EOF >/dev/null
    ${variable}
    EOF
  6. выше , могут быть объединены с , sudoа также

    sudo -u USER tee /path/to/file <<EOF
    ${variable}
    EOF
go2null
источник
16

Когда требуются права root

Если для файла назначения требуются права root, используйте |sudo teeвместо >:

cat << 'EOF' |sudo tee /tmp/yourprotectedfilehere
The variable $FOO will *not* be interpreted.
EOF
Серж Строобандт
источник
Можно ли передать переменные здесь документов? Как вы могли получить это так, что $ FOO был интерпретирован?
user1527227
@ user1527227 Как показано в файле примера: переменная $ FOO интерпретироваться не будет.
Серж Строобандт
Ниже я попытался объединить и организовать этот ответ с ответом Стефана Ласевского .
TomRoche
3
@ user1527227 Только не заключайте EOF в одинарные кавычки. Тогда $ FOO будет интерпретироваться.
imnd_neel
11

Для будущих людей, которые могут иметь эту проблему, работал следующий формат:

(cat <<- _EOF_
        LogFile /var/log/clamd.log
        LogTime yes
        DatabaseDirectory /var/lib/clamav
        LocalSocket /tmp/clamd.socket
        TCPAddr 127.0.0.1
        SelfCheck 1020
        ScanPDF yes
        _EOF_
) > /etc/clamd.conf
Джошуа Энфилд
источник
6
Не нужны круглые скобки: с cat << END > afileпоследующим heredoc отлично работает.
Гленн Джекман
Спасибо, это фактически решило другую проблему, с которой я столкнулся. После нескольких документов здесь возникли некоторые проблемы. Я думаю, что это имеет отношение к паренсу, поскольку с советом выше это исправило это.
Джошуа Энфилд
2
Это не сработает. Перенаправление вывода должно быть в конце строки, которая начинается с, catкак показано в принятом ответе.
Приостановлено до дальнейшего уведомления.
2
@DennisWilliamson Это работает, вот для чего нужны парены. Все catработает внутри подоболочки, и весь вывод подоболочки перенаправляется в файл
Izkata
2
@Izkata: Если вы посмотрите историю изменений этого ответа, скобки были удалены до того, как я оставил свой комментарий, а потом добавлены обратно. Комментарий Гленна Джекмана (и мой) применим.
Приостановлено до дальнейшего уведомления.
3

Как пример, вы можете использовать его:

Первый (создание ssh-соединения):

while read pass port user ip files directs; do
    sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directs
done <<____HERE
    PASS    PORT    USER    IP    FILES    DIRECTS
      .      .       .       .      .         .
      .      .       .       .      .         .
      .      .       .       .      .         .
    PASS    PORT    USER    IP    FILES    DIRECTS
____HERE

Второй (выполнение команд):

while read pass port user ip; do
    sshpass -p$pass ssh -p $port $user@$ip <<ENDSSH1
    COMMAND 1
    .
    .
    .
    COMMAND n
ENDSSH1
done <<____HERE
    PASS    PORT    USER    IP
      .      .       .       .
      .      .       .       .
      .      .       .       .
    PASS    PORT    USER    IP    
____HERE

Третье (выполнение команд):

Script=$'
#Your commands
'

while read pass port user ip; do
    sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip "$Script"

done <<___HERE
PASS    PORT    USER    IP
  .      .       .       .
  .      .       .       .
  .      .       .       .
PASS    PORT    USER    IP  
___HERE

Далее (с использованием переменных):

while read pass port user ip fileoutput; do
    sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip fileinput=$fileinput 'bash -s'<<ENDSSH1
    #Your command > $fileinput
    #Your command > $fileinput
ENDSSH1
done <<____HERE
    PASS    PORT    USER    IP      FILE-OUTPUT
      .      .       .       .          .
      .      .       .       .          .
      .      .       .       .          .
    PASS    PORT    USER    IP      FILE-OUTPUT
____HERE
MLSC
источник
-1

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

<<-End_of_file >file
       foo bar
End_of_file

Где →       это tabсимвол.

Дан
источник