ファイル入れ替えにおいてmvは使用してはいけない

サーバにローカル端末で更新した設定ファイル等をアップしてからmvで所定のファイルに移動させると、権限や所有者が変わってしまうことがある。
cpコマンドを--preserveオプションや--no-preserveオプションをつけずに実行し、上書きすると、timestampは更新されるものの、権限や所有者は元のファイルのまま保たれる。

つまり、ファイル入れ替えにおいてmvは使用してはいけない。

サンプルを用いてmv, cpをファイルの入れ替えでどのように使うべきか、また、なぜ権限等が変わってしまうのかを記載する。

mvではなくcpを使う

/usr/local/bin/test.shを~/test.shと入れ替える、という例を題にmvではなくcpを使う方法を書く。

ORIGINAL=/usr/local/bin/test.sh
UPDATED=~/test.sh
BACKUP=/tmp/test.sh

NGパターン

cp -p $ORIGINAL $BACKUP
mv -f $UPDATED $ORIGINAL

OKパターン

cp -p $ORIGINAL $BACKUP
\cp -f $UPDATED $ORIGINAL
# \cpでcpのalias(cp -i)が使われないようにし、-fオプションが有効になるようにしている。

特別に何かほかの情報を更新したい、あるいは更新したくないという場合は、--preserveオプション、--no-preserveオプションで対応すればいい。

--preserveオプションで指定できる項目

項目 --preserve=に指定するATTRIBUTE
最終更新日時と最新アクセス日時 timestamps
所有者と所有グループ ownership
アクセス権(パーミッション) mode
SELinuxの「コンテキスト」と呼ばれる情報 context
ディレクトリ内に存在するハードリンク links
ファイルシステムの拡張属性 xattr

参考:cpコマンドでファイルやディレクトリをコピーした際に保持される情報/属性について

それでもmvを使いたければ

cpではなくmvを使う場合、無理やり権限等を正そうとすると、chmodとchownの--referenceオプションを使って権限やオーナー・グループを元のファイルからコピーする必要がある。

# backup
cp -p $ORIGINAL $BACKUP

# 権限をコピー
chmod --reference=$ORIGINAL $UPDATED

# オーナー・グループをコピー
chown --reference=$ORIGINAL $UPDATED

# 入れ替え
mv -f $UPDATED $ORIGINAL

mvで権限等が変わる理由

そもそもmvで権限等が変わる理由は、変更元と変更先のファイルパスが同一ファイルシステム上にある場合という条件付きではあるが、mvコマンドがrename()というファイル名を変更するシステムコールを利用しているからだ。
manによると、renameは、変更後のファイルnewpathがすでに存在するときには、ファイルoldpathがファイルnewpathをatomicに置き換える。
置き換えなので権限等もoldpathのもの(今回の例で言うと$UPDATED)がそのまま持ち越される。

cpで権限等が変わらない理由

cpコマンドはopen()というファイルを開くシステムコールを利用している。
コピー先のファイルが既に存在しているなら、 open(path, O_WRONLY | O_TRUNC) でファイルを開く。既存のファイルを書き込みモード開いて、長さ 0 に切り詰め(O_TRUNC)、コピー元のファイルの内容を書いていくので、権限等が変わる要素がない。
ただし、cp -fとして-fオプションをつけて実行したとき、既存のファイルの書き込みモードでのオープンに失敗すると、cpは既存のファイルの削除 (もしくはアンリンク) を試み、削除に成功した場合は新規ファイルへのコピーとなる。サーバメンテナンスでファイルの入れ替え時に既存ファイルが開けない状況は(サービス等を一旦停止しているため)無いと思うが、cpの仕様として把握しておいた方がいいだろう。