シェルスクリプト弱者クソザコナメクジによる記事です。
優しい目で見てください。
文字列をbase64しようとしただけなのに、無駄にハマってしまった。
結論としては、 echo
の環境/シェルの違いによるものであった。
人類は、もう二度とこんな時間の失い方をしてはならないと思うので、ここにメモする。
in a nutshell
echo
は利用するシェルによって挙動が異なるので、移植性を考慮する必要がある場合、printfを使うべき。
例えば文字列をBase64符号化しようとしたときに以下の通り。
# だめ(環境、シェルによって違う挙動をするため)
echo hoge | base64
# よい(比較的ポータビリティに優れるため)
printf hoge | base64
シェルスクリプトで echo -n hoge | base64
して謎の挙動に惑わされた話
Basic認証のAPIを叩く野暮用があった。
シェルスクリプトをサクッとmacで書いて、パパっとRaspberry Piに置いておこうと思っていた。Basic認証ヘッダを組み立てるにあたり、ユーザ名及びパスワードをBase64エンコーディングする必要がある。
まず、直接curl が動作することを確認する。
# macOS sequoia(15.3.1), zsh
$ USER="hoge"
$ API_KEY="XXX"
$ curl -H "Authorization:Basic \
$( \
echo -n ${USER}:${API_KEY} | \
openssl base64\
)" \
"https://example.com/exampleApi"
問題なく動作したので、以下のシェルスクリプトを用意した。
#!/bin/bash
USER="hoge"
API_KEY="XXX"
curl -H "Authorization:Basic \
$( \
echo -n ${USER}:${API_KEY} | \
openssl base64\
)" \
"https://example.com/exampleApi"
しかし、何故か Invalid Authenticate
を返す。
なぜでしょうか?
切り分ける
Base64文字列部分を検証していく。
#!/bin/bash
HOGE="hoge"
echo $(echo -n ${HOGE} | base64)
実行結果↓
# macOS sequoia(15.3.1), zsh
$ sw_vers
ProductName: macOS
ProductVersion: 15.3.1
BuildVersion: 24D70
# 実行(1)
$ echo -n "hoge" | base64
aG9nZQ==
# 実行(2)
$ sh test.sh
LW4gaG9nZQo=
# Raspberry Pi / Debian 12
$ cat /etc/issue.net
Debian GNU/Linux 12
# 実行(3)
$ echo -n "hoge" | base64
aG9nZQ==
# 実行(4)
$ sh test.sh
aG9nZQ==
mac で、シェルスクリプト経由のとき(実行(2)
)のみ結果が異なっている。
ちなみにLW4gaG9nZQo=
はデコードすると-n hoge
なので、echoの-n
オプションの挙動がおかしそうである。
実行するシェルによって動作が異なるのだろうか?
shebang(#!/bin/bash
のところ)は意味が無いのであろうか?
さらに切り分ける。
#!/bin/bash
# 実行シェル確認
echo $SHELL
# ためしてみる
HOGE="hoge"
echo -n "echo -n \$HOGE | base64: "
echo $(echo -n ${HOGE} | base64)
# macOS sequoia(15.3.1), zsh
# bashで実行 => zsh!? 結果はOK
$ bash test2.sh
/bin/zsh
echo -n $HOGE | base64: aG9nZQ==
# zshで実行 => 結果OK
$ zsh test2.sh
/bin/zsh
echo -n $HOGE | base64: aG9nZQ==
# shで実行 => zsh!? echoの-nが聞いてない!? 結果もグチャグチャおかしい!?
$ sh test2.sh
/bin/zsh
-n echo -n $HOGE | base64:
LW4gaG9nZQo=E | base64:
# 直接実行 => shebangが無視されてzsh!? 結果はOK
$ chmod +x test2.sh
$ ./test2.sh
/bin/zsh
echo -n $HOGE | base64: aG9nZQ==
# sourceで実行=現在実行中のシェル環境(zsh)で実行された。結果もOK
$ source test2.sh
/bin/zsh
echo -n $HOGE | base64: aG9nZQ==
さて、なぜでしょうか?どうすればいいでしょうか?
echo
の挙動は環境によって異なり、ポータビリティ(移植性)の問題がある
詳細は以下記事に詳しい。
- OS、
echo
の実装により、エスケープシーケンスの解釈や、-n
オプションの有無が異なる - shebang を指定していたとしても、実体が何かは環境による
ということがわかった。
どうすればいいか?
printf
が移植性の観点では良い。
# macOS sequoia(15.3.1), zsh
$ echo $(printf hoge | base64)
aG9nZQ==
$ cat test.sh
#!/bin/bash
HOGE="hoge"
echo $(printf ${HOGE} | base64)
$ sh test.sh
aG9nZQ==
めでたしめでたし。
補足
実際には、printf にも稀に移植性の問題があるので注意してください。
また、歴史的経緯などの詳細が知りたい方は、以下の記事がおすすめです。