dockerfileで長めの対話型コマンドをさばく(正規表現)

※人生で初めて書いたQiitaですので、いたらぬ点があれば(やさしく)ご指摘ください..
# 長めの対話型コマンドをdockerfileに書きたい。
dockerfileで環境構築を自動化する際、自分はつまずきました。
**dockerfile、expectファイル、正規表現３つを絡めて解説した記事があると便利かな**、と思い、執筆した次第です。
## 全部 YES でOKなコマンドなら..
```
yes | (y 入力を必要とするコマンド) 
```
と記述すればOK。
(引用：[コマンドの途中で聞いてくる yes を自動入力したい？それyesで出来るよ](https://www.agent-grow.com/self20percent/2018/08/06/linux-command-auto-yes/))

## パスワード設定とか、YES以外の文字列を打つコマンドの場合は？
**「expectファイル」(.exp)**を使います！　expect:「待ち受ける、予期する」 の意。
**「こう聞かれたらこう答えよ！」**という命令を書き込んでおくファイルです。

今回は、MYSQLをUbuntu(20.04)にインストール時に実行する、**mysql_secure_installation**コマンド を例に説明します。
[こちらの記事](https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04-ja)の「ステップ2 — MySQLの設定」作業を、dockerfileで自動化することを想定します。
それぞれの設定項目の説明は割愛しますが、6つの質問に対し、
**1. No**
**2. password (※自由に決めてください)**
**3. y** 
という、3種類の返答をしたい場合を考えます。(返答の順番はNo→password→y→y→y→y)

まず、dockerfileと、expectファイルはそれぞれこんな感じです。

```dockerfile:Dockerfile
FROM ubuntu:20.04

RUN apt-get update && apt-get install -y sudo wget vim curl gawk make gcc

#〜中略〜
#↓mysqlのインストールあれこれ
RUN yes | sudo apt install mysql-server
RUN sudo usermod -d /var/lib/mysql/ mysql
RUN sudo service mysql start

#↓本題のexp構文です！
COPY mysql_secure_installation.exp /
#dockerfileと同じディレクトリにあるmysql_secure_installation.expファイルの中身をコピー

RUN expect mysql_secure_installation.exp
#mysql_secure_installation.expの内容を実行

CMD [...]
```

COPY句で、**Dockerfileと同じディレクトリ**に置いてある**mysql_secure_installation.exp**ファイルを呼び出します。
次に、**RUN expect mysql_secure_installation.exp**で、expファイルをそのまま実行。
気になるmysql_secure_installation.expの中身がこちら↓。

```shell:mysql_secure_installation.exp
#!/usr/bin/expect
 #↑これはシバン(shebang)と呼ばれる決まり文句みたいなもの。「インタプリタにexpectを指定」してるそうです(^^; 
 #参考：https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14146257229

set ans1 No  #"返答1"は"No"をセット
set ans2  password  #"返答2"は"password"をセット
set ans3 y #"返答3"は"y"をセット

spawn sudo mysql_secure_installation #実行するコマンドをspawnで指定

expect {
        -regexp ".*VALIDATE PASSWORD.*" { #正規表現でキーワードをキャッチ、質問を特定
                send "${ans1}\n" #そのキーワード("VALIDATE PASSWORD")を含む質問が来たら、ans1(No)を送る(send)
                exp_continue
        }
        -regexp ".*set the password for root.*" {#同じく正規表現でキーワードをキャッチ、質問を特定
                send "${ans2}\n" #同じく、キーワード("set the password for root")を含む質問が来たら、ans2(password)を送る
                exp_continue
        }
        "Re-enter new password:" { #この質問は短かったので、正規表現は使わずに、一字一句全部指定して質問を特定しています。
                send "${ans2}\n"
                exp_continue
        }
         -regexp ".*anonymous users.*" {
                send "${ans3}\n"
                exp_continue
        }
         -regexp ".*root login remotely.*" {
                send "${ans3}\n"
                exp_continue
        }
        -regexp ".*test database and access to.*" {
                send "${ans3}\n"
                exp_continue
        }
        -regexp ".*privilege tables.*" {
                send "${ans3}\n"
                exp_continue
        }
}
exit 0
```
簡単に解説すると、

まず冒頭の3行、SET〜で3種類の返答を規定します。

次に、spawn句で、実行するコマンド(=expectで返答を待ち受けたいコマンド=sudo mysql_secure_installation)を指定します。

そしてexpect句で、待ち受ける質問を特定する方法と、その質問に対する返答(ans1〜3のいずれか)を指定します。
**-regexp**は、正規表現を使いますよ、というオプションです。
(regexp:regular expression,直訳で正規表現)

dockerfileを書く時は、まず適当なコンテナ等でコマンドを一通り試してから書くはずで、本来は**対話型の質問は一字一句わかってるわけだし、正規表現なんてなんで使うの？**とそもそもの疑問を持たれた方もいるかもしれません。
自分はそうはいっても、**そこそこ長い質問文を完全一致でexpect句に書くのは怖かった**ので(一文字足りないとか、スペースの有無とか)、regexpオプションを使いました。
キーワードだけ書くようにすれば、expectファイルもグッと見やすくなりますし、、ですが、**その質問固有のキーワードを抜き出すことに注意**してください！他の質問にも現れるキーワードは当然避ける！

exp_continueについて。本来ならばexpectは、対話がマッチした時点で指定したアクションを実行し、アクション実行後はexpectコマンドは終了、次のコマンドへ移るのですが、exp_continueを書くことで、**マッチ&アクション後もexpectから抜けずにexpectの処理を続けることができます**。[(この点参考にさせていただいた投稿)](https://qiita.com/ine1127/items/cd6bc91174635016db9b)

mysql_secure_installationのコマンドが一通り終わったら、exit 0で正常終了。

以上、長くなりましたが、dockerfile,expect,regexを絡めて使う参考例でした。
[その他参考にしたサイト1](https://qiita.com/u-dai/items/5b2b23de29481bd9ac73)　[2](https://www.hiroom2.com/2017/02/24/docker-dockerfile%E3%81%A7expect%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E4%BD%BF%E3%81%86%E3%81%A8%E8%A4%87%E9%9B%91%E3%81%AB%E3%81%AA%E3%82%8B%E4%BE%8B/)