ハードリンクを使用したcpの動作に驚いた


20

私はハードリンクの概念を非常によく理解しており、cp---そして最近のPOSIX仕様---のような基本的なツールのマニュアルページを何度も読みました。それでも、私は次の動作を観察して驚いた:

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

この時点johnpaul、同じiノード(およびコンテンツ)を持ちgeorge、両方の点で異なります。今私たちは:

$ cp george paul

この時点で、私は期待georgepaul異なるinode番号が、同じ内容---この期待が満たされたを持っている---しかし、私はまた、予想paul今から別のinode番号を持っているjohnとのために、johnまだコンテンツを持っていますjohn。これは私が驚いた場所です。ファイルをコピー先パスにコピーすると、paulその同じファイル(同じiノード)を、iノードを共有する他のすべてのコピー先パスにインストールする結果にもなりますpaulcp新しいファイルを作成し、以前は古いファイルで占められていた場所に移動することを考えていましたpaul。代わりに、既存のファイルを開きpaul、切り捨てて、書き込むことですgeorgeのコンテンツを既存のファイルに追加します。したがって、同じiノードを持つ「その他」のファイルは、「それらの」コンテンツを同時に更新します。

わかりました、これは体系的な動作であり、それを期待することがわかったので、必要に応じて回避方法を利用したり、利用したりできます。この動作が文書化されるのを見るはずだったのは、私にとって何が困ったことですか?私がすでに見た文書のどこかに文書化されていない場合、私は驚くでしょう。しかし、どうやら私はそれを見逃したようで、この振る舞いについて議論するソースを見つけることができません。

回答:


4

まず、なぜこのように行われるのですか?1つの理由は歴史的です:それがUnix First Editionで行われ方法です。

ファイルはペアで取得されます。1つ目は読み取り用に開かれ、2つ目は作成されたモード17です。1つ目は2つ目にコピーされます。

「作成済み」とは、creatシステムコール(eが欠落していることで有名なもの)を指し、指定された名前がある場合は既存のファイルを切り捨てます。

そして、ここでの「のソースコードだcpUnixの第二版では(私は初版のソースコードを見つけることができません)。openソースファイルとcreat2番目のファイルの呼び出しを確認できます。また、First Editionの改善として、2番目のファイルが既存のディレクトリである場合、cpそのディレクトリにファイルを作成します。

しかし、あなたは尋ねるかもしれません、なぜそれがそのように行われたのですか?「Unixがもともとそのようにした理由」に対する答えは、ほとんどの場合単純です。cp読み取り用にソースを開き、宛先を作成します。ファイルを作成するシステムコールは、書き込み用に開いて既存のファイルを上書きします。これにより、ファイルが既に存在するかありません。

さて、それが文書化されている場所については、FreeBSDのmanページをご覧ください

既に存在する宛先ファイルごとに、許可が許可されている場合、その内容は上書きされます。モード、ユーザーID、およびグループIDは、-pオプションが指定されていない限り変更されません。

その言葉遣いは少なくとも1990年(BSDが4.3BSDだった頃)にまで遡ります。Solaris 10にも同様の表現があります。

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

HP-UX 10のマニュアルでは、あなたのケースの詳細まで記述されています。

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

POSIXでは標準化されています。Single UNIX v2からの引用:

dest_fileが存在する場合、次の手順が行われます:(…)dest_fileのファイル記述子は、パス引数としてdest_fileを使用して呼び出されるXSH仕様open()関数と同等のアクションを実行し、O_WRONLYとO_TRUNC oflag引数として。

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

GNU coreutilsマニュアルに対するドキュメントのバグを報告することをお勧めします。この動作はドキュメント化されていないためです。の説明でも、リンクが削除され、新しいファイルが作成される--preserve=linksシナリオではpaul、なしで何が起こるかが明確になりません--preserve=links-f種類の説明は、それなしで何が起こるかを暗示していますが、それを綴りません(「このオプションなしでコピーし、既存の宛先ファイルを書き込み用に開くことができない場合、コピーは失敗します。ただし、-force、...」)。


「ファイルがすでに存在するかどうかにかかわらず、呼び出し側がファイル名の所有権を取得できるようにするため」と言うのはなぜですか?Cpは既存のファイルの所有権を取得しません。
jrw32982サポートモニカ

@ jrw32982私は、ファイルのメタデータという意味での所有権ではなく、ファイルに何を入れるかを決定するという意味で所有権を意味しました。私はその文を書き直しました。
ジル「SO-悪であるのをやめる」

20

cp宛先ファイルが既に存在する場合、宛先ファイルを上書きすることを文書化します。「上書き」の意味を詳細に指定していないのは確かですが、「置換」ではなく「上書き」と明確に述べています。あなたが慢になりたいなら、あなたは「上書き」がまさにするcpことであり、あなたが期待していた振る舞いは適切に「置換」と呼ばれると主張できます。

またcp、既存の宛先ファイルを「置換」する場合、おそらく「上書き」よりも驚くべきまたは正しくないと考えられるかもしれません。例えば:

  • cp最初に古いファイルを削除してから新しいファイルを作成した場合、ファイルが存在しない時間間隔があり、これは驚くべきことです。
  • 場合はcp最初の一時ファイルを作成し、所定の位置に移動し、それはおそらく、奇妙な名前を持つ、このような一時ファイルが時折注目されるという事実に起因し、これをドキュメント化する必要があります...しかし、それはしていません。
  • cpパーミッションが原因で古いファイルと同じディレクトリに新しいファイルを作成できなかった場合、これは残念です(特に古いファイルを既に削除している場合)。
  • ファイルを実行するユーザーが所有していなかった場合はcp、ユーザーが実行cpしなかったroot、新しいファイルのものに新しいファイルの所有者とパーミッションを一致させることは不可能であろう。
  • ファイルにcpわからない派手な特別な属性がある場合、これらはコピーで失われます。今日では、の実装はcp拡張属性などを確実に理解する必要がありますが、常にそうであるとは限りませんでした。また、MacOSリソースフォーク、またはリモートファイルシステムの場合、基本的には他のものもあります。

結論として、これでcp実際に何ができるかがわかりました。もう二度と驚くことはありません!正直なところ、何年も前に同じことが私にも起こったのではないかと思います。


POSIXの参照をチェックする必要がありますが、実際にはmanページが用cpBSD(少なくとも、OSX)とGNU上のバージョンはcp、「上書き」についてとても明示的ではありません。その単語は、オプション-iとのコメントでのみ使用されます-n。Gnuのマンページは特に情報Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.In the first synopsis form, the cp utility copies the contents of the source_file to the target_file.
価値が低く

Gnu coreutils情報ページが始まります:‘cp’ copies files (or, optionally, directories). The copy is completely independent of the original.
dubiousjim

2
POSIX 2008標準では、観察された動作が指定されていることがわかります。答えを追加します。
dubiousjim

16

POSIX 2013標準では、観察された動作が指定されていることがわかります。それは言います:

  1. 場合のsource_fileがタイプ通常のファイルであり、以下のステップがとられなければなりません。

    a。... dest_fileが存在する場合、次の手順が実行されます。

    私。場合は-iオプションが有効になっている、cpユーティリティは標準エラー出力にプロンプトを書き込み、標準入力から行を読み込むものとします。応答が肯定的でない場合、source_fileでcpこれ以上何もせず 、残りのファイルに進みます。

    ii。以下のためのファイル記述子DEST_FILEは、と等価のアクション実行することによって得られるものとopen()POSIX.1-2008のシステムインターフェースボリュームに定義された関数を使用して呼び出さDEST_FILEを path引数、およびビット単位込みとしてORO_WRONLY及びO_TRUNCとしてoflagのの引数。

    iii。ファイル記述子を取得する試みが失敗し、-fオプションが有効な場合、dest_fileをパス引数として使用して呼び出されるPOSIX.1-2008のSystem Interfacesボリュームで定義された機能cpと同等のアクションを実行することにより、ファイルの削除を試みます。この試行が成功した場合、ステップ3bに進みます。unlink()cp

    ...

    d。source_fileの内容は、ファイル記述子に書き込まれます。書き込みエラーがあるcpと、診断メッセージが標準エラーに書き込まれ、ステップ3eに進みます。

    e。ファイル記述子は閉じられます。


1
面白い。あなたと同じように、私cpはに同様の結果を与えmv、destが含まれていたハードリンクを解除すると想定しました。しかし、今考えてみるとunlink(2)、ターゲット(cp -f)を具体的に指定するか、別の名前の一時ファイルを作成してから作成する必要がありますrename(2)。簡単な実装は、上書きのためにファイルを開くだけで、これはPOSIXが必要とするものです。それは次と同等ですcat src > dest
ピーターコーデス

2

「ファイルをコピー先パスpaul にコピーすると、同じファイル(同じiノード)が、iノードを共有する他のすべてのコピー先パスにもコピーされますpaul。」と言えば、申し訳ありません。ハードリンクは非常に良好です。マッカートニーirにリンゴを贈るなら、ポールにリンゴを贈り、ジョン・レノンの作詞作曲パートナーにリンゴを贈りました。しかし、私は3つのリンゴを配っていません。複数の名前/タイトル/記述子を持つ人にリンゴを渡しました。

同様に、にコピーgeorgeするとpaul、にコピーしませんjohn。むしろ、ディレクトリエントリgeorgeが指すiノードのファイルにデータをコピーしていますpaul

ステップバイステップ:行う   とき

echo john > john

新しいファイルを作成しました(johnそのディレクトリに名前の付いたファイルがまだなかったと仮定します)。または、john厳密に言えば、これは、そのディレクトリに名前を持つディレクトリエントリがまだ存在しないことを前提としています(厳密には、ディレクトリにはファイルがなく、ディレクトリエントリのみがiノードを指しているため)。あなたがした後

cp -l john paul

または

ln john paul

新しいファイルを作成していません。むしろ、既存のファイルに新しい名前を付けました。これで、2つの名前のファイルが作成されました:johnpaul。そしてあなたが言うとき

cp george paul

そのファイルを上書きしています。2つの名前があるという事実は無関係です。おそらくアクセスできない場所に42個の名前があり、このコマンドはgeorge\nそれらの名前(パス)のすべてにデータをコピーしません。複数の名前を持つ1つのファイルにデータをコピーするだけです。


1
ありがとう。そうです、私が書いたときに書いていたものの、怖がり引用符が必要な性格に気づいていました。johnそしてpaul、同じファイルの2つのパス名として始めます。しかし、それは私が自分自身を表現するのに最も簡単な方法でした。ハードリンクという単なる概念が正しく理解されいるとは、2つの動作のどちらかを決定するものではないと思いますcp(なし-l)。
-dubiousjim

しかし、発言してくれてありがとう。私は言葉遣いを明確にしようとしました。
dubiousjim
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.