2025年3月23日日曜日

bashでコマンドを作るときに便利な$@は""で括って"$@"で使うのが良い


背景

今後も時間を取られる場面がありそうなので、備忘録としてbashの $@ の利用例と注意点を記事に残します。

使ったもの

bash 5.1.16
docker 28.0.2

利用例 テスト用のdockerのmariadbを開くコマンド

下記のコマンド実行でテスト用のdocker上のmariadbが開くコマンドの例です。
./script/mariadb-test

ファイル構成
docker-compose.yml
docker-compose.test-yml
script/docker-compose-test
script/mariadb-test

docker-compose.yml
services:
mariadb:
image: "mariadb:11.4"
volumes:
- .volumes/mariadb:/var/lib/mysql
environment:
MARIADB_ROOT_PASSWORD: maria-root-pass
MARIADB_DATABASE: server_practice
MARIADB_USER: maria-user
MARIADB_PASSWORD: maria-pass
docker-compose.test.yml
services:
mariadb:
volumes:
- .volumes/mariadb-test:/var/lib/mysql

docker composeをdocker-compose.ymlにdocker-compose.test.ymlを上書きして実行するコマンドです。
このコマンドに渡った引数をdocker composeに渡すときに「"$@"」を利用しています。
script/docker-compose-test
#!/script/bash

DIR_HERE=$(dirname $(realpath $0))
DIR_REPO=$DIR_HERE/../
cd $DIR_REPO

docker compose -f docker-compose.yml -f docker-compose.test.yml "$@"

docker-compose-testで起動するmariadbを開くコマンドです。
コンテナの環境変数MARIADB_USERを利用するために、「\$MARIADB_USER」という評価前の記述をコンテナのbashに渡して実行時にコンテナ内の環境変数を割り当てています。
script/mariadb-test
#!/bin/bash

DIR_HERE=$(dirname $(realpath $0))

$DIR_HERE/docker-compose-test exec mariadb bash -c "mariadb --host=localhost --user=\$MARIADB_USER --password=\$MARIADB_PASSWORD \$MARIADB_DATABASE"

実行するとmariadbを開けます。
./script/mariadb-test


""で括らない$@では文字列が分解される

先程共有したscript/docker-compose-testの「"$@"」を「$@」にすると、下記のように引数が認識されずmariadbが開けません。

script/docker-compose-test
#!/bin/bash

DIR_HERE=$(dirname $(realpath $0))
DIR_REPO=$DIR_HERE/../
cd $DIR_REPO

docker compose -f docker-compose.yml -f docker-compose.test.yml $@ # ""なし

./script/mariadb-test
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

echoで解釈内容を確認したところ、""無しの$@では文字列がスペースで分解されるため意図しない動作になると分かりました。
echo values of \$@
for val in $@; do
echo $val
done

echo values of \"\$@\"
for val in "$@"; do
echo $val
done
values of $@
exec
mariadb
bash
-c
mariadb
--host=localhost
--user=$MARIADB_USER
--password=$MARIADB_PASSWORD
$MARIADB_DATABASE
values of "$@"
exec
mariadb
bash
-c
mariadb --host=localhost --user=$MARIADB_USER --password=$MARIADB_PASSWORD $MARIADB_DATABASE

"$@"と$@では下記の差異が発生すると分かりました。
# "$@"で組み上がるコマンド
docker compose -f docker-compose.yml -f docker-compose.test.yml exec mariadb bash -c "mariadb --host=localhost --user=\$MARIADB_USER --password=\$MARIADB_PASSWORD \$MARIADB_DATABASE"
# $@で組み上がるコマンド
docker compose -f docker-compose.yml -f docker-compose.test.yml exec mariadb bash -c mariadb --host=localhost --user=\$MARIADB_USER --password=\$MARIADB_PASSWORD \$MARIADB_DATABASE

文字列が分解されてしまうので "$@" を使いましょう。

bashの説明書のspecial paramsの@の説明でも "$@" での使い方が解説されています。
Bash Reference Manual 3.4.2 Special Parameters

似た表記に"$*"があるが、大体の場合は"$@"が適切

"$*"だと全ての引数が1つの文字列にまとめられてしまうので、今回の場合でも期待しない動作になります。
""無しの$*は$@と同じ結果になりました。
echo values of \"\$*\"
for val in "$*"; do
echo $val
done
values of "$*"
exec mariadb bash -c mariadb --host=localhost --user=$MARIADB_USER --password=$MARIADB_PASSWORD $MARIADB_DATABASE

参考: シェルスクリプトの $* と $@ の違いと雑学色々

おわり

$@で見た目は良さそうなのに意図通りに動かずに時間を取られましたが "$@" とすれば意図しない要素の分解が発生せずに利用できると分かりました。

参考

Prevent Bash From Breaking Quoted String
Bash Reference Manual 3.4.2 Special Parameters
シェルスクリプトの $* と $@ の違いと雑学色々

0 件のコメント :