21
11

3分でわかる,Shell Script/Dockerfileの可読性を上げるヒアドキュメントの使い方

Last updated at Posted at 2024-09-05

これは何?

shell scriptやDockerfile等を書く時にヒアドキュメントを使うと完結かつ,わかりやすく書くことができます。

例: ヒアドキュメントを使わないDockerfileのRUN

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends sudo && \
    echo 'Creating ${USER_NAME} group.' && \
    addgroup ${USER_NAME} && \
    echo 'Creating ${USER_NAME} user.' && \
    adduser --ingroup ${USER_NAME} --gecos "my_portscanner user" --shell /bin/bash --no-create-home --disabled-password ${USER_NAME} && \
    echo 'using sudo' && \
    usermod -aG sudo ${USER_NAME} && \
    echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    rm -rf /var/lib/apt/lists/*

例: ヒアドキュメントを使ったDockerfileのRUN

RUN <<EOF
apt-get update -y
apt-get install -y --no-install-recommends sudo
echo 'Creating ${USER_NAME} group.'
addgroup ${USER_NAME}
echo 'Creating ${USER_NAME} user.'
adduser --ingroup ${USER_NAME} --gecos "my_portscanner user" --shell /bin/bash --no-create-home --disabled-password ${USER_NAME}
echo 'using sudo'
usermod -aG sudo ${USER_NAME}
echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
rm -rf /var/lib/lists
EOF

見た目がごちゃごちゃしないかつ,\や&&のつけ忘れでエラーになるみたいな事故を防げて便利です。
本記事ではヒアドキュメントの使い方を紹介します。


catコマンドでつかう

最も有名な例はcatコマンドでヒアドキュメントを使うことです。

cat <<EOF
heredoc> hoge
heredoc> fuga
heredoc> EOF
hoge
fuga

こういった形でEOFを入力するまで値を連続して入力することができます。

このヒアドキュメントを使うことでシェルスクリプト中でのファイルを生成して値を書き込む処理が簡潔にかけます。
以下の例はAWSのEC2のUserDataを使って起動時にdocker.httpdを起動するためのserviceファイルを作成しています。

#!/bin/bash
yum install -y docker
cat <<'EOF' > /etc/systemd/system/docker.httpd.service
[Unit]
Description=httpd Container
After=docker.service
Requires=docker.service
[Service]
Environment="CONTAINER_NAME=httpd-container"
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop ${CONTAINER_NAME}
ExecStartPre=-/usr/bin/docker rm ${CONTAINER_NAME}
ExecStartPre=/usr/bin/docker pull httpd
ExecStart=/usr/bin/docker run --rm --name ${CONTAINER_NAME} -p 80:80 httpd
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now docker.httpd
systemctl restart docker.httpd
systemctl daemon-reload

EOFを''で囲むことによりヒアドキュメント中での変数展開が行われないようにしています。
こちらの質問で@uasiさんに教えていただきました。


Dockerfileで使う

上記にも示したように,RUNで使えます。

RUN <<EOF
set -ex
apt-get update -y
apt-get install -y --no-install-recommends sudo
echo 'Creating ${USER_NAME} group.'
addgroup ${USER_NAME}
echo 'Creating ${USER_NAME} user.'
adduser --ingroup ${USER_NAME} --gecos "my_portscanner user" --shell /bin/bash --no-create-home --disabled-password ${USER_NAME}
echo 'using sudo'
usermod -aG sudo ${USER_NAME}
echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
rm -rf /var/lib/lists
EOF

コメント欄で@ko1nksmさんにお教えいただいたのですが,
ヒアドキュメントのRUNの冒頭でset -exすることでデバックがしやすくなるので追記しておきます。

  • -xでコマンドが実行する前にデバック情報を出してくれます。

    # -xなし
    5.082 6 packages can be upgraded. Run 'apt list --upgradable' to see them.
    5.085
    5.085 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
    
    # -xあり
    3.654 6 packages can be upgraded. Run 'apt list --upgradable' to see them.
    3.655 + apt install -y sud
    3.657
    3.657 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
    
  • -eでエラーが発生した際に途中でbuildを止めてくれます。

    • -eなし --> 途中でエラーが発生しても最後のコマンドが成功していれば止まらない
    FROM python:3.12.4-slim-bullseye
    RUN <<EOF
    apt update -y
    apt-get install -y --no-install-recommends sud # わざとタイポした
    ls # 成功するコマンド
    EOF
    
    docker buildx build .
    [+] Building 1.2s (6/6) FINISHED                                                                                                   docker:default
     => [internal] load build definition from Dockerfile                                                                                         0.0s
     => => transferring dockerfile: 148B                                                                                                         0.0s
     => [internal] load metadata for docker.io/library/python:3.12.4-slim-bullseye                                                               1.0s
     => [internal] load .dockerignore                                                                                                            0.0s
     => => transferring context: 2B                                                                                                              0.0s
     => [1/2] FROM docker.io/library/python:3.12.4-slim-   bullseye@sha256:26ce493641ad3b1c8a6202117c31340c7bbb2dc126f1aeee8ea3972730a81dc6         0.0s
     => CACHED [2/2] RUN <<EOF (apt update -y...)                                                                                                0.0s
     => exporting to image                                                                                                                       0.0s
     => => exporting layers                                                                                                                      0.0s
     => => writing image sha256:fc59842d7a59c1937163ce3712d71d2d56fd4fe76a7649f5f9665c7d7ef67e9a                                               0.0s
    
    • -eあり --> 途中でエラーが発生した段階で止まる。
    docker buildx build .
     > [2/2] RUN <<EOF (set -e...):
    0.192
    0.192 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
    0.192
    0.342 Get:1 http://deb.debian.org/debian bullseye InRelease [116 kB]
    0.373 Get:2 http://deb.debian.org/debian-security bullseye-security InRelease [27.2 kB]
    0.386 Get:3 http://deb.debian.org/debian bullseye-updates InRelease [44.1 kB]
    0.430 Get:4 http://deb.debian.org/debian bullseye/main amd64 Packages [8066 kB]
    1.644 Get:5 http://deb.debian.org/debian-security bullseye-security/main amd64 Packages [287 kB]
    1.679 Get:6 http://deb.debian.org/debian bullseye-updates/main amd64 Packages [18.8 kB]
    2.506 Fetched 8559 kB in 2s (3711 kB/s)
    2.506 Reading package lists...
    2.843 Building dependency tree...
    2.929 Reading state information...
    2.938 6 packages can be upgraded. Run 'apt list --upgradable' to see them.
    2.944 Reading package lists...
    3.260 Building dependency tree...
    3.344 Reading state information...
    3.394 E: Unable to locate package sud
    ------
    Dockerfile:2
    --------------------
       1 |     FROM python:3.12.4-slim-bullseye
       2 | >>> RUN <<EOF
       3 | >>> set -e
       4 | >>> apt update -y
       5 | >>> apt-get install -y --no-install-recommends sud
       6 | >>> ls
       7 | >>> EOF
       8 |
    --------------------
    ERROR: failed to solve: process "/bin/sh -c set -e\napt update -y\napt-get install -y --no-install-recommends sud\nls\n" did not complete successfully: exit code: 100
    

追記: コメントで教えていただいたのですが,

RUN <<EOF bash -ex

のように書けるのでこっちのほうが本質的な処理がRUNの中に入るので可読性が高そうです。


Terraformで使う

terraformでuser_dataに対してコマンドを投入する際にヒアドキュメントが使えます。
ヒアドキュメントを入れ子で使う際にはEOF2を使います。

resource "aws_instance" "example" {
  ami           = "ami-00c79d83cf718a893"
  instance_type = "t3.micro"
  # attach IAM Role
  iam_instance_profile = aws_iam_instance_profile.ssm_instance_profile.name

  tags = {
    Name = "sigma-ec2"
  }

  user_data = <<EOF
#!/bin/bash
yum install -y docker
cat <<'EOF2' > /etc/systemd/system/docker.httpd.service
[Unit]
Description=httpd Container
After=docker.service
Requires=docker.service
[Service]
Environment="CONTAINER_NAME=httpd-container"
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop $${CONTAINER_NAME}
ExecStartPre=-/usr/bin/docker rm $${CONTAINER_NAME}
ExecStartPre=/usr/bin/docker pull httpd
ExecStart=/usr/bin/docker run --rm --name $${CONTAINER_NAME} -p 80:80 httpd
[Install]
WantedBy=multi-user.target
EOF2
systemctl enable --now docker.httpd
systemctl restart docker.httpd
systemctl daemon-reload
EOF
}

Terraformが$を解釈しないように$${CONTAINER_NAME}のようにしてにしてescapeしています。
EOF2を''で囲んでいるのは上記と同じで変数展開をしないためです。


総括

  • ヒアドキュメントを使うことで見た目がスタイリッシュで可読性が高くなるのでおすすめ。
  • 他にも使える例を探してみたい。
21
11
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
11