Un utilitaire pour échanger deux fichiers
Bien que je n'aie jamais eu à échanger deux fichiers, je me suis toujours demandé s'il existait une commande pour échanger deux fichiers et j'ai récemment décidé de voir s'il y en avait une. Finalement, j'ai découvert qu'il n'y en avait pas et j'ai donc décidé d'en créer un.
Voici le code :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);
}
Voici un exemple d'utilisation :
swap doc1.txt doc2.txt
Réponses
file1_path == file2_path
indique sûrement que les chemins font référence au même fichier. Cependant, même s'ilsfile1_path != file2_path
peuvent toujours faire référence au même fichier.file1_exists = std::filesystem::exists(file1_path);
introduit une condition de concurrence TOC-TOU. Le fichier peut exister au moment du test, mais disparaître au moment de l'utilisation. Voir le point suivant.std::filesystem::rename
peut échouer. Vous l'appelez trois fois. Si le deuxième ou le troisième appel échoue (avec une exception !), le système de fichiers ne se retrouve pas exactement dans un état auquel on s'attendrait. Utilisez unenoexcept
surcharge, testez leerror_code
après chaque appel et annulez toutes les actions avant l'échec. Cela prendrait également automatiquement en charge les chemins inexistants.Non
assert
. Il n'est bon que d'attraper les bogues, pas les problèmes d'exécution. Dans le code de production (compilé avec-DNDEBUG
), il ne fait rien et votre programme ne détecterait pas d'mkstemp
échec.Le programme ne fait rien en silence s'il est appelé avec, disons, 4 arguments. Cela demande aussi beaucoup d'efforts s'il est appelé avec 2 arguments. Appeler
print_help()
à tout momentargc != 3
est beaucoup plus simple.
Je suppose que la "question" demande des critiques de code. Si je me trompe à ce sujet, s'il vous plaît soyez gentil .. LOL
Ma première impression est la lisibilité de main. Le grand commutateur d'analyse d'arguments cache la logique la plus importante.
Peut-être faciliter la lecture (objets de chaîne et comparaisons insensibles à la casse ?). Mieux encore : reléguer à une méthode d'aide à l'entretien ménager.
(Aussi : Parfois, la simplicité est acceptable. Si vous ne recevez pas deux arguments de nom de fichier, vous pouvez immédiatement cracher l'aide, plutôt que de dire à l'utilisateur de demander explicitement de l'aide.)