🐕

bashにパス通ってれば、どんなDockerイメージでもCMDでbash記法使える方法

2022/11/15に公開約1,600字2件のコメント

ユースケース

こんなことありませんか?

docker run(に相当するECS等の単発実行)の処理書いたぞ!
DockerfilenのCMD相当の部分に、bashで文字列変換したりするように書いたった

お、おいおい、こいつ動作してるシェルがbashじゃないやん。。。
bash記法使えない。。。うーん。。。

なぜこうなったか?

CMDでbash記法を使うなら、CMDが動作するシェルはbashでないといけません。
つまりENTRYPOINTがちゃんとbashになっている必要があると思います。
(他のシェルも同様だと思います)

ただし公式Dockerイメージが全部bashで動作しているわけがありません。
そうなるとコンテナ内でbashのスクリプトで処理したい場合などに困ります。

多分普通はこうするが

こんなことになるのは面倒なので公式Dockerイメージを使わず、自分でイメージ作ってビルドしたり。
ただビルドパイプラインが必要になったり、管理がどんどん煩雑になります。

対処法

コード

DockerfileのENTRYPOINT上書きでCMDの部分がbash上で動作するようにしてあげましょう。
(雑コードなのでセンスが無いのは許してください)

Dockerfileを作るもいいですが、最近の動作環境だと実行時にENTRYPOINT書き換えができたりすると思います。
そこに流し込んでもいいと思います。

FROM HOGEHOGE

ENTRYPOINT ["/usr/bin/env", "bash", "-c", "<<EOF /usr/bin/env bash -c \"$0 $*\" \nEOF"]

CMD ["ls", "$(echo bbbbbbbbb)", "aaaaaa$(date)"]

CMD部分はもちろん任意に書き換えてください。
サンプルとしてlsにしている理由は、わざとエラーさせ、想定通りの挙動であることを表現したいからです。

ls: cannot access 'bbbbbbbbb': No such file or directory
ls: cannot access 'aaaaaaTue': No such file or directory
ls: cannot access 'Nov': No such file or directory
ls: cannot access '15': No such file or directory
ls: cannot access '10:34:55': No such file or directory
ls: cannot access 'UTC': No such file or directory
ls: cannot access '2022': No such file or directory

このように正しくbash記法が動作していることがわかります。

解説

自明ですが、bash記法を使いたい場合はbash上で記述する必要があると上にも書きました。
コードの前段階のコードを抜粋で示します。

ENTRYPOINT ["/usr/bin/env", "bash", "-c", "<<EOF ls $(echo bbbbbbbbb) aaaaaa$(date) \nEOF"]

これであればわかりやすいと思います。
bashのシェル上にbash用に記述されたlsを流し込んでいます。

これをCMDから受け取りたい場合は、$0$*を利用して埋め込みます。
類似する方法を色々試しましたがきれいな書き方が中々見つからず、少々奇妙かもですがbash上にさらにbashでCMDを実行するようにしました。

Discussion

いろいろ勘違いされているようです。

まずこの記事の内容にbash記法はありません。$(...) はどの POSIX シェルでも対応しています。

ENTRYPOINT ["/usr/bin/env", "bash", "-c", "<<EOF /usr/bin/env bash -c \"$0 $*\" \nEOF"]

この引数部分を通常のシェルスクリプトで書き直すと以下のようになりますが、

<<EOF /usr/bin/env bash -c "$0 $*"
EOF

# 以下と同等
/usr/bin/env bash -c "$0 $*" <<EOF 
EOF

この <<EOF はなんの意味もありません。厳密には空行を標準入力に渡しているという違いはありますが、少なくとも今回の記事の内容では意味があることを行っていません。

/usr/bin/env もこの記事の範囲ではなんの効果もないので以下のように書き換えることができます。

ENTRYPOINT ["bash", "-c", "bash -c \"$0 $*\""]

また コードを組み立てて bash を実行するぐらいなら eval を使ったほうが良いです。

ENTRYPOINT ["bash", "-c", "eval \"$0 $*\""]

しかしながら、根本的には CMD の書き方の問題で、[ ... ] を使用しているのがシェル経由で実行されない理由です。つまり動作しているシェルがbashではないという話ではなく CMD の書き方が原因です。シェル経由で実行する shell form (参照)で書けば動作するはずです。

FROM HOGEHOGE
# ENTRYPOINT の指定は不要
CMD ls $(echo bbbbbbbbb) aaaaaa$(date)

ただ個人的には CMD でシェルの構文を使う必要はあるだろうか?と思っています。どうしても必要な場合は、私なら COPY でシェルスクリプトをイメージに追加して、それを呼ぶだけにするかもしれません。

コメントいただきありがとうございます!

$(...) はどの POSIX シェルでも対応しています。

POSIXについて不勉強でした、ありがとうございます!

シェル経由で実行する shell form

こちらも全くの不勉強でした、ありがとうございます!

個人的には CMD でシェルの構文を使う必要はあるだろうか?と思っています

こちらについては、記事が拙く慣れていないもので前提をどこまで書くべきか渋ってしまった結果だと反省しております。
AWS Batch等を始めとするCMDでshell formが利用できない[]のタイプ(register-job-definition)で構文をどう使うか?という回避のためにENTRYPOINTで対応させてました。

原因については 参照 の方にも詳しく書かれておりました!
大変勉強になりました!ありがとうございます!

Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, CMD [ "echo", "$HOME" ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: CMD [ "sh", "-c", "echo $HOME" ].

ログインするとコメントできます