Một tiện ích để hoán đổi hai tệp

Aug 17 2020

Mặc dù tôi chưa bao giờ phải hoán đổi hai tệp, tôi luôn tự hỏi liệu có lệnh nào để hoán đổi hai tệp hay không và gần đây tôi đã quyết định xem có lệnh nào không. Cuối cùng, tôi phát hiện ra rằng không có một cái và kết quả là tôi quyết định tạo một cái.

Đây là mã: main.cc

#include <iostream>
#include <filesystem>
#include <cstring>
#include <cassert>

static auto print_help() -> void {
    std::cout << "Usage: swap [file1] [file2]\n";
    std::cout << "swaps the contents of file1 and file2\n";
    std::cout << "use swap --help to print this message\n";
}

static auto validate_files(const std::filesystem::path& file1_path, const std::filesystem::path& file2_path) -> void {
    {
        /* check if file exists */
        const auto file1_exists = std::filesystem::exists(file1_path);
        const auto file2_exists = std::filesystem::exists(file2_path);
        const auto exists = file1_exists && file2_exists;


        if (!exists) {
            if (!file1_exists) std::cerr << "cannot find file " << file1_path << '\n';
            if (!file2_exists) std::cerr << "cannot find file " << file2_path << '\n';
            exit(EXIT_FAILURE);
        }
    }

    {
        if (file1_path == file2_path) {
            std::cerr << "swaping the same two files does nothing\n";
            exit(EXIT_SUCCESS);
        }
    }
}

static auto get_temp_filename(char* template_name) -> void {
    /* tmpnam_s does not work on linux */
#if defined(WIN32) || defined(_WIN32)
    errno_t err = tmpnam_s(template_name, L_tmpnam);
    assert(!err);
#else
    int err = mkstemp(template_name);
    assert(err != -1);
#endif
}

int main(int argc, char** argv) {
    std::ios::sync_with_stdio(false);
    switch (argc) {
        case 2: {
            /* convert the second arg to upper case */
            std::transform(argv[1],
                argv[1] + strlen(argv[1]),
                argv[1],
                ::toupper);
            if (!strcmp(argv[1], "--HELP")) {
                print_help();
                return EXIT_SUCCESS;
            }
            else {
                std::cerr << "Invalid args see --help for usage\n";
                return EXIT_FAILURE;
            }
        }
        case 3:
            break;
        default: {
            std::cerr << "Invalid args see --help for usage\n";
            return EXIT_FAILURE;
        }
    }
    const auto file1_path = std::filesystem::path{ argv[1] };
    const auto file2_path = std::filesystem::path{ argv[2] };


    validate_files(file1_path, file2_path);
    char temp_filename[L_tmpnam] = "XXXXXX";
    get_temp_filename(temp_filename);
    const auto temp_filepath = std::filesystem::path{ temp_filename };
    /* move-swap the files instead of copy-swaping */
    /* renaming a file is the same as moving it */
    std::filesystem::rename(file1_path, temp_filepath);
    std::filesystem::rename(file2_path, file1_path);
    std::filesystem::rename(temp_filepath, file2_path);
}

Đây là một ví dụ về cách sử dụng:

swap doc1.txt doc2.txt

Trả lời

11 vnp Aug 17 2020 at 06:22
  • file1_path == file2_pathchắc chắn nói rằng các đường dẫn tham chiếu đến cùng một tệp. Tuy nhiên, ngay cả khi file1_path != file2_pathchúng vẫn có thể tham chiếu đến cùng một tệp.

  • file1_exists = std::filesystem::exists(file1_path);giới thiệu điều kiện đua TOC-TOU. Tệp có thể tồn tại tại thời điểm thử nghiệm, nhưng sẽ biến mất theo thời gian sử dụng. Xem gạch đầu dòng tiếp theo.

  • std::filesystem::renamecó thể thất bại. Bạn gọi nó là thời gian cây. Nếu cuộc gọi thứ hai hoặc thứ ba không thành công (với một ngoại lệ!), Hệ thống tệp sẽ không chính xác ở trạng thái mà người ta mong đợi. Sử dụng noexceptquá tải, kiểm tra error_codesau mỗi cuộc gọi và khôi phục tất cả các hành động trước khi thất bại. Điều đó cũng sẽ tự động chăm sóc các đường dẫn không tồn tại.

  • Đừng assert. Nó chỉ tốt để bắt lỗi, không phải vấn đề thời gian chạy. Trong mã sản xuất (được biên dịch với -DNDEBUG), nó không có tác dụng gì và chương trình của bạn sẽ không phát hiện ra mkstemplỗi.

  • Chương trình im lặng không làm gì nếu được gọi với, nói, 4 đối số. Nó cũng tốn rất nhiều công sức nếu được gọi với 2 đối số. Gọi print_help()bất cứ lúc nào argc != 3cũng đơn giản hơn nhiều.

1 Razzle Aug 28 2020 at 20:26

Tôi đoán 'câu hỏi' là yêu cầu phê bình mã. Nếu tôi sai về điều đó, xin hãy nhẹ nhàng .. LOL

Ấn tượng đầu tiên của tôi là khả năng đọc của main. Công tắc phân tích cú pháp đối số lớn đang ẩn logic quan trọng hơn ..

Có thể làm cho nó dễ đọc hơn (đối tượng chuỗi và so sánh không phân biệt chữ hoa chữ thường?) .. Thậm chí tốt hơn: chuyển sang phương thức trợ giúp quản lý.

(Ngoài ra: Đôi khi đơn giản cũng không sao. Nếu bạn không nhận được hai đối số tên tệp, bạn có thể đưa ra sự trợ giúp ngay lập tức, thay vì yêu cầu người dùng yêu cầu trợ giúp một cách rõ ràng.)