Tại sao phải kiểm tra sự tồn tại của tập tin trước khi tìm nguồn cung ứng?


13

Khi cố gắng tìm nguồn tệp, bạn có muốn gặp lỗi khi nói tệp không tồn tại để bạn biết phải sửa gì không?

Ví dụ: nvm khuyên bạn nên thêm phần này vào hồ sơ / RC của bạn:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Với ở trên, nếu nvm.shkhông tồn tại, bạn sẽ gặp "lỗi im lặng". Nhưng nếu bạn cố gắng . "$NVM_DIR/nvm.sh", đầu ra sẽ được FILE_PATH: No such file or directory.


3
Bạn không nên. Thật đáng tiếc. Hãy thử nguồn sau đó xử lý lỗi, nếu có
Mikel

2
@Mikel đúng! vậy thì tại sao tôi lại thấy nó ở khắp mọi nơi?
JBallin

3
@FaheemMitha, tốt, nếu những gì bạn đang làm hoàn toàn nhạy cảm với bảo mật (tức là chương trình của bạn hoạt động thay cho người khác), thì bạn thực sự cần phải quan tâm đến các điều kiện cuộc đua ( TOCTOU ). Tuy nhiên, đó có lẽ không phải là trường hợp vì bạn gặp vấn đề lớn hơn nếu có ai đó sửa đổi các tập tin HOME.
ilkkachu

8
@FaheemMitha Không bao giờ tốt hơn để kiểm tra sự tồn tại đầu tiên. Tình huống có thể thay đổi giữa kiểm tra và sử dụng, mang lại cả dương tính giả và âm tính giả: và dù sao bạn vẫn phải xử lý lỗi khi sử dụng. Và như bạn đã đề cập đến hiệu suất, việc kiểm tra trước khi sử dụng chậm hơn gấp đôi so với việc không làm như vậy và để hệ thống làm điều đó, điều mà nó sẽ làm dù thế nào đi nữa. Nó không thể được.
dùng207421

1
@SimonRichter, trong trường hợp này nvm sẽ không hoạt động. Người dùng sẽ phải tự tìm ra rằng tập tin bị thiếu.
JBallin

Câu trả lời:


25

Trong shell POSIX, .là một nội dung đặc biệt, do đó, lỗi của nó khiến cho shell thoát ra (trong một số shell như bash, nó chỉ được thực hiện khi ở chế độ POSIX).

Những gì đủ điều kiện là một lỗi phụ thuộc vào vỏ. Không phải tất cả đều thoát do lỗi cú pháp khi phân tích tệp, nhưng hầu hết sẽ thoát khi không tìm thấy hoặc mở tệp có nguồn gốc. Tôi không biết bất kỳ cái gì sẽ thoát nếu lệnh cuối cùng trong tệp có nguồn gốc được trả về với trạng thái thoát khác không (trừ khi errexittất nhiên là tùy chọn được bật).

Làm ở đây:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Có phải là trường hợp bạn muốn tìm nguồn tệp nếu nó ở đó và không nếu nó không (hoặc trống ở đây với -s).

Đó là, nó không nên được coi là một lỗi (lỗi nghiêm trọng trong shell POSIX) nếu tệp không có ở đó, tệp đó được coi là một tệp tùy chọn.

Nó vẫn sẽ là một lỗi (nghiêm trọng) nếu tệp không thể đọc được hoặc là một thư mục hoặc (trong một số shell) nếu có lỗi cú pháp trong khi phân tích nó sẽ là điều kiện lỗi thực sự cần được báo cáo.

Một số người sẽ tranh luận rằng có một điều kiện chủng tộc. Nhưng điều duy nhất có nghĩa là shell sẽ thoát với lỗi nếu tệp bị xóa ở giữa [., nhưng tôi cho rằng nó hợp lệ khi coi đó là lỗi mà tệp đường dẫn cố định này sẽ đột nhiên biến mất trong khi tập lệnh là đang chạy.

Mặt khác,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

trong đó commandloại bỏ thuộc tính đặc biệt cho .lệnh (vì vậy nó không thoát khỏi vỏ bị lỗi) sẽ không hoạt động như sau:

  • nó sẽ ẩn .các lỗi nhưng cũng là lỗi của các lệnh chạy trong tệp có nguồn gốc
  • nó cũng sẽ ẩn các điều kiện lỗi thực sự như tệp có quyền sai.

Các cú pháp phổ biến khác (xem ví dụ grep -r /etc/default /etc/init*trên các hệ thống Debian để biết các tập lệnh init chưa được chuyển đổi thành systemd(nơi EnvironmentFile=-/etc/default/serviceđược sử dụng để chỉ định tệp môi trường tùy chọn thay thế)) bao gồm:

  • [ -e "$file" ] && . "$file"

    Kiểm tra tập tin ở đó, vẫn nguồn nếu nó trống. Vẫn còn lỗi nghiêm trọng nếu không thể mở được (ngay cả khi nó ở đó hoặc ở đó). Bạn có thể thấy nhiều biến thể hơn như [ -f "$file" ](tồn tại và là một tệp thông thường ), [ -r "$file" ](có thể đọc được) hoặc kết hợp các biến thể đó.

  • [ ! -e "$file" ] || . "$file"

    Một phiên bản tốt hơn một chút. Làm cho nó rõ ràng hơn rằng các tập tin không tồn tại là một trường hợp OK. Điều đó cũng có nghĩa là ý $?chí phản ánh trạng thái thoát của lệnh cuối cùng được chạy $file(trong trường hợp trước, nếu bạn nhận được 1, bạn không biết liệu đó có phải là do $filekhông tồn tại hay nếu lệnh đó thất bại).

  • command . "$file"

    Yêu cầu tệp sẽ ở đó, nhưng không thoát nếu không thể hiểu được.

  • [ ! -e "$file" ] || command . "$file"

    Kết hợp các cách trên: không sao nếu tệp không có ở đó và đối với trình bao POSIX, lỗi mở (hoặc phân tích) tệp được báo cáo nhưng không gây tử vong (có thể được mong muốn hơn ~/.profile).


Lưu ý: zshTuy nhiên, bạn không thể sử dụng commandnhư vậy trừ khi shthi đua; lưu ý rằng trong vỏ Korn, sourcethực sự là một bí danh cho command ., một biến thể không đặc biệt của.


Hấp dẫn! Tôi không biết rằng về POSIX sh. Nhưng câu hỏi là về .bash_profile. Tôi đoán an toàn tốt hơn xin lỗi, nhưng bash bao giờ ở chế độ POSIX khi .bash_profilecó nguồn gốc?
Mikel

(Tôi nhận thấy bạn có thể diễn giải câu hỏi này khi áp dụng rộng rãi hơn cho tất cả các vỏ POSIX dựa trên việc đọc liên kết nguồn nvm trong câu hỏi.)
Mikel

@Mikel, câu trả lời của tôi vẫn áp dụng bashkhi không ở chế độ POSIX. Bạn muốn [ -e /file ] && . /filenếu bạn không coi đó là lỗi khi tệp không tồn tại. Nguồn thử sau đó xử lý lỗi, nếu có thể không được thực hiện ở đây.
Stéphane Chazelas

1
@Mikel, đó là phản tác dụng. 1) không ngăn được lối ra khi có lỗi với shell POSIX (hoặc bash ở chế độ POSIX) 2), sao chép thông báo lỗi (của bạn trên thiết bị xuất chuẩn), .sẽ báo cáo lỗi (trên thiết bị lỗi chuẩn). Và nếu mục đích là không coi đó là lỗi khi tệp không tồn tại, thì điều đó không đúng (và không thể, từ trạng thái thoát để thông báo nếu .thất bại vì tệp không tồn tại hoặc không thể đọc được, hoặc là không thể phân tích cú pháp, hoặc lệnh cuối cùng thất bại) đó là những điểm tôi đưa ra ở đây trong câu trả lời này.
Stéphane Chazelas

1
Liên quan đến điều kiện cuộc đua - vấn đề IMHO ít xảy ra khi đăng nhập thất bại một lần (trong khi điều gì đó kỳ lạ đang xảy ra trên hệ thống) so với việc đăng nhập thất bại một cách nhất quán (ngay cả khi có gì đó không hoàn toàn đúng trong cấu hình của người dùng -các tập tin). Vì vậy, một điều kiện cuộc đua trong kiểm tra này vẫn là một cải tiến so với việc không có kiểm tra.
ruakh

5

Người duy trì nvmphản ứng của:

thật dễ dàng để gỡ cài đặt nvm bằng cách xóa tệp; buộc công việc bổ sung (để theo dõi nơi (các) dòng đó là nguồn nvm) dường như không có giá trị đặc biệt.

Giải thích của tôi (kết hợp với lời giải thích tuyệt vời của Stéphane và nhận xét của Kusalananda):

Nó đơn giản và an toàn hơn.

Nó bảo vệ chống lại các vỏ POSIX khi khởi động do một tệp bị thiếu (vì nhiều lý do). Những người sử dụng đạn không phải POSIX (ví dụ bash) có thể loại bỏ điều kiện nếu họ thích.


1
Tôi phản đối cách giải thích của bạn rằng nó "nhắm vào người mới bắt đầu". Đó là phòng thủ. Bạn không muốn trình đăng nhập của người dùng chấm dứt bất ngờ khi khởi động chỉ vì tệp đó bị mất (vì bất kỳ lý do gì). Nếu dòng mã đó nằm trong một trong các tệp shell init bên dưới /etc, thì nó cho phép một số người dùng có tệp và một số không có tệp. IMHO, nvmphản hồi của người bảo trì chỉ chạm vào một khía cạnh.
Kusalananda

1

Như JBallinStéphane Chazelas đã chỉ ra, trong các vỏ POSIX, việc tìm nguồn cung cấp một tệp không tồn tại sẽ khiến việc đăng nhập thất bại.

Nhưng việc thêm một bài kiểm tra để xem liệu tập tin có tồn tại và sau đó cố gắng tìm nguồn nó có thể gây ra một thứ gọi là điều kiện cuộc đua hay không. Nếu có gì đó thay đổi nvm.shở giữa [ -s nvm.sh ]. nvm.sh, nó sẽ gây ra chính xác lỗi mà họ đang cố gắng ngăn chặn, mặc dù hiếm hơn nhiều.

Nói chung, cách để ngăn chặn điều kiện cuộc đua là chỉ cần thử điều bạn muốn làm, sau đó xử lý lỗi nếu thất bại, ví dụ:

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Hóa ra điều này không hoạt động trong shell POSIX, vì, như trên, .việc không thành công sẽ khiến shell thoát ngay lập tức, trước khi bất kỳ xử lý lỗi nào có thể chạy.

Câu trả lời của tôi cho rằng các vỏ POSIX không liên quan đến câu hỏi này, vì .bash_profilekhông nên chạy ở chế độ POSIX. Vì vậy, chúng ta chỉ có thể làm mã ở trên.

Để an toàn nhất, chúng tôi có thể đảm bảo rằng chế độ POSIX không có hiệu lực hoặc đảm bảo chế độ POSIX bị vô hiệu hóa bằng cách sử dụng kỹ thuật được mô tả trong /unix//a/383581/3169 .

Câu trả lời của Stéphane có một số gợi ý hữu ích về cách xử lý tất cả các vỏ POSIX, mà tôi nghĩ là ý định của tác giả nvm, nhưng khác biệt một chút so với câu hỏi ở đây, đó là lý do tại sao chúng tôi có nhiều cách tiếp cận khả thi, tùy thuộc vào mục tiêu của bạn là gì .

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.