コマンドのエラー後にミスが起きる原因|sudo・--force・再実行の落とし穴

エラーそのものより、エラーのあとに打つコマンドが事故を生みます。止まる・読む・確かめる・報告する、の手順を説明します。

エラーのあとにやりがちな行動と、代わりにやる確認を左右に並べた図
エラーのあとにやりがちな行動と、代わりにやる確認を左右に並べた図

コマンドを打ってエラーが出ること自体は、ベテランでも毎日のように起きます。エラーはコマンドが「ここで止めました」と知らせてくれた状態で、多くの場合まだ何も壊れていません。本番作業の事故が起きやすいのは、むしろそのあとです。エラーを消そうとして打った 2 本目、3 本目のコマンドが、最初の失敗より大きな被害を出します。この記事では、エラーのあとの行動で事故が起きる仕組みと、手を止めて確かめる手順を整理します。

エラーのあとに事故が起きる理由

エラーが出ると、作業は予定から外れます。手順書には次に打つコマンドが書かれていないので、その場で判断することになります。しかも「早く元に戻したい」「自分のミスを大きくしたくない」という気持ちが働き、確認を飛ばしやすくなります。手順書どおりに進めていたときより、エラー直後のほうが判断の材料は少なく、急ぐ理由は多いのです。

さらに、エラーのあとに打つコマンドは、エラーを回避するためのものになりがちです。権限で止められたら権限を上げる、確認で止められたら確認を外す、という方向です。コマンドが止めてくれた安全装置を、自分の手で外していることになります。ベテランがエラーのあとに一度手を離すのは、この流れを知っているからです。

エラーのあとの行動起きること代わりにやること
エラー文を読まずに打ち直す同じ失敗や別の失敗を重ねる何が・どこで・なぜを読む
Permission denied に sudoroot 所有のファイルが残る、守りを外す誰が何に書けないかを ls -l で見る
-f や --force で押し切る止めてくれた確認ごと消えるなぜ拒否されたかを先に調べる
検索で見つけたコマンドを貼る意味の分からない変更が本番に入るman や公式ドキュメントで確かめる
そのまま再実行する途中まで成功した処理が二重に走る今の状態を確認してから決める

エラー文を最後まで読む:何が・どこで・なぜ

エラー文には、多くの場合「どのコマンドが」「どの対象で」「なぜ」失敗したかが書かれています。赤い文字や Error という単語を見た瞬間に読むのをやめてしまうと、肝心の対象と理由を見落とします。まずは 1 行を最後まで読み、3 つの要素に分けてみてください。

$ cp app.jar /opt/app/current/cp: cannot create regular file '/opt/app/current/app.jar': Permission denied# 何が:cp(ファイルを作れなかった)# どこで:/opt/app/current/app.jar# なぜ:Permission denied(書き込み権限がない)
エラー文を 3 つに分けて読む

エラーが何行も出たときは、最後の行ではなく最初のエラーから読みます。1 つ目の失敗が原因で、後ろのエラーはその影響で連鎖して出ていることがよくあるためです。ビルドやテストのログは長いので、grep で error や denied、failed などの語を探すと最初の失敗を見つけやすくなります。

エラー文はそのまま控える

エラー文は、言い換えずにそのままコピーしてメモに残します。あとで報告や検索をするとき、要約した文章では手がかりが失われます。画面をスクロールして流れてしまう前に控えるのが習慣です。

Permission denied に sudo、エラーに --force を返さない

Permission denied は「今のユーザーには許されていない」という意味で、多くは設計どおりの拒否です。反射的に sudo を付けると、通るようにはなりますが、作られたファイルやディレクトリが root の所有になります。そのあと通常のユーザーで動くアプリやデプロイが、そのファイルを書き換えられずに別の Permission denied を出すことがあります。chmod -R 777 で開けるのは、誰でも書き換えられる状態を作るのでさらに危険です。

whoamils -ld /opt/app/currentls -l /opt/app/current/app.jarnamei -l /opt/app/current/app.jar   # パスの各階層の権限を一覧する
sudo の前に、誰が何に書けないのかを確かめる

sudo には、付けても効かない場面があることも知っておくと役立ちます。sudo echo 'x' > /etc/app.conf のようなリダイレクトは、sudo ではなく手元のシェルがファイルを開くため、権限のない状態で失敗します。ここでさらに別の書き方を試し始めると、意図しないファイルを上書きしかねません。権限の問題は、所有者とグループを確かめ、必要な人に相談してから直すのが確実です。

-f や --force も同じ構図です。git push が rejected になるのは多くの場合リモートに自分の手元に無いコミットがあるから、ツールが「上書きしますか」と止めるのは既存のものが消えるからです。force はその確認を飛ばして実行する指定なので、拒否された理由を調べないまま付けると、止めてもらえたはずの事故がそのまま起きます。force を付けたくなったら、何を守るための拒否なのかを一文で言えるかを自分に問います。

検索で見つけたコマンドを、意味を確かめずに貼らない

エラー文で検索すると、解決策としてコマンドが見つかります。便利ですが、そのコマンドは別の人の別の環境で効いたものです。OS やバージョン、権限の前提が違えば、同じエラー文でも原因が違います。回答には chmod -R 777、rm -rf で丸ごと消して作り直す、git reset --hard など、手元では効くが本番では危ない操作もよく含まれています。

  • 貼る前に、コマンドの各オプションの意味を man やヘルプ(--help)で確かめる。
  • そのコマンドが何を変更し、何を消すのかを一文で説明できるか確認する。
  • 回答の前提(OS、ディストリビューション、バージョン)が自分の環境と同じか見る。
  • curl で取得したスクリプトを直接 sh や bash に流す書き方は、中身を読んでから実行する。
  • 本番で試す前に、検証環境や手元で同じ結果になるか確かめる。

失敗したのに次へ進む:; と && と set -e

手で打つときもスクリプトでも、前のコマンドが失敗したことに気づかず次へ進むと事故になります。シェルは ; や改行で区切ったコマンドを、前の成否に関係なく順に実行します。&& でつなぐと、左のコマンドが成功(終了コード 0)したときだけ右を実行します。cd に失敗したまま rm が動く事故は、この違いから起きます。

cd /var/app/build ; make clean   # cd が失敗しても make clean が動くcd /var/app/build && make clean  # cd が成功したときだけ動くcd /var/app/build || exit 1      # スクリプトなら失敗で終了する
前のコマンドが成功したときだけ次を実行する

スクリプトの先頭に set -e を書くと、失敗したコマンドの時点でスクリプトが終了します。ただし、if や while の条件に書いたコマンド、&& や || でつないだ途中のコマンド、! で反転したコマンドの失敗では止まりません。set -e は万能ではないので、破壊的な操作の直前は && や || exit 1 で明示的に守ります。

終了コードを確かめる:$? と pipefail

コマンドが成功したかどうかは、画面の文字ではなく終了コードで判断できます。終了コードは直前のコマンドが返す数値で、0 が成功、0 以外が失敗を表します。bash では $? で見られます。echo $? 自体もコマンドなので、確かめたいコマンドの直後に打たないと、別のコマンドの結果を見ることになります。

$ cp app.jar /opt/app/current/$ echo $?1$ grep -q ERROR app.log; echo $?   # 一致なしは 1、ファイルが無いなどのエラーは 2
終了コードを確かめる

grep の 1 や diff の 1 のように、0 以外でも「失敗」ではなく「該当なし」「違いあり」を表すコマンドもあります。数値の意味はコマンドごとに man の EXIT STATUS の節に書かれています。bash では、見つからないコマンドは 127、実行できないファイルは 126 になります。

パイプには注意が必要です。cmd1 | cmd2 の終了コードは、既定では最後のコマンド cmd2 のものです。たとえば mysqldump db | gzip > backup.sql.gz で mysqldump が途中で失敗しても、gzip が成功すれば終了コードは 0 になり、壊れたバックアップが「成功」として残ります。set -o pipefail を設定すると、パイプのどこかで失敗があれば 0 以外を返すようになります。bash では PIPESTATUS 配列で各コマンドの終了コードも確認できます。

set -o pipefailmysqldump app_db | gzip > backup.sql.gzecho "${PIPESTATUS[@]}"   # 例:2 0(mysqldump が失敗、gzip は成功)
パイプの途中の失敗を拾う
2>/dev/null でエラーを消さない

エラーが邪魔だからと 2>/dev/null で標準エラー出力を捨てると、失敗の手がかりが画面から消えます。終了コードは残りますが、見る習慣が無ければ気づけません。調査中や本番作業ではエラー出力を捨てないようにします。

途中まで成功した状態と、リトライの二重実行

エラーで止まったコマンドが、何もしないで止まったとは限りません。複数のファイルをコピーする処理は途中のファイルまでコピー済みかもしれませんし、マイグレーションは一部だけ適用されているかもしれません。MySQL では CREATE や ALTER などの DDL が暗黙にコミットされるため、トランザクションの中で実行しても ROLLBACK で戻せません。エラーのあとは「元のまま」でも「完了」でもない、半端な状態を疑います。

この状態で同じコマンドをもう一度実行すると、成功していた部分が二重に処理されます。INSERT は同じ行が 2 回入り、メールや通知、決済の API は 2 回送られます。タイムアウトのエラーは特に注意が必要で、手元に応答が返らなかっただけで、相手側では処理が完了していることがあります。リトライの前に、今どこまで終わっているかを確かめます。

-- 再実行の前に、すでに入っていないかを見るSELECT id, status, created_at FROM ordersWHERE request_id = 'req-20261017-0042';
再実行の前に今の状態を確かめる

確認の手段は処理によって違います。ファイルなら ls -l で件数・サイズ・更新日時、データベースなら SELECT で件数や状態、サービスなら systemctl status やログで起動の有無を見ます。何を見れば「どこまで終わったか」が分かるかを、作業前に手順書に書いておくと、エラーが出たときに迷いません。

焦って別のターミナルで打たない

エラーが出ると、調べるために新しいターミナルを開いたり、別のサーバーに入って比べたりします。ウィンドウが増えた状態で直すコマンドを打つと、意図と違う環境で実行してしまうことがあります。検証環境で原因を確かめたつもりが本番で打っていた、またはその逆という取り違えです。直すコマンドを打つ前に、hostname やプロンプトの表示で、今いる場所がエラーの出た環境と同じかを確かめます。

エラーが出たら止まり、確かめ、報告する

エラーのあとの行動は、手順として決めておくと迷いません。次の順番は、本番作業でもふだんの開発でも使えます。

  1. 手を止める。次のコマンドをすぐに打たない。
  2. エラー文を最後まで読み、何が・どこで・なぜ失敗したかを書き出す。
  3. echo $? で終了コードを見て、本当に失敗したのか、途中まで成功していないかを考える。
  4. ls、SELECT、systemctl status などで今の状態を確かめる。
  5. 直す方法が手順書に無い、または本番の状態が変わった可能性があるなら、エラー文と今の状態を添えて報告・相談する。

報告は、自分で直せなかったからするものではありません。本番の状態が手順書の想定から外れたことを、周りが知るためのものです。早めに共有すれば、切り戻しの判断や影響の確認を並行して進められます。エラーが出ることは失敗ではなく、エラーのあとに確かめずに進むことが事故につながる、と覚えておいてください。下の問題で、エラーのあとの判断を確かめてみてください。

問題で確かめる

この記事の内容を、制限時間つきの問題で確かめられます。

参考

よくある質問

コマンドが成功したかどうかはどう確かめる?

直後に echo $? を実行して終了コードを見ます。0 なら成功、0 以外なら失敗です。ただし grep の 1(一致なし)のように、コマンドによって意味が違うので man の EXIT STATUS を確認します。

Permission denied が出たら sudo を付ければよい?

まず whoami と ls -l で、どのユーザーがどのファイルに書けないのかを確かめます。sudo で作ったファイルは root の所有になり、別の権限エラーの原因になります。本番では権限の変更を相談してから行います。

set -e を書けば失敗で必ず止まる?

止まらない場面があります。if や while の条件、&& や || でつないだ途中のコマンド、! で反転したコマンドの失敗では終了しません。パイプの途中の失敗は set -o pipefail も必要です。

タイムアウトしたので同じコマンドをもう一度実行してよい?

先に今の状態を確かめます。タイムアウトは応答が返らなかっただけで、相手側では処理が完了していることがあります。INSERT や送信を再実行すると二重になります。

エラーを検索して見つけたコマンドは使ってよい?

各オプションの意味を man や公式ドキュメントで確かめ、何を変更するか説明できてから使います。前提の環境が違うと効かないうえ、chmod -R 777 のような危ない操作も含まれています。