こんにちは、「CYBIRD Advent Calendar 2025」14日目を担当する@R_araiです。
13日目は@ice_matcha3さんの「Fargate Spotの中断が頻発したためどうにかした話」でした。
はじめに
株式会社サイバードにてAndroidアプリの運用開発を担当しております。
ある日Androidアプリを改修した際にビルドマシンでエラーが発生しました。
解決策自体はさほど難しくない内容でしたが、ビルドする前に工程が一つ増えてしまうことが分かり、流石にビルド時に毎回対応するのはめんどくさいし他タイトルでも発生するかも…という懸念があったので自動化することにしました。
自動化するならjenkinsでも使っているシェルスクリプトが一番分かりやすいかなと思ったのですが、そもそもシェルスクリプトは全くの素人で学習コストもあまりかけられない状況だった為、Geminiを使ってサクッと自動化プログラムを作ったそんなお話になります。
こんな人におすすめ
- シェルスクリプト未経験〜初心者
- AIを使ったコード生成の仕方を知りたい方
- ファイルの一部書き換えなど単調作業の自動化を考えている方
目的/ゴール
- ビルドマシンで発生したエラーを解決し、今後同じエラーが発生しないようにする。
- エラーを解決する為のシェルスクリプトをGeminiを活用しながら作成する。
問題が発覚した経緯
改修中のアプリでローカル環境でのビルドに問題ないことを確認できたのでビルドマシンでのビルドを行なったところエラーが発生した。
エラーの内容はgradle.propertiesというファイルに定義されているプロパティが存在しないというものだった。
調査したところ、今回の改修でgradle.propertiesに新しいプロパティが追加されていた。
ビルドマシンではビルドを行う前にプロジェクト側のgradle.propertiesをビルドマシン側の設定が定義されたgradle.propertiesで上書きしていたことで新しく追加したプロパティが消えていたことが原因でした。
上書きしていたgradle.propertiesには本番ビルドするのに必要なストアファイルのパスなどが定義されているのでビルドマシンはビルド前にコピーして解決する流れになっていた。
ですがコピーだけだと、今回みたいにgradle.propertiesに定義が追加される度に手動でビルドマシン側も修正する必要が出てきてしまうことがわかりました。
これはめんどくさいのでいっそのこと自動で解決する機能を実装しようという経緯になります。
解決したいこと/どんなことをできるようにするか
- gradle.propertiesに定義を追加しても手動でビルドマシン側のgradle.propertiesを修正せずともビルドできるようにする。
- 今回の対応前のバージョンをビルドしても問題が発生しないようにする。
- 他プロジェクトでも同じ問題が発生したら使い回せるように柔軟性も考えて作成する。
解決するための要件
- ビルドマシン内のgradle.propertiesに定義された以外のプロパティが追加されていた場合、その要素を追加する機能を作る。
- 他プロジェクトや別のビルドマシンで流用することを想定してjenkinsのJob内にシェルを書くのではなくてシェルスクリプトファイルとして作る。
- プロジェクトによって適用するファイル名が違う場合を考慮して、修正対象のファイルとパラメータが追加されたファイルを指定できるようにする。
思いついた方法
ビルドマシン側で用意しているgradle.propertiesをWorkSpace側のgradle.propertiesにそのままコピーするのをやめて、ビルドマシン側で用意しているgradle.propertiesの要素と同一要素がWorkSpace側のgradle.propertiesにあった場合その行だけ上書きする。
具体的にどんな処置にするか
移行元ファイルと移行先ファイルを手動で設定出来るようにする。
key=valueの形でプロパティが定義されているので移行元ファイルの行毎にkeyを取得して移行先に同じkeyの行が存在するか検索しヒットしたらその行を書き換える。
実際にやってみた
とりあえずGeminiに丸投げしてみる。
丸投げといってもどんなことをできるようにするかで記載したものをそのまま質問してしまうとどんな処理が返ってくるか検討もつかないのである程度処理の流れが把握できるように具体的にどんな処置にするかで記載した内容を元に質問します。
シェルスクリプトで以下の内容を行う処理を教えて。
Aというファイルで1行ずつ以下の処理を行う。
現在の文字列を変数aに格納する。
=という文字が出てくるまでの文字列を変数bに保存する。
Bというファイルに変数bで始まる文字列を検索して、存在した場合変数aに格納した文字列に置き換える。
出てきたのが↓
#!/bin/bash
# Aファイルのパス
FILE_A="A.txt"
# Bファイルのパス
FILE_B="B.txt"
# Aファイルが存在するか確認
if [ ! -f "$FILE_A" ]; then
echo "エラー: ファイル $FILE_A が見つかりません。"
exit 1
fi
# Bファイルが存在するか確認
if [ ! -f "$FILE_B" ]; then
echo "エラー: ファイル $FILE_B が見つかりません。"
exit 1
fi
# Aファイルを1行ずつ読み込み処理
while IFS= read -r line_a; do
# 現在の文字列を変数aに格納
a="$line_a"
# =という文字が出てくるまでの文字列を変数bに保存
# =がない場合は行全体を変数bとする
if [[ "$a" == *"="* ]]; then
b="${a%%=*}"
else
b="$a"
fi
echo "処理中の行 (A): $a"
echo "検索キー (b): $b"
# Bファイルに変数を検索して、存在した場合変数aに格納した文字列に置き換える
# sed -i はファイルを直接編集します。
# macOS の場合、sed -i の後に拡張子が必要です (例: sed -i '' ...)
# GNU sed (Linux) の場合、拡張子なしで動作します。
# 以下はGNU sedを想定しています。macOSの場合は適宜修正してください。
if grep -q "^$b" "$FILE_B"; then
echo " $b で始まる行が $FILE_B で見つかりました。置き換えます。"
# sed で置き換える際は、変数bが正規表現として解釈される可能性があるのでエスケープが必要です。
# ここでは、sed の区切り文字として _ を使用し、/ をエスケープしないようにしています。
# また、bを検索し、その行全体をaに置き換えています。
sed -i "s|^${b}|${a}|" "$FILE_B"
echo " $FILE_B を更新しました。"
else
echo " $b で始まる行は $FILE_B で見つかりませんでした。スキップします。"
fi
echo "--------------------"
done < "$FILE_A"
echo "処理が完了しました。"
コードが生成されたので試しに実行してみる。
実行結果
storeFile=hoge
password=1234
storeFile=aaaa
password=1234
newValue=bbbb
storeFile=hoge=aaaa
password=1234=1234
newValue=bbbb
なんか思っていたのと違う…
どうやら"s"コマンドは検索にヒットした箇所を置き換えているだけなので行全体を書き換えることはできないらしい。
検索にヒットした行全体を書き換える機能が分かればなんとかなりそう。
それ以外の処理はいじらなくても良さそうなので行全体を置き換える方法をGeminiに聞いてみる。
Geminiに以下の質問をする。
Macでシェルスクリプトを使って指定した文字列が含まれている行を見つけてその行全体を指定した文字列に書き換えるコードを教えて。
出たのが↓
sed -i "" "/$SEARCH_STRING/c\\$REPLACE_STRING" "$FILE"
"c"コマンドがcの後に続く文字列で検索にヒットした行を置き換えることができるらしい。
の、はずが以下のエラーが出た
sed: 1: "/android.debug.obsolete ...": extra characters after \ at the end of c command
正規化で使っている文字がダメなのかエラーが出続ける。
sed -i "" "/$SEARCH_STRING/c $REPLACE_STRING" "$FILE_B"
などいろいろ試したがなぜか解決できず…
仕方ないので別の方法を模索
検索にヒットした行を削除し、該当する行をコピーする方法を試してみる。
置き換え処理以外は問題がないようなのでGeminiに行削除と行コピーの方法を質問する。
FILE_Bというファイル内でkeyという文字列が含まれる行を削除する方法を教えて
出てきたのが↓
sed -i "" "/$key/d" "$FILE_B"
FILE_Bというファイル内に変数copy_lineの文字列を追加する方法を教えて
出てきたのが↓
sed -i "" "1s|^|$copy_line\n|" "$FILE_B"
上記のコードを最初に生成したシェルスクリプトの"s"コマンドが使われていた箇所に置き換えて実行してみる。
storeFile=hoge
password=1234
storeFile=aaaa
password=1234
newValue=bbbb
newValue=bbbb
storeFile=hoge
password=1234
置き換え自体はできたが空行ができてしまった。
このままだと実行する度に空行が増えてしまうので最後に空行を削除する処理を追加する。
Geminiに以下の質問をする。
FILE_B内の空の行を削除する方法を教えて
出てきたのが↓
# 実行する度に改行が増えてしまうので改行を埋めて整理します。
sed -i "" "/^$/d" "$FILE_B"
実行結果
storeFile=hoge
password=1234
storeFile=aaaa
password=1234
newValue=bbbb
newValue=bbbb
storeFile=hoge
password=1234
いい感じにできました!
じゃあ後はこれをビルドマシンで叩くけるようにするだけ、と思ったのも束の間新たな問題発見…
知らない謎ファイルが増殖してる…
.!00000!.gradle.propertiesなる空のファイルがシェルスクリプトを実行する度に生成される現象が発生していた。
どうも一行削除しているsedコマンドで発生しているみたいだが最終的に根本原因は分からなかった。
しょうがないので一番最後にゴミファイルを削除するコマンドを追加する。
Geminiに以下の質問をする。
指定したディレクトリ直下で指定したファイル名が含まれるファイルを削除する方法を教えて
出てきたのが↓
# sed -i "" "/$key/d" "$FILE_B"を実行するとなぜか !00000!gradle.propertiesというゴミファイルが増殖する問題が解決できなかったので該当ファイルを全て削除する。
DIR_PATH=$(dirname "$FILE_B")
find "$DIR_PATH" -maxdepth 1 -type f -name "*!gradle.properties" -print0 | xargs -0 rm
rm コマンドは気をつけて使用する必要があるのでやっていることを細かく記載します。
DIR_PATH=$(dirname "$FILE_B")
書き換えられたファイルのあるディレクトリにゴミファイルが生成されるのでdirnameコマンドを使って書き換えるファイルが存在するディレクトリを$DIR_PATHに格納
find
ファイルを検索する為のコマンド。
指定された条件に一致するファイルやディレクトリを見つける。
-maxdepth 1
find が検索するディレクトリの深さを制限するオプション
1を指定すると指定されたディレクトリの直下のみを対象にします。
-type f
検索対象をファイルのみに限定するオプション
削除対象をファイルのみにすることで意図しないディレクトリの削除を防ぐ。
-name "*!gradle.properties"
検索するファイル名を指定するオプション
-print0
find が見つけたファイル名をヌル文字 (\0) で区切って標準出力に出力するオプションです。
これがないともしファイル名にスペースなどが含まれていると引数の区切りと誤解されて正しくファイルが削除されないなどといった問題を防ぐことができます。
|
パイプと言ってfindコマンドで受け取ったファイル名を次に記述するxargsコマンドに渡すのに使用する。
xargs -0 rm
findから受け取ったファイル名リストを使ってファイルを削除する。
いい感じゴミファイルだけ削除ができるようになりました!
次に後回しにしていた移行元ファイルと移行先ファイルをコマンドラインの引数で指定できるようにします。
Geminiに以下の質問をする。
シェルスクリプトで2つのファイルを指定する方法を教えて
出てきたのが↓
# 引数の数をチェック
if [ "$#" -ne 2 ]; then
echo "使用法: $0 <ファイル1のパス> <ファイル2のパス>"
exit 1
fi
file1="$1" # 最初の引数をfile1変数に格納
file2="$2" # 2番目の引数をfile2変数に格納
$#:コマンドの引数の数を取得
$0:実行中のシェルスクリプト自身のファイル名
$1:1番目の引数
これをFILE_AとFILE_Bに置き換えてシェルスクリプトは完成
あとはjenkinsなどで使用用途に合わせてディレクトリをしてしてコマンドを叩くようにすれば完成です。
お疲れ様でした!
最後に
いかがだったでしょうか?
シェルスクリプトが分からなくてもある程度のプログラミングの知識とGeminiがあれば単純作業を自動化することができました!
これまで単純作業だけど学習コストだったり面倒くさかったりで中々自動化に踏み切れなかった人でもAIのおかげでハードルが下がったのではないでしょうか?
あまり難しく考えずできるところからじゃんじゃん自動化して行きたいですね!
以上になります!
明日のCYBIRD Advent Calendar 2025 15日目は@suzu_ayaさんの「去年より詳細にPHPで画像ファイルの色の分類をしてみた」です!
