grepを忘れただけなのに

去年も『本番環境でやらかしちゃった人のアドベントカレンダー』は盛り上がりましたね。
知見が多く、関心しながら拝見しています。

https://qiita.com/advent-calendar/2020/yarakashi-production

人は必ず何かしらミスを起こすもの。
明日は我が身と思いながら、業務をこなす日々です。

そんな私も業界に入って1年目(前々職)に、本番環境の洗礼にあったことがございます。
当時は苦々しい思いをしましたが、その経験を供養するためにもここに残そうと思います。

# 発生当時の状況

事件当時、私はサーバのリプレイス案件にアサインしていました。
その業務の中で上司に日常的に運用されているスクリプトの調査を依頼されました。
私はまだ経験が浅かったため理解が合っているかは怪しいですが、関わっていたシステムは設計の段階で大分やっつけだったらしく、
格納場所が間違っているスクリプトやログが散見されました。
リプレイスを切っ掛けに整理をする予定だったと記憶しています。

入ったばかりのペーペーの私が1人で調査できる訳がないので、ベテランの方(以後Sさんと呼称)と一緒に調査することになりました。
ベテランの方はいわゆる**職人**という感じで、積極的にコミュケーションをとる方ではなく、私はその雰囲気もあって特に会話もせず、基本Sさんの言うことに疑問を持たず従うという動きをしていました。
それが事件の引き金となることも知らず・・・。

# 事件経緯

調査のために本番環境にログインし、`ls`コマンド等で情報を取得しようとしました。
新人である私がおぼついた操作をしようものなら、**職人**をイライラさせてしまうため、作業確認者(ほぼ置物)となって作業にあたりました。
対象ディレクトは複数ありましたが、その内の一つに下記のようなスクリプト以外のものが紛れ込んでいるディレクトリがありました。

```
-- script
    |-- 190106.log
    ~~~~~~~~  略 ~~~~~~~~~~
    |-- 201206.log
    |-- app01.sh
    |-- app02.sh
    |-- app03.sh
    |-- app04.sh
    |-- app05.sh
    |-- app06.sh
    |-- app07.sh
    |-- app08.sh
    ~~~~~~~~  略 ~~~~~~~~~~
    |-- app40.sh
    |-- common01.sh
    |-- common02.sh
    |-- common03.sh
    |-- common04.sh
    |-- common05.sh
    ~~~~~~~~  略 ~~~~~~~~~~
    |-- common30.sh
    |-- day
    `-- month
```

皆さんはこの中から、スクリプトだけ表示させるならどのようなコマンドを使用するでしょうか？
`find`でしょうか？`grep`でしょうか？
はたまた`ls`で全部取得して、コンソールのログから抽出しますか？

どちらにしろ、やり方はSさん次第です。
私に意見はございません。ベテランに任せれば全て上手くいくのですから。

Sさんは手癖のようにコマンドを入力します。(実際に手癖なのかもしれません)
コンソール画面に高速にコマンドが表示されます。

```
[root@foo script]# ls -l |
```

私が「あ、grepで表示するのか」と思う間もなく、コマンドは放たれました。
そのコマンドとは下記の形でした。

```
[root@foo script]# ls -l | sh
```

grepを打ち忘れてますね。
猿も木から落ちる。弘法も筆の誤り。
結果、画面に表示されたのは

```
sh: line 1: total: command not found
sh: line 2: -rw-rw-r--.: command not found
sh: line 3: -rw-rw-r--.: command not found
sh: line 4: -rwxr-xr-x.: command not found
sh: line 5: -rwxr-xr-x.: command not found
sh: line 6: -rwxr-xr-x.: command not found
~~~~~~~~~~~ 略  ~~~~~~~~~
```

ずらっと`command not found`が続いていました。
これは・・・どうなったんだ？もしかしてやってしまったのか？本番環境で？どうする？どうする？
危うくテンパる間際の私に、Sさんはこう言いました。

「打ち間違えた。**lsの標準出力をshコマンドに繋げてしまった**が、パーミッションの表示に該当するコマンドは存在しないので問題はない。」

へ〜そうなんだ〜。さすがSさん。冷静だぜ！
一瞬ヒヤってしたしたが、ベテランが大丈夫って言ってるのでヨシ！

その後も調査を続け、上記の打ち間違い以外は特に何事もなく終えました。

# 事件発生

**本番環境のバッチが動かない**

そんな連絡を受けたのは、作業した次の日でした。
のんきな顔して出社してきた私に対し、複数の人が詰め寄ってきます。
本番運用を任されている人達です。

「バッチで使用している**スクリプトの中身が消えている**。昨日、本番作業をした時にスクリプトを編集しなかったか？」

え？スクリプトの編集？
そんな覚えはない。

いや・・・。もしかしてあの**コマンドの打ち間違い**か？

ともかく私では、どのコマンドがスクリプトに影響を及ぼしたか判断がつかなかったため、作業当時のログを提出し、
打ち間違いをした箇所を報告しました。

結果、**私たちの作業でスクリプトの中身を消している**ことが判明しました。

# 原因

やはり、下記のコマンドが原因でした。

```
[root@foo script]# ls -l | sh
```

『いやいや。そもそも`command not found`が出てたし、何も起きるわけないでしょ。』
そんなふうに考えていた時期が私にもありました。

なぜ上記のコマンドでスクリプトのクリア処理が起こったのか？
それは対象ディレクトリにある**ファイル**に鍵がありました。
では当時を再現した状態を見てみましょう。

```
[root@foo script]# ls -l
total [n]
-rw-rw-r--. 1 root    other    0 Dec  6 07:48 190106.log
~~~~~~~~~~~~~ 略 ~~~~~~~~~~~~~~
-rw-rw-r--. 1 root    other    0 Dec  6 07:13 201206.log
-rwxr-xr-x. 1 root    other   39 Dec  6 07:10 app01.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:46 app02.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:46 app03.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:46 app04.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:46 app05.sh
~~~~~~~~~~~~~ 略 ~~~~~~~~~~~~~~
-rwxr-xr-x. 1 root    other    0 Dec  6 07:46 app40.sh
lrwxrwxrwx. 1 root    other   32 Dec  5 14:00 common01.sh -> /db/script/common/common01.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:47 common02.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:47 common03.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:47 common04.sh
-rwxr-xr-x. 1 root    other    0 Dec  6 07:47 common05.sh
~~~~~~~~~~~~~ 略 ~~~~~~~~~~~~~~
-rwxr-xr-x. 1 root    other    0 Dec  6 07:47 common30.sh
drwxrwxr-x. 2 root    other    6 Dec  6 07:12 day
drwxrwxr-x. 2 root    other    6 Dec  6 07:11 month
```

そう。**シンボリックリンク**があったのです。

つまり下記のようなコマンドが走っていたことになります。

```
sh lrwxrwxrwx. 1 root    other   32 Dec  5 14:00 common01.sh -> /db/script/common/common01.sh
```
もっと分かりやすくすると・・・

```
sh -c "lrwxrwxrwx. 1 root other 32 Dec 5 14:00 common01.sh -> /db/script/common/common01.sh"
```
ここで問題なのはシンボリックリンクの`> /db/script/common/common01.sh`の部分。
この**リダイレクトだけが発動して**、シンボリックリンク先のファイルをクリアしてしまっていたのです。

`>`は「コマンドが実行される前に、fileをtruncationモードで開いて全ての中身を消去」するので`command not found`だろうがお構いなしに消してたのでした・・・。


# どうすればよかったのか？

- 本番作業をコマンド手打ちとかありえない

- findコマンドでやれよ

- そもそもrootでやること？

   

とか色々あるとは思いますが、私が出来た範囲で防げたことがあります。

**私が作業確認者として、しっかりコマンド実行前に確認すること。**

これだけでコマンドの打ち間違いに気づけました。
(私が気づかなくても実行前に一拍おくだけで、Sさんが気づいた可能性があります)

本番作業を甘く見て、ベテラン作業者1人だけに任せてしまいました。

以後反省し、どんなに話辛い人とペアになっても作業前は必ず認識を合わせるようにしてます。
本番環境の前では人は皆平等になるのだから・・・。