11月5日、Igor Giampaolo が「This Overlooked Linux mv Option Replaces Three Commands With One」と題した記事を公開した。この記事では、mv --exchange オプションを使って2つのファイルやディレクトリをアトミックに入れ替える方法について詳しく解説されている。
3コマンドの「踊り」を1コマンドに
Linuxユーザーなら一度はやったことがあるはずだ。2つのファイルを入れ替えたいとき、一方を一時ファイルに逃がし、もう一方を移動し、最後に一時ファイルを戻す——あの3ステップの儀式だ。
mv nginx.conf nginx.conf.tmp
mv nginx.conf.new nginx.conf
mv nginx.conf.tmp nginx.conf.new
この方法には2つの問題がある。
- 一瞬ファイルが消える。 1コマンド目と2コマンド目の間、
nginx.confが存在しない瞬間がある。サービスがその瞬間にファイルを読もうとすると、何も見つからない。 - 途中で失敗すると厄介。 2コマンド目が失敗すると、一時ファイルが残ったまま中途半端な状態になる。
2024年4月にリリースされた GNU coreutils 9.5 から、これを1コマンドで解決するオプションが追加された。
mv --exchange nginx.conf nginx.conf.new
なぜ今まで使えなかったのか
--exchange オプションは、Linuxカーネルの renameat2() システムコールに RENAME_EXCHANGE フラグを渡して実行される。これによりスワップがアトミック(不可分)に行われる。どの瞬間を切り取っても両方のファイル名が存在し続け、一時ファイルも不要だ。
renameat2() 自体はカーネル 3.15(2014年)から存在する。にもかかわらず、mv コマンドから使えるようになったのは実に10年後の coreutils 9.5 だ。その間、同等の機能は C のコードや rename(2) を直接呼ぶスクリプトでしか利用できず、シェルスクリプターには縁遠い機能だった。今回の統合で、カーネルレベルの仕組みを意識せずとも、使い慣れた mv の延長として恩恵を受けられるようになった。
まず自分の環境を確認する
mv --version
バージョン9.5以上であれば利用できる。なお、バージョン番号はセグメントごとの比較なので 9.11 は 9.5 より新しい。オプションの存在は以下でも確認できる。
mv --help | grep exchange
何も出力されなければ、そのシステムの mv はまだ対応していない。NixOS、Fedora、Debian 13 などの比較的新しいディストリビューションなら標準で使える。macOSのBSD系 mv は非対応なので注意が必要だ。
ファイルのスワップ
echo "config A" > a.conf
echo "config B" > b.conf
mv --exchange a.conf b.conf
cat a.conf b.conf
config B
config A
中身が入れ替わった。一時ファイルなし、コマンド1発だ。

ディレクトリのスワップ——最初にハマる落とし穴
ファイルだけでなくディレクトリごと丸ごと入れ替えられるのも、このオプションの強みだ。ただし初見で必ずハマるポイントがある。
mkdir release-old release-new
touch release-old/v1 release-new/v2
mv --exchange release-old release-new
mv: cannot exchange 'release-old' and 'release-new/release-old': Unknown error -1
エラーメッセージの第2引数が release-new/release-old になっているのに注目。通常の mv と同じく、最後の引数がディレクトリの場合は「その中に移動する」と解釈されてしまう。
解決策は -T(--no-target-directory)オプションを併用することだ。
mv -T --exchange release-old release-new
ls release-old release-new
release-new:
v1
release-old:
v2
ディレクトリツリーごと丸ごと入れ替わった。内部にファイルが何個あっても、変わるのはディレクトリ名だけなので一瞬で完了する。デプロイ時に新バージョンのディレクトリをライブディレクトリと入れ替える用途に特に有効だ。
知っておくべき制限
同一ファイルシステム上である必要がある
異なるファイルシステム間(例: 通常のディスクと /dev/shm のような tmpfs マウント)をまたぐスワップはカーネルが拒否する。
mv --exchange b.conf /dev/shm/x.conf
mv: cannot exchange 'b.conf' and '/dev/shm/x.conf': Unknown error -1
エラーメッセージはディレクトリ問題のときと同じ「Unknown error -1」なので原因がわかりにくい。これはカーネルが返すエラーコードを glibc が適切にマッピングできていないためで、実際にはファイルシステムをまたいでいることが原因だ。通常の mv はコピー+削除で異なるファイルシステム間の移動を実現しているが、それはアトミックにできないため --exchange では対応しない。

ファイルシステム側のサポートが必要
renameat2() にはファイルシステム側のサポートが必要だ。ext4、Btrfs、XFS、tmpfs といった一般的なLinuxファイルシステムは対応している。
長年「当たり前」として受け入れていた3ステップの儀式が、たった1オプションで不要になる。ディストリビューションのアップデートと合わせて習得しておく価値がある機能だ。なお、同じコマンドをもう一度実行すればスワップを元に戻せる点も覚えておきたい。
詳細はThis Overlooked Linux mv Option Replaces Three Commands With Oneを参照していただきたい。