ハードリンクがあるcpの動作に驚きました。

ハードリンクがあるcpの動作に驚きました。

私はハードリンクの概念について非常によく知っており、cp基本ツールのマニュアルページだけでなく、最近のPOSIX仕様まで何度も読みました。しかし、私は次のような行動を見つけ、まだ驚きました。

$ echo john > john
$ cp -l john paul
$ echo george > george

この時点では、同じinode(および内容)を持ち、john2つの点で異なります。今、私たちは次のことを行います。paulgeorge

$ cp george paul

この時点で私はinode番号は異なりますが、内容は同じであるとgeorge予想しましたpaul。これらの期待は満たされた。返品paulこれで、別のinode番号があり、johnまだjohnコンテンツがあると予想されますjohn。私はこれが私を驚かせた。ファイルをターゲットパスにコピーすると、共有paulinodepaulの他のすべてのターゲットパスに同じファイル(同じinode)がインストールされることがわかりました。私はこれがcp新しいファイルを作成し、古いファイルが以前に占有されていた場所に移動すると思いましたpaul。代わりに、既存のファイルを開いて切り取り、paul既存のgeorgeファイルに内容を書き込むようです。したがって、同じinodeを持つ「他の」ファイルは、「それらの」内容を同時に更新します。

まあ、これはシステムの振る舞いであり、これが起こることを知っているので、問題を解決する方法を見つけたり、適切な場合にそれを利用することができます。混乱しているのは、この動作が文書化されている場所をどこで見ることができるかということです。文書化されていないと驚きますどこかに私が見た文書には。しかし、明らかに私はそれを見逃しており、今はこの動作を議論するソースを見つけることができません。

答え1

cpこのマニュアルには、ターゲットファイルがすでに存在する場合はそのファイルを上書きすることが記載されています。そうですね。 「オーバーライド」が何を意味するのかを指定しませんが、「交換」ではなく「上書き」とはっきりしています。賢明に言えば、「再定義」がcpまさにその仕事をし、希望する動作を「代替」と呼ぶのが適切だと言えます。

さらに、cp既存のオブジェクトファイルを「置き換える」場合、これは「上書き」よりも深刻、驚くべき、または間違っていると見なすことができます。たとえば、

  • 以前のファイルを最初に削除してから新しいファイルを作成すると、ファイルが存在しない期間があることはcp驚くべきことです。
  • 一時ファイルが最初に作成されてから所定の場所に移動された場合は、奇妙cpな名前の一時ファイルが時々見つかるため、これを記録する必要がありますが、そうではありません。
  • 権限の問題により、古いファイルと同じディレクトリに新しいファイルを作成できない場合は、cp残念です(特に古いファイルを削除した場合)。
  • ファイルが実行中のユーザーに属せず、実行中のユーザーにも属していない場合、cp新しいcpファイルrootの所有者と権限を新しいファイルの所有者と権限と一致させることはできません。
  • ファイルにcp未知の特殊属性がある場合、これらの属性はコピーから失われます。最近では、 の実装がcp拡張属性などの項目を安定的に理解できるはずですが、必ずしもそうではありません。 MacOSリソースフォークやリモートファイルシステムなどの他のものも基本的にあります。

cp要約すると、これで実際に何をしているのかがわかりました。もう一度これには驚かないでしょう!正直言って、何年も前に同じことが私に起こったかもしれないと思います。

答え2

POSIX 2013標準が見えます。観察される動作を指定します。。それは言う:

  1. もしソースファイル一般的なファイル形式の場合は、次の手順を実行する必要があります。

    ㅏ。 ...もし宛先ファイル存在する場合は、次の措置を講じる必要があります。

    私。この-iオプションが適用される場合、cpユーティリティは標準エラーのプロンプトを作成し、標準入力から1行を読み取る必要があります。答えが否定的な場合、cpそれ以上の措置は取られません。 ソースファイル残りのファイルを処理し続けます。

    2. ファイル記述子宛先ファイルopen()POSIX.1-2008のシステムインタフェースボリュームで定義されている機能と同等の操作を実行して取得する必要があります。宛先ファイルパス引数として、ビット単位でOR含めO_WRONLYO_TRUNC超過分議論。

    3.ファイル記述子を取得しようとすると失敗し、このオプションが適用される場合は、POSIX.1-2008システムインターフェースボリュームで定義されている-f機能と同じ操作を実行してファイルを削除しようとする必要があります。cpunlink()宛先ファイルパスパラメータとして。この試みが成功したら、cpステップ3bに進む必要があります。

    ...

    dの内容。ソースファイルファイル記述子を作成する必要があります。書き込みエラーのため、cp診断メッセージは標準エラーに書き込まれ、ステップ3eに進みます。

    e.ファイル記述子を閉じる必要があります。

答え3

まず、なぜこのようなことをするのか?その理由の1つは歴史的です。それが何が起こったかです。Unixの最初のバージョンでは

ファイルはペアで提供されます。最初は読み取り用に開き、2番目はモード17を生成します。次に、最初の項目を2番目の項目にコピーします。

「作る」という意味はcreatシステムコール(例:eが欠けていることはよく知られています。)がある場合は、その名前で既存のファイルを切り捨てます。

そしてここcpUnix 2番目のバージョンのソースコードです(最初のバージョンのソースコードは見つかりません)。openソースファイルと2番目のファイルへの呼び出しが表示されcreat、最初のバージョンよりも改善されたもので、2番目のファイルが既存のディレクトリである場合、そのディレクトリにcpファイルが作成されます。

しかし、その時、なぜこんなことをしたのかと尋ねることもできます。 「そもそもUnixがなぜこのようなことをしたのか」への答えはほとんどいつも簡単です。cp読み取り用のソースを開くとターゲットを作成する - ファイルを生成するシステムコールは、書き込み用にファイルを開いて既存のファイルを上書きします。存在または存在しません。

ここで文書化された場所について説明します。FreeBSD マニュアルページ

既存のターゲットファイルごとに権限が許可されている場合は、その内容を上書きします。 -pオプションを指定しないと、モード、ユーザーID、グループIDは変更されません。

このようなフレーズがあります。少なくとも1990年にさかのぼる(当時、BSDは4.3BSDでした。)インターネットにも同様の言葉があります。ソラリス10:

target_fileが存在する場合、cpはその内容を上書きしますが、関連するスキーマ(および該当する場合はACL)、所有者、およびグループは変更されません。

あなたの場合でもHP-UX 10手動:

new_fileが別のリンクを持つ既存のファイルへのリンクである場合、既存のファイルを上書きしてすべてのリンクが保持されます。

POSIXはこれを標準言語で表現します。から引用シングルUNIX v2:

dest_file が存在する場合、次のステップが実行されます。 (…)dest_fileのファイル記述子はoflagパラメータで取得されます。

私が引用したマニュアルページと仕様には、そのオプションが渡され、ターゲットファイルを-f開いたり生成したりしようとすると失敗した場合(通常はファイルに書き込む権限がないため)、ターゲットを削除しようとしてcpファイルを再生成してみてください。 。これにより、シーンのハードリンクが失われます。

文書のバグを報告することもできます。GNU coreutils マニュアル、この動作を文書化しないからです。--preserve=links(あなたのシナリオではpaulリンクが削除され、新しいファイルが作成されます)の説明でさえ、そうでなければ何が起こるのか明示的に明らかにしません--preserve=links-f(「このオプションがないと、--forceを使用するとコピーは失敗します...」)

答え4

「ファイルをターゲットパスにコピーすると、そのinodeを共有する他のすべてのターゲットパスpaul にも同じファイル(同じinode)がコピーされますpaul。」と言えば申し訳ありません。ハードリンクが良いです。私がマッカートニー卿に謝罪を与えた場合、ポールには謝罪を与え、ジョンレノンの作曲パートナーには謝罪を与えた。しかし、私はまだリンゴの3つを配っていません。名前/役職/説明が複数の個人に謝罪したものを与えました。

同様にgeorgeにコピーするpaul返品john代わりに、georgeそのディレクトリエントリがそのinodeを指すファイルにデータをコピーしますpaul

ステップバイステップ:  するとき

echo john > john

john新しいファイルを作成しました(指定されたファイルがディレクトリにまだ存在しないと仮定)。あるいは、より厳密に言えば、その名前を持つディレクトリエントリがディレクトリにすでに存在しないと仮定しますjohn(厳密には、ディレクトリにファイルがなく、inodeを指すディレクトリエントリのみがあるため)。終わった後

cp -l john paul

または

ln john paul

新しいファイルを作成しなかったのではなく、既存のファイルに新しい名前を付けました。これで、2つの名前のファイルができました:johnpaul。言うと

cp george paul

あなたは覆っています。そのファイル。名前が2つあるという事実は重要ではありません。名前が 42 個であってもアクセスできない場所にあっても、このコマンドはgeorge\nその名前 (パス) のすべてにデータをコピーしません。データが到着するとコピーされます。ファイル名前が複数あります。

関連情報