1つのコマンドで "rm -rf targetdir && mv sourcedir targetdir"と同じことを実行できますか?

1つのコマンドで "rm -rf targetdir && mv sourcedir targetdir"と同じことを実行できますか?

「あるファイルを別のファイルに置き換える」操作を説明する方法はいくつかありますが、ここで重点的に説明する方法はコマンドを使用して実行できる方法です。

mv /that/there/someotherfile /this/here/somefile

この例では、/that/there/someotherfileおよびは/this/here/somefile現在のファイルシステムに存在する通常のファイルでなければなりません。

すべてが順調に進むと、上記のコマンドを実行した後、「以前に知られている」ファイルは消えますが、/that/there/someotherfileその内容はファイルの新しい内容になります/this/here/somefile。後者の前のコンテンツは「上書き」されます。

ここで、「あるディレクトリを別のディレクトリに置き換える」同様の操作を考えてみましょう。たとえば、/path/to/targetdir一部のディレクトリをディレクトリで上書きします/some/other/path/to/sourcedir。私はこれを行うことができます

rm -rf /path/to/targetdir && mv /some/other/path/to/sourcedir /path/to/targetdir

有効な「やや標準」1コマンドを使用してこれを実行できますか?関連する2つのディレクトリの内容に関係なく

/path/to/targetdirのディレクトリなら、

mv -T /some/other/path/to/sourcedir /path/to/targetdir

...仕事をします。

/path/to/targetdirまた、両方のディレクトリの下に存在しない相対パスを含まず、両方のディレクトリの下に存在するすべての相対パスが同じタイプのファイルシステムエントリを指すことを知っています(/some/other/path/to/sourcedirつまり、すべてディレクトリまたは一般ファイルなど)。は以下を得ることができます閉鎖上記の作業に

rsync -a --remove-source-files /some/other/path/to/sourcedir/ /path/to/targetdir

もちろん、rm -rf上記の+シーケンスをカプセル化するために2つのスクリプトや関数を実装することは難しくありませんが、ある程度mv標準のUnixコマンドですでに使用できるものを実装することを避けたいと思いました。


1私はこの質問に対する答えが許可された一連の命令に対する個人の見解に決定的に依存していることを知っていますが、残念ながらここでは手を振るよりも優れたものを提供することはできません...たとえば、cpと思います。mvそれほど標準的ではありませんが、この場合でもこれらのコマンドが使用するオプションの一部はそうでない可能性があります。「次までPOSIXを強制」)、上記の意味では、単一のコマンドを使用して「あるディレクトリを別のディレクトリに置き換える」という普遍的な方法はありません。その場合は、許可された一連のコマンドを自由に定義して、コマンドを便利で面白いものにします。つまり、許可された一連のコマンドを選択するときは、あなたの好みに従う準備ができています。

2有名な最後の言葉。

答え1

rsync一般的な再起動の可能性を忘れた場合、この非常に危険なコマンドが機能する可能性があります。

rsync -av --remove-source-files --delete-before /path/to/source/ /path/to/target

しかし、いくつかの注意事項があります。

  • ファイルを移動するのではなく、ファイルをコピーして元のファイルを削除します。これは、同じファイルシステムにある大容量ファイルの場合に問題になる可能性があります。
  • ソースディレクトリは削除されません。これが問題の場合は、2つのコマンドに戻って元のrm && mv設定に戻すこともできます。
  • これはPOSIXではありません

全体的に私はこのrm && mvアプローチを好むと思います。このバージョンにはbash(または配列を持つ他のシェル)が必要です。私はそれが使用されたと思い、両方ともmvPOSIXに準拠しています。rmfind

rmmv() {
    local args=("$@") target

    if [ $# -eq 0 ]
    then
        echo "${0##*/}: missing file operand" >&2
        exit 1
    elif [ $# -eq 1 ]
    then
        echo "${0##*/}: missing file operand after '${args[0]}'" >&2
        exit 1
    fi

    target="${args[@]: -1}"
    unset "args[${#args[@]}-1]"

    if [ -d "$target" ]
    then
        # Directory target; remove its contents
        ( cd -P -- "$target" && find . -depth -path './*' -exec rm -rf {} + )

    elif [ "${#args[@]}" -gt 1 ]
    then
        # Multiple sources but not a directory
        echo "${0##*/}: target '$target' is not a directory" >&2
        exit 2
    fi

    # Do it
    mv -- "${args[@]}" "$target"
}

答え2

TL、DR:シンボリックリンクを使用してください。

POSIX ディレクトリ

空でないディレクトリの自動置換は、POSIXシステムコールだけでは不可能です。これrenameシステムコールは、宛先が空のディレクトリのみを許可します。これlinkシステムコールはディレクトリを対象とすることが禁止されています。

/some/other/path/to/sourcedirあるいは、いくつかの/path/to/targetdir非原子的方法がある。この回答では、ターゲットディレクトリがすでに存在していると仮定します。そうでない場合は、この場合を別々に処理する必要があります。これは、同時状況では少し難しい場合があります。また、ソースとターゲットが同じファイルシステムにあると仮定します。

  • 既存のターゲットを邪魔にならない場所に移動し、新しいバージョンを所定の位置に移動します。これにより、ターゲットが存在しない小さな時間枠が残ります。 2つの交換プロセスが同時に動作している場合、この方法は安定して動作しません。

    mv -f /path/to/targetdir /path/to/targetdir.old
    mv /some/other/path/to/sourcedir /path/to/targetdir
    rm -rf /path/to/targetdir.old
    
  • 既存のターゲットを削除し、新しいバージョンを所定の位置に移動します。これにより、ターゲットが部分的にのみ満たされている間、潜在的に大きな時間ウィンドウが残り、ターゲットがない間はより小さな時間ウィンドウが残ります。 2つの交換プロセスが同時に動作している場合、この方法は安定して動作しません。

    rm -rf /path/to/targetdir
    mv /some/other/path/to/sourcedir /path/to/targetdir
    
  • 既存のターゲットを消去し、新しいバージョンを所定の位置に移動します。これにより、ターゲットが部分的にのみ満たされている間に潜在的に長い時間が残ります。ターゲットウィンドウは保持され、アトミックに埋められます。 2つの交換プロセスが同時に動作している場合、この方法は安定して動作しません。

    (cd /path/to/targetdir && rm -rf ..?* .[!.]* *)
    mv /some/other/path/to/sourcedir /path/to/tmp/targetdir
    mv /path/to/tmp/targetdir /path/to/
    

    新しいバージョンがターゲットと同じ基本名を持つ中間ステップを参照してください。これは必須ですmv。渡された宛先がmv既存のディレクトリの場合、mvソースはそのディレクトリに移動されます。したがって、mv上書きする既存のディレクトリの親ディレクトリに渡される宛先を設定する必要があります。

カーネル固有のシステムコールを使用したい場合は、アトミック交換を実行する方法があります。

Linuxディレクトリ

システムコールrenameat2Linux ≥3.15では、任意の種類の2つのディレクトリエントリをアトミックに交換できます。インターフェイスを提供するユーティリティはありません。

perl -we '
    require "syscall.ph";
    sub AT_FDCWD() {return -100}
    sub RENAME_EXCHANGE () {return 2}
    my ($source, $target) = @ARGV;
    $! = 0;
    syscall(SYS_renameat2(), AT_FDCWD, $source, AT_FDCWD, $target, 2) != -1 or die $!}
' /some/other/path/to/sourcedir /path/to/targetdir &&
rm -rf /some/other/path/to/sourcedir # This now contains the former target

これは完全に原子的な代替です。複数の交換プロセスが同時に実行される場合、最終結果は新しいバージョンの1つが配置され、他のバージョンは削除されます。

ディレクトリを原子的に効率的に置き換える別の方法は次のとおりです。バインドマウント。この機能は多くのUnixバリアントに存在しますが、使用される方法は異なる場合があります。ここではmount --bindroot権限を必要とするLinuxを使用します。もう一つの注目すべき方法は、bindfs異常な権限を必要としないFUSEファイルシステムです。

mkdir /path/to/targetdir.old
mount --bind /path/to/targetdir /path/to/targetdir.old
mount --bind/some/other/path/to/sourcedir /path/to/targetdir
rm -rf /path/to/targetdir.old

これにより、ファイルは移動されず、ターゲットパスの下に表示されます。このアプローチを使用すると、ソースはそのまま残ります。

POSIXシンボリックリンク

原子置換を行う方法は、シンボリックリンクを使用することです。シンボリックリンクできる原子に置き換えられました。これは直観にずれているので少し面倒です。symlinkしません。使用する必要がありますrename。さらに、もしmvディレクトリへのシンボリックリンクであるターゲットが与えられたら、ソースを移動します。下にシンボリックリンクを上書きする代わりにディレクトリ。mvシンボリックリンクを含むディレクトリをターゲットに渡すことで、ディレクトリシンボリックリンクであるターゲットを上書きできます。これを行うには、ソースのデフォルト名が同じでなければなりません。

# Precondition: /path/to/targetdir doesn't exist or is a symbolic link.
mkdir -p staging
ln -s /some/other/path/to/sourcedir staging/targetdir
old_target=$(cd /path/to/targetdir && pwd -P)
mv staging/targetdir /path/to/targetdir
# Postcondition: /path/to/targetdir is a symbolic link to the new version.
rm -rf "$old_target"

名前の変更はアトミックなので、複数の同時インスタンスがある場合は確実に機能しませんが、削除する項目を特定するわけではありません。

答え3

この場合、コマンドを削除する代わりにコマンドを追加することをお勧めします。

mv target somewhere_else
mv source target

ソース、ターゲット、およびその他の場所が同じファイルシステムにある場合は、両方のコマンドをすばやく返す必要があります。もしそうなら、必ずしもすぐではないかもしれません。

rm -rf somewhere_else

ディスク容量が問題の場合は、上記のタスクをサブツリーに分割できます。

source一般的に、次のように、さまざまなファイル/フォルダの種類と数に応じて、より良いソリューションを見つけることができますtargetrsync、再帰diff/patch、バージョン管理など

関連情報