Ngạc nhiên vì hành vi của cp với các liên kết cứng


20

Tôi hiểu rất rõ khái niệm về các liên kết cứng và đã đọc các trang hướng dẫn cho các công cụ cơ bản như cp--- và thậm chí các thông số kỹ thuật POSIX gần đây --- một số lần. Tôi vẫn ngạc nhiên khi thấy các hành vi sau:

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

Tại thời điểm này johnpaulsẽ có cùng một nút (và nội dung), và georgesẽ khác nhau ở cả hai khía cạnh. Bây giờ chúng tôi làm:

$ cp george paul

Tại thời điểm này, tôi mong đợi georgepaulcó các số inode khác nhau nhưng cùng một nội dung --- kỳ vọng này đã được thực hiện --- nhưng tôi cũng dự kiến paulbây giờ sẽ có một số inode khác johnjohnvẫn có nội dung john. Đây là nơi tôi đã ngạc nhiên. Nó chỉ ra rằng việc sao chép một tệp vào đường dẫn đích paulcũng có kết quả của việc cài đặt cùng một tệp đó (cùng một nút) tại tất cả các đường dẫn đích khác có chung paulinode. Tôi đã nghĩ rằng cptạo ra một tập tin mới và di chuyển nó vào nơi trước đây bị chiếm giữ bởi tập tin cũ paul. Thay vào đó, những gì nó dường như làm là mở tệp hiện có paul, cắt bớt nó và viếtgeorgeNội dung của tập tin hiện có. Do đó, bất kỳ tệp "khác" nào có cùng inode đều được cập nhật nội dung "của chúng" cùng một lúc.

Ok, đây là một hành vi có hệ thống và bây giờ tôi biết để mong đợi nó, tôi có thể tìm ra cách làm việc xung quanh nó, hoặc tận dụng nó, khi thích hợp. Điều gì làm tôi bối rối là tôi phải xem tài liệu này ở đâu? Tôi sẽ ngạc nhiên nếu nó không được ghi lại ở đâu đó trong các tài liệu mà tôi đã xem. Nhưng rõ ràng là tôi đã bỏ lỡ nó và bây giờ không thể tìm thấy một nguồn thảo luận về hành vi này.

Câu trả lời:


4

Đầu tiên, tại sao nó được thực hiện theo cách này? Một lý do là lịch sử: đó là cách nó được thực hiện trong Unix First Edition .

Các tập tin được thực hiện theo cặp; cái đầu tiên được mở để đọc, chế độ thứ hai được tạo 17. Sau đó, cái đầu tiên được sao chép vào cái thứ hai.

Tạo được đề cập đến creatcuộc gọi hệ thống (cuộc gọi nổi tiếng thiếu một e ), nó cắt ngắn tập tin hiện có theo tên đã cho nếu có.

đây là mã nguồn của cpPhiên bản thứ hai Unix (Tôi không thể tìm thấy mã nguồn của Ấn bản đầu tiên). Bạn có thể thấy các cuộc gọi đến opencho tệp nguồn và creatcho tệp thứ hai; và, như là một cải tiến cho Ấn bản đầu tiên, nếu tệp thứ hai là một thư mục hiện có, cptạo một tệp trong thư mục đó.

Nhưng, bạn có thể hỏi, tại sao nó lại được thực hiện theo cách đó? Câu trả lời cho vấn đề tại sao Unix ban đầu làm theo cách đó hầu như luôn luôn đơn giản. cpmở nguồn của nó để đọc và tạo đích của nó - và hệ thống gọi để tạo một tệp ghi đè lên một tệp hiện có bằng cách mở nó để ghi, bởi vì điều đó cho phép người gọi áp đặt nội dung của tệp theo tên đã cho dù tệp đã tồn tại hay không phải.

Bây giờ, như là nơi nó được ghi lại: trong trang man FreeBSD .

Đối với mỗi tệp đích đã tồn tại, nội dung của nó sẽ bị ghi đè nếu quyền cho phép. Chế độ, ID người dùng và ID nhóm của nó không thay đổi trừ khi tùy chọn -p được chỉ định.

Từ ngữ đó đã có mặt ít nhất là từ năm 1990 (trở lại khi BSD là 4.3BSD). Có từ ngữ tương tự trên Solaris 10 :

Nếu target_file tồn tại, cp ghi đè lên nội dung của nó, nhưng chế độ (và ACL nếu có), chủ sở hữu và nhóm được liên kết với nó không bị thay đổi.

Trường hợp của bạn thậm chí được đánh vần trong hướng dẫn HP-UX 10 :

Nếu new_file là một liên kết đến một tệp hiện có với các liên kết khác, ghi đè lên tệp hiện có và giữ lại tất cả các liên kết.

POSIX đặt nó trong tiêu chuẩn. Trích dẫn từ Single UNIX v2 :

Nếu Dest_file tồn tại, các bước sau đây sẽ được thực hiện: (Bằng) như đối số oflag.

Các trang man và thông số kỹ thuật mà tôi đã trích dẫn thêm xác định rằng nếu -ftùy chọn được thông qua và nỗ lực mở / tạo tệp mục tiêu không thành công (thường là do không có quyền ghi tệp), hãy cpthử xóa mục tiêu và tạo lại tệp . Điều này sẽ phá vỡ liên kết cứng trong kịch bản của bạn.

Bạn có thể muốn báo cáo lỗi tài liệu đối với hướng dẫn sử dụng GNU coreutils , vì nó không ghi lại hành vi này. Ngay cả mô tả về --preserve=links, trong kịch bản của bạn sẽ dẫn đến paulliên kết bị xóa và một tệp mới được tạo, không làm rõ điều gì xảy ra mà không có --preserve=links. Mô tả về -floại ngụ ý những gì xảy ra mà không có nó nhưng không đánh vần được nó


tại sao bạn nói "bởi vì điều đó cho phép người gọi có quyền sở hữu tên tệp cho dù tệp đó đã tồn tại hay chưa"? Cp không có quyền sở hữu một tập tin có sẵn.
jrw32982 hỗ trợ Monica

@ jrw32982 Tôi có nghĩa là quyền sở hữu theo nghĩa quyết định những gì đi vào tệp, không phải quyền sở hữu theo nghĩa siêu dữ liệu tệp. Tôi đã viết lại câu đó.
Gilles 'SO- ngừng trở nên xấu xa'

20

cptài liệu mà nó ghi đè lên tệp đích nếu tệp đích đã có sẵn. Bạn đúng rằng nó không chỉ định chi tiết "ghi đè" nghĩa là gì, nhưng nó chắc chắn nói "ghi đè", không phải "thay thế". Nếu bạn muốn trở thành người phạm tội, bạn có thể lập luận rằng "ghi đè" chính xác là những gì cpvà hành vi mà bạn đang mong đợi sẽ được gọi là "thay thế".

Cũng lưu ý rằng nếu cp"thay thế" các tệp đích đã tồn tại trước đó, điều đó có thể hợp lý được coi là đáng ngạc nhiên hoặc không chính xác, có thể là so với "ghi đè". Ví dụ:

  • Nếu cplần đầu tiên xóa tệp cũ và sau đó tạo một tệp mới thì sẽ có một khoảng thời gian trong đó tệp sẽ vắng mặt, điều này sẽ gây ngạc nhiên.
  • Nếu cplần đầu tiên tạo một tệp tạm thời và sau đó di chuyển nó vào vị trí thì có lẽ nó sẽ ghi lại điều này, do thực tế là các tệp tạm thời có tên lạ đó thỉnh thoảng sẽ được chú ý ... nhưng không.
  • Nếu cpkhông thể tạo một tệp mới trong cùng thư mục với tệp cũ do quyền thì điều này thật đáng tiếc (đặc biệt là nếu nó đã xóa tệp cũ).
  • Nếu tập tin đã không được sở hữu bởi người sử dụng đang chạy cpvà chạy người sử dụng cpkhông phải là rootsau đó nó sẽ không thể để phù hợp với chủ sở hữu & điều khoản của các tập tin mới cho những người của tập tin mới.
  • Nếu tệp có các thuộc tính đặc biệt ưa thích mà cpkhông biết, thì chúng sẽ bị mất trong bản sao. Ngày nay, việc triển khai cpphải hiểu một cách đáng tin cậy những thứ như thuộc tính mở rộng, nhưng không phải lúc nào cũng như vậy. Và có những thứ khác, như các nhánh tài nguyên MacOS, hoặc, cho các hệ thống tập tin từ xa, về cơ bản là bất cứ thứ gì.

Vì vậy, trong kết luận: bây giờ bạn biết những gì cpthực sự làm. Bạn sẽ không bao giờ ngạc nhiên bởi nó một lần nữa! Thành thật mà nói, tôi nghĩ điều tương tự cũng có thể xảy ra với tôi, nhiều năm trước.


Phải kiểm tra tham chiếu POSIX, nhưng trên thực tế, các mantrang cptrên BSD (ít nhất là OSX) và các phiên bản Gnu cpkhông quá rõ ràng về "ghi đè". Từ đó chỉ được sử dụng trong các ý kiến ​​về các tùy chọn -i-n. Trang web của Gnu đặc biệt không có thông tin, bắt đầu Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.Trang chủ BSD / Mac ít nhất nóiIn the first synopsis form, the cp utility copies the contents of the source_file to the target_file.
dubiousjim

Trang thông tin ‘cp’ copies files (or, optionally, directories). The copy is completely independent of the original.
lõi

2
Tôi thấy rằng tiêu chuẩn POSIX 2008 không chỉ định hành vi được quan sát; Tôi sẽ thêm một câu trả lời.
dubiousjim

16

Tôi thấy rằng tiêu chuẩn POSIX 2013 không chỉ định hành vi được quan sát . Nó nói rằng:

  1. Nếu source_file thuộc loại tệp thông thường, các bước sau sẽ được thực hiện:

    a. ... nếu Dest_file tồn tại, các bước sau sẽ được thực hiện:

    tôi. Nếu -itùy chọn có hiệu lực, cptiện ích sẽ viết lời nhắc đến lỗi tiêu chuẩn và đọc một dòng từ đầu vào tiêu chuẩn. Nếu phản hồi không được xác nhận, cpsẽ không làm gì thêm với source_file và chuyển sang bất kỳ tệp nào còn lại.

    ii. Một mô tả tập tin cho dest_file được thu được bằng cách thực hiện hành động tương đương với open()chức năng quy định tại khối lượng hệ thống giao diện của POSIX.1-2008 gọi sử dụng dest_file như là đối số con đường, và bitwise-bao gồm ORcác O_WRONLYO_TRUNCnhư oflag tranh cãi.

    iii. Nếu nỗ lực để có được một bộ mô tả tệp không thành công và -ftùy chọn có hiệu lực, cpsẽ cố gắng loại bỏ tệp bằng cách thực hiện các hành động tương đương với unlink()chức năng được xác định trong khối Giao diện hệ thống của POSIX.1-2008 được gọi bằng cách sử dụng Dest_file làm đối số đường dẫn. Nếu lần thử này thành công, cpsẽ tiếp tục với bước 3b.

    ...

    d. Nội dung của source_file sẽ được ghi vào bộ mô tả tệp. Bất kỳ lỗi ghi sẽ gây ra cpđể viết một thông báo chẩn đoán lỗi tiêu chuẩn và tiếp tục bước 3e.

    e. Bộ mô tả tập tin sẽ được đóng lại.


1
Hấp dẫn. Giống như bạn, tôi giả sử cpsẽ cho kết quả tương tự mvvà phá vỡ mọi liên kết cứng mà số phận là một phần của. Nhưng bây giờ tôi nghĩ về nó, điều đó có nghĩa là nó sẽ phải đặc biệt unlink(2)là mục tiêu ( cp -f) hoặc tạo một tên tạm thời khác và sau đó là rename(2)nó. Việc thực hiện đơn giản là chỉ cần mở tệp để ghi đè, đó là những gì POSIX yêu cầu. Nó tương đương vớicat src > dest
Peter Cordes

2

Nếu bạn có thể nói, thì sao chép một tập tin vào đường dẫn đích paul cũng sao chép cùng một tập tin (cùng một nút) vào tất cả các đường dẫn đích khác có chung paulinode., Tôi rất tiếc phải nói rằng bạn không hiểu khái niệm về liên kết cứng rất tốt. Nếu tôi tặng một quả táo cho Sir McCartney, tôi đã tặng một quả táo cho Paul và tôi đã tặng một quả táo cho đối tác sáng tác bài hát của John Lennon. Nhưng tôi đã không cho ra ba quả táo; Tôi đã tặng một quả táo cho một người có nhiều tên / tiêu đề / mô tả.

Tương tự như vậy, khi bạn sao chép georgeđến paul, bạn sẽ không còn sao chép nó vào john. Thay vào đó, bạn đang sao chép georgedữ liệu vào tệp có inode được trỏ đến bởi paulmục nhập thư mục.

Từng bước:   Khi bạn làm

echo john > john

bạn đã tạo một tệp mới (giả sử rằng chưa có tệp có tên johntrong thư mục đó). Hoặc, nói một cách nghiêm túc hơn, điều này giả định rằng đã không có một mục nhập thư mục có tên johntrong thư mục đó (bởi vì, nói đúng ra, không có tệp nào trong các thư mục; chỉ có các mục trong thư mục, trỏ đến inodes). Sau khi bạn làm

cp -l john paul

hoặc là

ln john paul

bạn chưa tạo một tập tin mới; thay vào đó, bạn đã đặt cho tệp hiện tại của mình một tên mới. Bây giờ bạn có một tệp có hai tên: johnpaul. Và khi bạn nói

cp george paul

bạn đang ghi đè lên tập tin đó . Việc nó có hai tên là không liên quan; nó có thể có 42 tên, có thể ở những nơi bạn thậm chí không thể truy cập và lệnh này sẽ không sao chép george\ndữ liệu vào tất cả các tên đó (đường dẫn); nó chỉ là sao chép dữ liệu vào một tệp có nhiều tên.


1
Cảm ơn. Đúng vậy, tôi đã nhận thức được nhân vật cần trích dẫn sợ hãi về những gì tôi đã viết khi tôi viết nó: johnpaulbắt đầu như hai tên đường dẫn cho cùng một tệp. Nhưng đó là cách dễ nhất mà tôi có thể nghĩ ra để thể hiện bản thân. Tôi không nghĩ rằng khái niệm đơn thuần về một liên kết cứng, được hiểu chính xác, chỉ ra một trong hai hành vi cho cp(không có -l).
dubiousjim

Nhưng cảm ơn vì sự ủng hộ; Tôi đã cố gắng làm rõ từ ngữ.
dubiousjim
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.