0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

シェルスクリプトでechoを使うな

Posted at

シェルスクリプト弱者クソザコナメクジによる記事です。
優しい目で見てください。

文字列を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"

問題なく動作したので、以下のシェルスクリプトを用意した。

test.sh
#!/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文字列部分を検証していく。

test.sh
#!/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のところ)は意味が無いのであろうか?

さらに切り分ける。

test2.sh
#!/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 にも稀に移植性の問題があるので注意してください。

また、歴史的経緯などの詳細が知りたい方は、以下の記事がおすすめです。

0
1
0

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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?