サーバーのコマンド実行をお願いするときに気をつけること5選

f:id:rasukarusan:20190223163320p:plain 本番サーバーでコマンド実行する際、権限を持っている人にコマンド実行を依頼することってあるじゃん。全台サーバーに実行みたいな。
そんな時に「このコマンドエラー出るよ」と言われないために気をつけるべきことを書いていくぞ。

1. rm, cpには\をつけてaliasを無効化しろ

サーバーによってはrmやcpに-iをつけたものをaliasとして登録していることがある。

alias rm='rm -i'  
alias cp='cp -i'  

-iをつけていると以下のように削除する際、毎回確認を求めてくるようになる。

$ rm -i hoge.txt  
remove hoge.txt?  

例えば「.txtを全て削除したい」といった際、実行者はいちいちyを入力しないと削除できない。

$ rm *.txt  
remove foo.txt? y  
remove fuga.txt? y  
remove hoge.txt?  
...  

なので\をつけてaliasを無効化しよう。

$ \rm *.txt  

rmやcpに限った話ではないのでとりあえず付けておくことが望ましい。

2. sudoが必要なコマンドはsudoをつけろ

「rm -rf」って書いてあるんだから実行者がつけてくれるだろうと思ってはいけない。
もしくは「実行前にrootユーザーになってくれるっしょ」など他人任せにしてはいけない。
実行者としてはコピってそのまま動くのが望ましい。要するに優しさを見せましょうってことや。

# 優しさが足りないコマンド  
$ \rm -rf target_dir  
  
# 優しさにあふれるコマンド  
$ sudo \rm -rf target_dir  

3. cpをする時は権限に気をつけろ

画像の保存先を変更するに、既存のディレクトリを新しい保存先にコピーすることがあるだろう。その際普通にcpでやるなら注意が必要だ。

# public/imagesからupload/imagesにコピー
$ sudo \cp -r /home/www/public/images /home/www/public/upload/images 

この場合upload/imagesの所有者がrootになる

# コピーする前は所有者がapache
$ ls -l /home/www/public/ 
total 4.0K
drwxr-xr-x 2 apache apache 4.0K  223 03:50 images

# コピーした後は所有者がrootに変わる
$ ls -l /home/www/public/upload
total 4.0K
drwxr-xr-x 2 root root 4.0K  223 03:54 images

そうすると画面(ブラウザ)から画像をアップロードできないなど問題が発生する。 これを防ぐためにcpのオプションである-pを付けよう。

# public/imagesからupload/imagesに所有者・権限もそのままでコピー
$ sudo \cp -pr /home/www/public/images /home/www/public/upload/images 

# コピーした後も以前と属性が変わらない
$ ls -l /home/www/public/upload
total 4.0K
drwxr-xr-x 2 apache apache 4.0K  223 03:54 images

ちなみにmvなら所有者も権限も変更されない。

4. 実行場所に依存しないようにしろ

例えば以下のコマンド。 app/shell内のシェルスクリプトに実行権限を与える。

# app/shell配下にはshellスクリプトが置いてあるとする  
$ ls app/shell  
hoge.sh  
  
# 実行権限を付与  
$ ls app/shell/ | xargs chmod +x  

一見成功しそうではあるがこれは失敗する。
例えばこのコマンドを/etc配下で実行した場合、app/shellなんてディレクトリないよと怒られる。コマンドの実行者に「〇〇ディレクトリ配下で実行してください」などとお願いするのはナンセンスだろう。

[root@linux: /etc]$ pwd  
/etc  
# 実行場所によってエラーが出る  
[root@linux: /etc]$ ls app/shell   
ls: app/shell: No such file or directory  

なのでlsで展開させるディレクトリの場所を絶対パスで書くべきである。以下のような形。

$ ls /home/www/app/shell/ | xargs chmod +x  

しかしこれでも不完全だ
なぜならlsで出力されるのはファイル名のみで絶対パス表記じゃないからである。
このコマンドを/etcで実行するとパイプ先のxargs chmodhoge.sh no such fileと怒られてしまう。/etc配下にhoge.shはないからね。

# lsで出力されるのはファイル名のみ  
[root@linux: /etc]$ ls /home/app/shell   
hoge.sh  
[root@linux: /etc]$ ls /home/app/shell | xargs chmod +x  
chmod: hoge.sh: No such file or directory  

解決法としては事前にコマンドでディレクトリを移動してから実行する。

cd /home/www/; ls app/shell/ | xargs chmod +x  

しかし一々移動するのもアレなのでfindで指定してしまおう。

# findの場合、絶対パスで出力してくれる  
$ find /home/www/app/shell -type f -name "*.sh"  
/home/www/app/shell/hoge.sh  
  
# コマンド実行時にどこのディレクトリにいても実行可能  
$ find /home/www/app/shell -name "*.sh" | xargs chmod +x  

5. できるだけ細かくファイルは指定しろ

複数のファイルに変更を加える場合、できるだけ細かくファイルの種類を特定するべきである。
誤って他のファイルを消さないか?など、コマンドの実行者に余計な考慮をさせるわけにはいかない。

例えば以下のようなディレクトリ構造があるとする。

$ tree /home/www/public/images  
.  
|-- user1  
|   `-- hoge.png  
`-- user2  
    |-- foo.png  
    |-- fuga.jpeg  
    `-- log  

「userX内の画像ファイルを消したい」コマンドを実行するとしよう。
この時images/userXには画像しかないと思い込んでいると以下のようなコマンドを実行してしまう。

$ sudo \rm /home/www/public/images/user*/*  

そうするとrm: user2/log: is a directoryとエラーが出てしまう。
コマンドを実行する前にディレクトリ内は確認するとは思うが、複数サーバーが存在する場合見逃してしまう可能性もある
このようなミスをなくすためできるだけコマンドの方で対象のファイルのみを指定するのがいいだろう。

# userX内のpngファイルのみ削除  
$ sudo \rm $(find /home/www/app/images/user*/ -name "*.png")  

また、以下のようにxargsで削除してもよいが、

$ find /home/www/app/images/user*/ -name "*.png" | xargs sudo \rm  

もしファイル名に空白を含むものがあった場合xargs -i sudo \rm {}などとオプションを指定する必要があるので、rm $(find ~~~)のように$()``で「コマンドの実行結果に対してコマンドを実行する」形式のほうがオプションの付け忘れ等気にしなくてすむ。
バッククォートだと実行者がシングルクォートと見間違えたり見づらかったりするので$()をおすすめする

まとめ

普段自分一人で開発している時は気にする必要はないが、コマンドの実行を依頼したりWikiに載せるときは色々気をつけたほうがいいよねって話。 要は優しさですよね。