두 파일을 교환하는 유틸리티

Aug 17 2020

두 개의 파일을 스왑 할 필요가 없었지만 두 파일을 스왑하는 명령이 있는지 항상 궁금해했으며 최근에는 하나가 있는지 확인하기로 결정했습니다. 결국 나는 하나가 없다는 것을 알았고 결과적으로 나는 하나를 만들기로 결정했습니다.

다음은 코드입니다. 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);
}

다음은 사용 예입니다.

swap doc1.txt doc2.txt

답변

11 vnp Aug 17 2020 at 06:22
  • file1_path == file2_path경로가 동일한 파일을 참조한다는 것을 확실히 알려줍니다. 그러나 file1_path != file2_path여전히 동일한 파일을 참조 할 수도 있습니다.

  • file1_exists = std::filesystem::exists(file1_path);TOC-TOU 경쟁 조건을 소개합니다. 파일은 테스트 시점에 존재하지만 사용 시점에 사라질 수 있습니다. 다음 글 머리 기호를보십시오.

  • std::filesystem::rename실패 할 수 있습니다. 당신은 그것을 트리 타임이라고 부릅니다. 두 번째 또는 세 번째 호출이 실패하면 (예외가 있습니다!), 파일 시스템은 예상 한 상태로 끝나지 않습니다. noexcept과부하를 사용하고 , error_code각 호출 후를 테스트하고 , 실패하기 전에 모든 작업을 롤백합니다. 또한 존재하지 않는 경로를 자동으로 처리합니다.

  • 하지 마십시오 assert. 런타임 문제가 아닌 버그를 잡는 것이 좋습니다. 프로덕션 코드 (로 컴파일 됨 -DNDEBUG)에서는 아무 작업도 수행하지 않으며 프로그램에서 mkstemp오류를 감지하지 못합니다 .

  • 이 프로그램은 예를 들어 4 개의 인수를 사용하여 호출하면 아무 작업도 수행하지 않습니다. 또한 2 개의 인수로 호출하면 많은 노력이 필요합니다. print_help()언제든지 전화 하는 argc != 3것이 훨씬 더 간단합니다.

1 Razzle Aug 28 2020 at 20:26

나는 '질문'이 코드 비평을 요구하고 있다고 생각합니다. 내가 틀렸다면 부드러워 .. LOL

나의 첫인상은 메인 의 가독성 입니다. 큰 인수 구문 분석 스위치는 더 중요한 논리를 숨기고 있습니다.

읽기 쉽게 만들 수 있습니다 (문자열 객체와 대소 문자를 구분하지 않는 비교?) .. 더 나은 방법 : 하우스 키핑 도우미 메서드에 의존합니다.

(또한 : 때로는 단순한 것도 괜찮습니다. 두 개의 파일 이름 인수를받지 못하면 사용자에게 명시 적으로 도움을 요청하는 대신 즉시 도움말을 뱉어 낼 수 있습니다.)