サーバー側の設定値をまとめて書き換える手順を、エージェントに作ってもらった夜のことです。Linux の環境ではすんなり動いたので、同じ手順をそのまま香川の自宅の iMac にも流しました。
ところが、置き換えは一行も進まず、ターミナルには見慣れないエラーが一つ出ただけでした。原因は、sed -i という短いオプションの書き方の違いでした。
個人開発で壁紙アプリの配信まわりを触っていると、サーバー(Linux)と手元の iMac を行き来する手順が日常になります。エージェントが返す手順書には、Linux で動くことを前提にした書き方がよく混ざります。その手順を自分の Mac で動かす前に、何を見ればよいか。小さな再現とあわせて、書き残します。
最初にお伝えしたいのは、「動いた」の置き場所です
あの夜の私は、Linux で動いたことを「手順が正しい」ことの証拠にしていました。いま思えば、確かめた場所を取り違えていたのだと思います。しかし、動いたのは手順ではなく、その手順と、その環境の組み合わせです。
sed のように同じ名前でも中身が違うコマンドは、組み合わせが変わると結果が変わります。macOS に入っている sed と、Linux で一般的な GNU 版の sed は、その代表例です。
何が違うのか、まず手元で再現しました
今回の再現は、手元の Linux 環境(GNU 版の sed 4.8)で行いました。macOS 側は実機で測っていないため、そちらは公式のマニュアルにある仕様として書きます。
設定ファイルを一つ用意して、old を new に置き換えます。
printf 'API_HOST=old.example.com\nMODE=old\n' > app.env
sed -i 's/old/new/' app.env
cat app.envGNU 版では、これで二行とも new に変わります。ファイル名も増えません。
次に、macOS 向けの書き方として知られる「-i の直後に空の文字列を置く」形を、同じ Linux で流してみました。
sed -i '' 's/old/new/' app.env結果は次のとおりでした。
sed: can't read s/old/new/: No such file or directory
終了コードは 2、ファイルの中身は old のままです。GNU 版は '' を「空のスクリプト」と読み、続く s/old/new/ をファイル名として開こうとして失敗した、ということです。
つまり、同じ -i でも、続く引数の意味が環境で違います。Linux 向けの短い形を Mac に持ち込めば止まり、Mac 向けの形を Linux に持ち込んでも止まります。
| 書き方 | GNU 版(手元で実測) | macOS 同梱の sed(マニュアル上の仕様) |
|---|---|---|
sed -i 's/old/new/' f | 置き換わる(終了コード 0) | 「s/old/new/」を拡張子と読み、次の引数をスクリプトとして解釈しようとするため、エラーになる |
sed -i '' 's/old/new/' f | 失敗する(終了コード 2・内容は変わらない) | 置き換わる |
sed -i.bak 's/old/new/' f | 置き換わる(f.bak が残る) | 置き換わる(f.bak が残る) |
表の三行目だけが、どちらでも同じ意味になります。ここが今回の出口です。
どちらでも動く書き方は、三つあります
一つ目は、拡張子をくっつけて書く -i.bak の形です。GNU 版でも f.bak を残して置き換わり、macOS でも同じ動きになります。
二つ目は、perl -pi -e を使う方法です。同じ設定ファイルで試すと、置き換わり、余計なファイルも増えませんでした。
perl -pi -e 's/old/new/' app.envperl は macOS にも最初から入っているため、手順書を使い回したいときには気楽です。ただし、正規表現の細かな書き方は sed と少し違うので、複雑な置き換えは別途確かめてください。
三つ目は、そもそも「どちらの sed か」を先に判定する書き方です。
if sed --version >/dev/null 2>&1; then
SED_INPLACE=(sed -i) # GNU 版
else
SED_INPLACE=(sed -i '') # macOS 同梱の版
fi
"${SED_INPLACE[@]}" 's/old/new/' app.env--version は GNU 版にはありますが、macOS 同梱の版にはない、という違いを使っています。判定してから使うので、手順書の書き手がどちらの環境を想定していたかに左右されません。
バックアップを使うなら、後始末まで手順に入れます
-i.bak は安全ですが、一つ気をつけたい点があります。ファイルが増えるのです。三つのファイルに流すと、元のファイルに加えて .bak が三つできました。ディレクトリのファイル数が 5 から 8 に増え、後始末の rm で 5 に戻りました。
Git のリポジトリで流したときは、変更されたファイルの横に、追跡されていない .bak が並びました。
M g1.txt
M g2.txt
M g3.txt
?? g1.txt.bak
?? g2.txt.bak
?? g3.txt.bak
そのまま git add . をすると、バックアップごと記録されてしまいます。自分のサイトのリポジトリで一度やりかけて、気づいたのはコミットの直前でした。置き換えの一行のすぐ後ろに、後始末の一行を並べておくのが私のやり方です。
sed -i.bak 's/old/new/' g*.txt && rm -f g*.txt.bakまた、macOS 側でよく聞く落とし穴として、-i -e と書くと、-e が拡張子として読まれ、ファイル名-e というバックアップが生まれる、というものがあります。GNU 版で同じ書き方を流すと、余計なファイルは増えませんでした。環境によって結果が違う典型なので、-i -e の形は避けるようにしています。
エージェントに頼むときは、環境を一言添えます
ここまでの確かめを、毎回自分の頭でやるのは大変です。そこで、依頼の文に環境を一言添えるようにしました。
この手順は macOS(iMac)と Linux の両方で動かします。
sed の -i は、どちらでも同じ意味になる -i.bak の形にして、
置き換えの直後にバックアップを削除する行も入れてください。これだけで、返ってくる手順の癖がかなり揃うかもしれません。少なくとも私の手元では、手戻りが減ったと感じています。それでも、手元で流す前には、置き換え対象のファイルを一つだけ選んで、先に試します。
動いたのは手順ではなく、手順と環境の組み合わせです。 この一文を、手順書を受け取るたびに思い出すようにしています。
次にエージェントから sed -i を含む手順を受け取ったときは、まず -i の直後がどうなっているかだけ見てみてください。そこが -i.bak か perl -pi でなければ、一ファイルで試してから全体に流すところから始めていただければと思います。