Una utilidad para intercambiar dos archivos
Si bien nunca tuve que intercambiar dos archivos, siempre me pregunté si había un comando para intercambiar dos archivos y recientemente decidí ver si había uno. Eventualmente descubrí que no había uno y como resultado decidí crear uno.
Aquí está el código: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);
}
Aquí hay un ejemplo de uso:
swap doc1.txt doc2.txt
Respuestas
file1_path == file2_path
seguramente dice que las rutas se refieren al mismo archivo. Sin embargo, incluso sifile1_path != file2_path
todavía pueden hacer referencia al mismo archivo.file1_exists = std::filesystem::exists(file1_path);
introduce una condición de carrera TOC-TOU. El archivo puede existir en el momento de la prueba, pero desaparecer en el momento del uso. Vea la siguiente viñeta.std::filesystem::rename
Puede fallar. Lo llamas tiempos de árbol. Si la segunda o tercera llamada falla (¡con una excepción!), el sistema de archivos no termina exactamente en el estado que uno esperaría. Utilice unanoexcept
sobrecarga, pruebeerror_code
después de cada llamada y revierta todas las acciones antes de que se produzca un error. Eso también se ocuparía automáticamente de los caminos inexistentes.no
assert
_ Solo es bueno para detectar errores, no los problemas de tiempo de ejecución. En el código de producción (compilado con-DNDEBUG
) no hace nada y su programa no detectaría unamkstemp
falla.El programa silenciosamente no hace nada si se llama con, digamos, 4 argumentos. También requiere mucho esfuerzo si se llama con 2 argumentos. Llamar en
print_help()
cualquier momentoargc != 3
es mucho más sencillo.
Supongo que la 'pregunta' está pidiendo críticas de código. Si me equivoco en eso, por favor sea gentil... LOL
Mi primera impresión es la legibilidad de main. El gran interruptor de análisis de argumentos oculta la lógica más importante.
Tal vez hacer que sea más fácil de leer (¿objetos de cadena y comparaciones que no distinguen entre mayúsculas y minúsculas?). Aún mejor: relegarlo a un método auxiliar de limpieza.
(Además: a veces , lo simplista está bien. Si no recibe dos argumentos de nombre de archivo, puede escupir inmediatamente la ayuda, en lugar de decirle al usuario que pida ayuda explícitamente).