Un programma di utilità per scambiare due file
Anche se non ho mai dovuto scambiare due file, mi sono sempre chiesto se esistesse un comando per scambiare due file e recentemente ho deciso di vedere se ce n'era uno. Alla fine ho scoperto che non ce n'era uno e di conseguenza ho deciso di crearne uno.
Ecco il codice: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);
}
Ecco un esempio di utilizzo:
swap doc1.txt doc2.txt
Risposte
file1_path == file2_path
sicuramente dice che i percorsi si riferiscono allo stesso file. Tuttavia, anche sefile1_path != file2_path
possono ancora fare riferimento allo stesso file.file1_exists = std::filesystem::exists(file1_path);
introduce una race condition TOC-TOU. Il file potrebbe esistere al momento del test, ma scomparire al momento dell'utilizzo. Vedi il prossimo punto.std::filesystem::rename
potrebbe fallire. Lo chiami tre volte. Se la seconda o la terza chiamata fallisce (con un'eccezione!), il filesystem finisce non esattamente in uno stato che ci si aspetterebbe. Utilizzare unnoexcept
sovraccarico, testareerror_code
dopo ogni chiamata ed eseguire il rollback di tutte le azioni prima dell'errore. Ciò si occuperebbe anche automaticamente dei percorsi inesistenti.Non farlo
assert
. È utile solo rilevare i bug, non i problemi di runtime. Nel codice di produzione (compilato con-DNDEBUG
) non fa nulla e il tuo programma non rileverebbe unmkstemp
errore.Il programma silenziosamente non fa nulla se chiamato con, diciamo, 4 argomenti. Ci vuole anche così tanto impegno se chiamato con 2 argomenti. Chiamare in
print_help()
qualsiasi momentoargc != 3
è molto più semplice.
Immagino che la "domanda" richieda critiche al codice. Se mi sbaglio, per favore sii gentile.. LOL
La mia prima impressione è la leggibilità di main. Il grande interruttore di analisi degli argomenti nasconde la logica più importante.
Forse rendere più facile la lettura (oggetti stringa e confronti senza distinzione tra maiuscole e minuscole?) .. Ancora meglio: relegare a un metodo di aiuto per le pulizie.
(Inoltre: a volte semplicistico va bene. Se non ricevi due argomenti di nome file potresti sputare immediatamente l'aiuto, piuttosto che dire all'utente di chiedere esplicitamente aiuto.)