これは、使用するツールによって異なります。いくつかのケースを確認してみましょう:
mv /path/to/source/* /path/to/dest/
の行に沿って何かを実行すると シェルを使用すると、元の 1000 個のファイルが移動され、新しい 300 個のファイルは変更されないことになります。これは、シェルが *
を展開するという事実に由来します。 移動操作を開始する前に、移動が進行中の場合、リストは既に固定されています。
Nautilus (およびその他の GUI フレンド) を使用する場合、最終的には同じ方法になります:選択されたファイルに基づいて移動操作が実行されます。これは、新しいファイルが表示されても変わりません。
glob
のループの行に沿ってシステムコールを使用して独自のプログラムを使用する場合 そして1つだけ mv
glob
まで 空のままにすると、新しいディレクトリに 1300 個のファイルすべてが作成されます。これは、新しい glob
ごとに その間に表示された新しいファイルを取得します。
ディレクトリからすべてのファイルを移動するようにシステムに指示すると、システムはすべてのファイルを一覧表示してから、それらの移動を開始します。新しいファイルがディレクトリに表示された場合、それらは移動するファイルのリストに追加されないため、元の場所に残ります。
もちろん、mv
とは異なるファイルの移動方法をプログラムすることもできます。 これにより、ソース ディレクトリ内の新しいファイルが定期的にチェックされます。
カーネル自体が「1000 ファイルの移動」操作の「途中」になることはできません。提案している操作について、より具体的にする必要があります。
rename(*oldpath, const char *newpath)
を使用すると、1 つのスレッドで一度に 1 つのファイルしか移動できません または renameat
システムコール (同じファイルシステム内のみ)。または Linux renameat2
RENAME_EXCHANGE
のようなフラグがあります 2 つのパス名、または RENAME_NOREPLACE
をアトミックに交換するには しない 宛先が存在する場合は置き換えます。 (例:mv -i
を許可する stat
の競合状態を回避する実装 そして rename
stat
以降に作成されたファイルを上書きします。 .link
+ unlink
link
のため、それも解決できます 新しい名前が存在する場合は失敗します。)
しかし、これらのシステム コールはそれぞれ、システム コールごとに 1 つのディレクトリ エントリの名前のみを変更します . POSIX renameat
の使用 olddirfd
で と newdirfd
(open(O_DIRECTORY)
で開く ) ソースまたは宛先ディレクトリ 自体 であっても、ディレクトリ内のファイルをループし続けることができます 改名されていました。 (相対パスを使用すると、通常の rename()
でも可能になります .)
とにかく、他の回答が言うように、rename システム コールを使用するほとんどのプログラムは、最初の rename
を実行する前にファイル名のリストを把握します。 . (通常は readdir(3)
Linux getdents
などのプラットフォーム固有のシステム コールのラッパーとしての POSIX ライブラリ関数 ).
しかし、あなたが find -exec ... {} \;
について話しているのなら ファイルごとに 1 つのコマンドを実行するか、より効率的な -exec {} +
1 つのコマンド ラインに収まらないほど多くのファイルがある場合は、スキャン中に名前の変更が発生する可能性があります。例
find . -name '*.txt' -exec mv -t ../txtfiles {} \; # Intentionally inefficient
新しい .txt
を作成した場合 可能性 ../txtfiles
でそれらのいくつかを参照してください .しかし、内部的には find(1)
open(O_DIRECTORY)
を使用します と getdents
.
で .
すべてを返すのに 1 つのシステム コールで十分だった場合 .
のディレクトリ エントリ (これは一度に 1 つずつループし、-type
に必要な場合にのみさらにシステム コールを行います。 または再帰するか、または fork+exec に一致する場合)、リストはある時点でのディレクトリエントリのスナップショットです。ディレクトリをさらに変更しても、find
には影響しません これは、ループする内容をリストしたディレクトリのコピーが既にあるためです。 (おそらく内部的に readdir(3)
を使用しています) 、一度に 1 つのエントリを返しますが、glibc 内部では strace find .
を使用することでわかっています getdents64
になること count=32768
のバッファ サイズのシステム コール エントリ)
しかし、ディレクトリが巨大である場合、および/またはカーネルが find
を満たしていない場合 のバッファでは、最初に取得したものをループした後、2 回目の getdents システム コールを行う必要があります。そのため、名前を変更した後に新しいエントリが表示される可能性があります。
ただし、他の回答の下にあるコメントの議論を参照してください。(私が思うに) getdents は同じファイル名を 2 回返すことが許可されていないため、カーネルがスナップショットを作成した可能性があります。ファイルシステムが異なれば、巨大なディレクトリ内のエントリへのアクセスを線形検索よりも効率的にするために、異なるソート/インデックスメカニズムが使用されます。そのため、ディレクトリを追加または削除すると、残りのエントリの順序に他の影響が及ぶ可能性があります。うーん、おそらく、ファイルシステムが安定した順序を維持し、実際のインデックスを更新する可能性が高くなります (EXT4 dir_index
のように) 機能)、したがって、ディレクトリ FD の位置は、再開するディレクトリ エントリにすぎませんか? telldir(3)
ライブラリ インターフェイスは lseek
にマップされます 、またはそれが純粋にユーザー空間によって取得されたバッファーをループするためのユーザー空間のものである場合。しかし複数の getdents
巨大なディレクトリからすべてのエントリを取得するために必要になる場合があるため、シークがサポートされていなくても、カーネルは現在の位置を記録できる必要があります。
脚注 1:
ファイルシステム間で「移動」するには、ユーザー空間をコピーしてリンクを解除する必要があります。 (例:open
および read+write
のいずれか 、 mmap+write
または sendfile(2)
または copy_file_range(2)
、後者の 2 つは、ユーザー空間を介したファイル データのバウンスを完全に回避します。)