CLOVER🍀

That was when it all began.

Dockerfile内で複数行の文字列を作りたい

これは、なにをしたくて書いたもの?

時々、Dockerfile内で複数行の文字列を作りたくなる時があるのですが、どうやったらいいのかすぐに忘れるのでメモして
おこうかな、と。

環境

今回の環境は、こちらです。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.6
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        370c289
 Built:             Fri Apr  9 22:47:17 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.6
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       8728dd2
  Built:            Fri Apr  9 22:45:28 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.4
  GitCommit:        05f951a3781f4f2c1911b05e61c160e9c30eaa8e
 runc:
  Version:          1.0.0-rc93
  GitCommit:        12644e614e25b05da6fd08a38ffa0cfe1903fdec
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

結論

試した結果としては、複数行の文字列を書く…とはちょっと違いますが、シェルのグルーピング(Compound Commands)を
使うのがよいでしょう。

Command Grouping (Bash Reference Manual)

bash(1): GNU Bourne-Again SHell - Linux man page

こんな感じです。

RUN { \
    echo '文字列1'; \
    echo '文字列2'; \
    echo '文字列3'; \
}

{}は、()にしてもOKです。

RUN ( \
    echo '文字列1'; \
    echo '文字列2'; \
    echo '文字列3'; \
)

echoを繰り返しているだけでは?と思うかもですが、コマンドをグルーピングするとリダイレクトを1回で済ませることが
できます。

RUN { \
    echo '文字列1'; \
    echo '文字列2'; \
    echo '文字列3'; \
} > /tmp/test1

RUN ( \
    echo '文字列1'; \
    echo '文字列2'; \
    echo '文字列3'; \
) > /tmp/test2

他の方法としては、echoコマンドの-nオプションを使って以下のように書けばよいみたいです(dash)。Ubuntu Linuxなど。

RUN echo -n "文字列1\n\
文字列2\n\
文字列3\n"

RUN echo -n '文字列1\n\
文字列2\n\
文字列3\n'

もしくは、こちら(bash、ash)。CentOSやAlpine Linuxなど。

RUN echo -n $'文字列1\n\
文字列2\n\
文字列3\n'

bashやashの場合は、$が必要で、クォートは'(シングルクォート)である必要があります。"(ダブルクォート)ではうまく
いきません。

echoの-nオプションは、\nのような\の部分をエスケープとして解釈してくれるようになります。

enable interpretation of backslash escapes

echo(1): line of text - Linux man page

If -e is in effect, the following sequences are recognized:

\\
backslash
\a
alert (BEL)
\b
backspace
\c
produce no further output
\e
escape
\f
form feed
\n
new line
\r
carriage return
\t
horizontal tab
\v
vertical tab
\0NNN
byte with octal value NNN (1 to 3 digits)
\xHH
byte with hexadecimal value HH (1 to 2 digits)

-nの有無で、こういう差が出ます。

$ echo '\n'
\n
$ echo -e '\n'

つまり、\n\で改行と継続行です。

ちなみに、クォートの閉じの前を\にするとエラーになります。

RUN echo -n "文字列1\n\
文字列2\n\
文字列3\n\"
/bin/sh: 1: Syntax error: Unterminated quoted string

最後の\を削除するか、以下のように改行を加えましょう。

RUN echo -n "文字列1\n\
文字列2\n\
文字列3\n\
"

お題

複数行の文字列、ということで。今回は以下の内容を複数行の文字列で記述して、/root/.bashrcに追記するということを
やってみます。

alias helloworld="echo 'Hello World!!'"
alias greeting="echo 'Welcome!!'"

### もしくは

alias helloworld='echo "Hello World!!"'
alias greeting='echo "Welcome!!"'

グルーピング(Compound Commands)を使った例

最初に、結果を。ここに書いたものをecho -nで試行錯誤するのが、後ろに続きます。

RUN { \
    echo ''; \
    echo "alias helloworld='echo "'"'"Hello World!!"'"'"'"; \
    echo "alias greeting='echo "'"'"Welcome!!"'"'"'" ; \
    echo; \
} >> /root/.bashrc

変数展開を使った例。

ARG message1='Hello World!!'
ARG message2='Welcome!!'

RUN { \
    echo ''; \
    echo "alias helloworld='echo "'"'"${message1}"'"'"'"; \
    echo "alias greeting='echo "'"'"${message2}"'"'"'" ; \
    echo; \
} >> /root/.bashrc

結果は、どちらも以下になります。

# tail -n 6 /root/.bashrc  
#    . /etc/bash_completion
#fi

alias helloworld='echo "Hello World!!"'
alias greeting='echo "Welcome!!"'

Ubuntu Linux 20.04 LTS、CentOS 8、Alpine Linux 3.13(.bashrcはないですが)のいずれでも同じことができます。

このあとは、echo -nを使って頑張ることに興味がある人は読んでみてください。

Ubuntu Linuxで試す

Ubuntu Linux 20.04 LTSのイメージを使って、いろんなバリエーションでやってみましょう。

Dockerfile

FROM ubuntu:20.04

RUN echo -n '\n\
alias helloworld="echo '"'"'Hello World!!'"'"'"\n\
alias greeting="echo '"'"'Welcome!!'"'"'"\n\
\n\
' >> /root/.bashrc

いろいろくっついていますが、'で文字列を開始した中に'を入れようと頑張っているだけです。

alias helloworld="echo '"'"'Hello World!!'"'"'"\n\
alias greeting="echo '"'"'Welcome!!'"'"'"\n\

Dockerイメージをビルド。

$ docker image build -t kazuhira/ubuntu:20.04 .

実行。

$ docker container run -it --rm kazuhira/ubuntu:20.04
root@710cc3521c1a:/# 

確認。

# tail -n 6 /root/.bashrc  
#    . /etc/bash_completion
#fi

alias helloworld="echo 'Hello World!!'"
alias greeting="echo 'Welcome!!'"

OKですね。

# helloworld
Hello World!!
# greeting
Welcome!!

クォートを"にしてみます。

RUN echo -n "\n\
alias helloworld='echo "'"'"Hello World!!"'"'"'\n\
alias greeting='echo "'"'"Welcome!!"'"'"'\n\
\n\
" >> /root/.bashrc

確認。

# tail -n 6 /root/.bashrc
#    . /etc/bash_completion
#fi

alias helloworld='echo "Hello World!!"'
alias greeting='echo "Welcome!!"'

aliasを使った実行結果は同じなので、省略。

'と"の差は想像に難くないですが、変数展開ですね。

FROM ubuntu:20.04

ARG message1='Hello World!!'
ARG message2='Welcome!!'

RUN echo -n '\n\
alias helloworld="echo ${message1}"\n\
alias greeting="echo ${message2}"\n\
\n\
' >> /root/.bashrc

RUN echo -n "\n\
alias helloworld='echo ${message1}'\n\
alias greeting='echo ${message2}'\n\
\n\
" >> /root/.bashrc

結果。

# tail -n 10 /root/.bashrc
#    . /etc/bash_completion
#fi

alias helloworld="echo ${message1}"
alias greeting="echo ${message2}"


alias helloworld='echo Hello World!!'
alias greeting='echo Welcome!!'

CentOS、Alpine Linux

続いて、CentOS 8で試してみましょう。

Dockerfile

FROM centos:8.3.2011

RUN echo -n $'\n\
alias helloworld="echo Hello World!!"\n\
alias greeting="echo Welcome!!"\n\
\n\
' >> /root/.bashrc

Ubuntu Linuxの時のように、'の中に'を含めるのはちょっとムリそうでした…。

Dockerイメージのビルド。

$ docker image build -t kazuhira/centos:8.3.2011 .

確認。

# tail -n 6 /root/.bashrc 
    . /etc/bashrc
fi

alias helloworld="echo Hello World!!"
alias greeting="echo Welcome!!"

aliasの実行結果は割愛します。

クォートが'しか使えないので、変数を含めた場合は

FROM centos:8.3.2011

ARG message1='Hello World!!'
ARG message2='Welcome!!'

RUN echo -n $'\n\
alias helloworld="echo ${message1}"\n\
alias greeting="echo ${message2}"\n\
\n\
' >> /root/.bashrc

展開されずにそのまま残ってしまいます。

# tail -n 6 /root/.bashrc
    . /etc/bashrc
fi

alias helloworld="echo ${message1}"
alias greeting="echo ${message2}"

最後は、Alpine Linux 3.13。Alpine Linuxには.bashrcがないので、適当にファイルに出力して確認。

Dockerfile

FROM alpine:3.13.5

ARG message1='Hello World!!'
ARG message2='Welcome!!'

RUN echo -n $'\n\
alias helloworld="echo Hello World!!"\n\
alias greeting="echo Welcome!!"\n\
\n\
' >> /tmp/test

RUN echo -n $'\n\
alias helloworld="echo ${message1}"\n\
alias greeting="echo ${message2}"\n\
\n\
' >> /tmp/test

ビルド。

$ docker image build -t kazuhira/alpine:3.13.5 .

確認。

$ docker container run -it --rm kazuhira/alpine:3.13.5 cat /tmp/test

alias helloworld="echo Hello World!!"
alias greeting="echo Welcome!!"


alias helloworld="echo ${message1}"
alias greeting="echo ${message2}"

CentOS 8と同じですね。

とまあ、echo -nを使って頑張って複数行の文字列にした割には、グルーピング(Compound Commands)に比べると
報われないので。

素直にいきましょう。