You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为自制Linux mv命令程序添加源为目录的移动功能

Extending Your mv Implementation to Support Directory Moves

Great question! Let's break down how to update your custom mv program to handle directory moves, matching the behavior of the native Linux mv command. We'll cover core concepts, necessary function additions, and adjustments to your existing logic.

Key Behavior to Replicate

First, remember how the real mv handles directories:

  1. Same filesystem: Uses rename() (atomic, fast operation) to rename/move the directory.
  2. Cross-filesystem: Recursively copies the directory and its contents to the target, then deletes the original.
  3. Target exists:
    • If target is a directory: Moves the source directory into the target directory as a subdirectory.
    • If target is a file: Errors out (can't move a directory into a file).
  4. Multiple sources: All sources (files/directories) are moved into the target directory.

Step 1: Add Required Headers

First, add these headers to handle directory traversal and system limits:

#include <dirent.h>   // For directory reading
#include <limits.h>   // For PATH_MAX
#include <errno.h>    // For error handling with perror()
#include <fcntl.h>    // For file descriptor operations

Step 2: Replace the movefile Function with a Universal move_item

The original link()+remove() approach doesn't work for directories. Instead, we'll use rename() first (best for same-FS moves), then fall back to copy-delete for cross-FS scenarios:

int move_item(const char* src, const char* dest) {
    // Try rename() first - works for files/dirs on the same filesystem (atomic)
    if (rename(src, dest) == 0) {
        return 0;
    }

    // If rename fails, check what type of source we're dealing with
    struct stat src_stat;
    if (stat(src, &src_stat) == -1) {
        perror("Failed to stat source");
        return 1;
    }

    if (S_ISREG(src_stat.st_mode)) {
        // Fall back to link+remove for regular files (cross-FS)
        if (link(src, dest) == -1) {
            perror("Failed to create hard link");
            return 1;
        }
        if (remove(src) == -1) {
            perror("Failed to delete original file");
            remove(dest); // Clean up the duplicate
            return 1;
        }
        return 0;
    } else if (S_ISDIR(src_stat.st_mode)) {
        // Cross-FS directory move: copy then delete
        if (copy_directory(src, dest) == -1) {
            perror("Failed to copy directory");
            return 1;
        }
        if (delete_directory(src) == -1) {
            perror("Failed to delete original directory");
            delete_directory(dest); // Clean up the copied directory
            return 1;
        }
        return 0;
    } else {
        fprintf(stderr, "Unsupported file type: %s\n", src);
        return 1;
    }
}

Step 3: Add Recursive Directory Copy/Delete Helpers

We need functions to copy directories recursively and delete them (including contents):

Recursive Directory Copy

// Helper to copy a regular file with permissions
int copy_file(const char* src, const char* dest) {
    FILE* src_fp = fopen(src, "rb");
    if (!src_fp) return -1;

    FILE* dest_fp = fopen(dest, "wb");
    if (!dest_fp) {
        fclose(src_fp);
        return -1;
    }

    char buffer[BUFSIZ];
    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_fp)) > 0) {
        if (fwrite(buffer, 1, bytes_read, dest_fp) != bytes_read) {
            fclose(src_fp);
            fclose(dest_fp);
            remove(dest);
            return -1;
        }
    }

    // Preserve file permissions
    struct stat src_stat;
    if (stat(src, &src_stat) == 0) {
        fchmod(fileno(dest_fp), src_stat.st_mode);
    }

    fclose(src_fp);
    fclose(dest_fp);
    return 0;
}

// Recursively copy a directory and its contents
int copy_directory(const char* src_dir, const char* dest_dir) {
    struct stat dir_stat;
    if (stat(src_dir, &dir_stat) == -1) return -1;

    // Create destination directory with matching permissions
    if (mkdir(dest_dir, dir_stat.st_mode) == -1) return -1;

    DIR* dir = opendir(src_dir);
    if (!dir) return -1;

    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        // Skip . and .. to avoid infinite recursion
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        char src_path[PATH_MAX];
        char dest_path[PATH_MAX];
        snprintf(src_path, sizeof(src_path), "%s/%s", src_dir, entry->d_name);
        snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, entry->d_name);

        struct stat entry_stat;
        if (stat(src_path, &entry_stat) == -1) {
            closedir(dir);
            return -1;
        }

        if (S_ISDIR(entry_stat.st_mode)) {
            if (copy_directory(src_path, dest_path) == -1) {
                closedir(dir);
                return -1;
            }
        } else if (S_ISREG(entry_stat.st_mode)) {
            if (copy_file(src_path, dest_path) == -1) {
                closedir(dir);
                return -1;
            }
        }
        // Add support for symlinks/other types here if needed
    }

    closedir(dir);
    return 0;
}

Recursive Directory Delete

// Recursively delete a directory and all its contents
int delete_directory(const char* dir) {
    DIR* d = opendir(dir);
    if (!d) return -1;

    struct dirent* entry;
    while ((entry = readdir(d)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        char path[PATH_MAX];
        snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name);

        struct stat entry_stat;
        if (stat(path, &entry_stat) == -1) {
            closedir(d);
            return -1;
        }

        if (S_ISDIR(entry_stat.st_mode)) {
            if (delete_directory(path) == -1) {
                closedir(d);
                return -1;
            }
        } else {
            if (remove(path) == -1) {
                closedir(d);
                return -1;
            }
        }
    }

    closedir(d);
    // Delete now-empty directory
    return rmdir(dir);
}

Step 4: Adjust Existing Helper Functions

Fix Path Handling

Update removepath to be const-safe and changefilenameindir to avoid buffer overflow:

const char* removepath(const char* filename) {
    const char* p = filename;
    for (size_t i = 0; i < strlen(filename); i++) {
        if (filename[i] == '/')
            p = filename + i + 1;
    }
    return p;
}

char* changefilenameindir(const char* filename, const char* dir) {
    const char* base = removepath(filename);
    size_t full_len = strlen(dir) + 1 + strlen(base) + 1; // +1 for /, +1 for null terminator
    char* fullname = malloc(full_len);
    if (!fullname) {
        perror("malloc failed");
        return NULL;
    }
    snprintf(fullname, full_len, "%s/%s", dir, base);
    return fullname;
}

Simplify Source Existence Check

The original sourceexists function was checking write permissions unnecessarily. Update it to only verify sources exist:

int sourceexists(int argc, char** argv) {
    for (int i = 1; i < argc - 1; i++) {
        if (access(argv[i], F_OK)) {
            fileerror(1, argv[i]);
            return 1;
        }
    }
    return 0;
}

Step 5: Update Main Logic to Support Directories

Single Source/Destination Case

Modify the argc == 3 branch to handle directories:

if (argc == 3) {
    int ret = destinationwriteable(argv[2]);
    if (ret == 2) {
        if (regularfile(argv[2])) {
            // Handle file override (only if source is a file)
            if (isdirectory(argv[1])) {
                fprintf(stderr, "Cannot move directory %s to regular file %s\n", argv[1], argv[2]);
                return 1;
            }
            char* perm = permissions(argv[2]);
            printf("Replace and override mode (%s) y/n?: ", perm);
            free(perm);
            char x;
            do {
                x = getchar();
            } while (x != 'y' && x != 'n');
            if (x == 'n')
                return 1;
            remove(argv[2]);
            return move_item(argv[1], argv[2]);
        }
        fprintf(stderr, "Cannot move to %s: Permission denied\n", argv[2]);
        return 1;
    } else if (ret == 1) {
        // Destination doesn't exist - move directly
        return move_item(argv[1], argv[2]);
    } else {
        if (regularfile(argv[2])) {
            // Can't move directory to file
            if (isdirectory(argv[1])) {
                fprintf(stderr, "Cannot move directory %s to regular file %s\n", argv[1], argv[2]);
                return 1;
            }
            // Handle file override
            struct stat infof1, infof2;
            stat(argv[1], &infof1);
            stat(argv[2], &infof2);
            if (infof1.st_ino != infof2.st_ino)
                remove(argv[2]);
            return move_item(argv[1], argv[2]);
        } else if (isdirectory(argv[2])) {
            // Move source into destination directory
            char* fullname = changefilenameindir(argv[1], argv[2]);
            ret = move_item(argv[1], fullname);
            free(fullname);
            return ret;
        }
    }
}

Multiple Sources Case

Remove the block that blocks directory sources, and process all sources (files/dirs):

} else {
    if (regularfile(argv[argc - 1])) {
        fprintf(stderr, "Destination %s cannot be a regular file when moving multiple items\n", argv[argc - 1]);
        return 1;
    }
    int ret = destinationwriteable(argv[argc - 1]);
    if (ret == 2) {
        fprintf(stderr, "Cannot move to %s: Permission denied\n", argv[argc - 1]);
        return 1;
    } else if (ret == 1) {
        fprintf(stderr, "Destination %s must be an existing directory\n", argv[argc - 1]);
        return 1;
    }

    // Process all sources (files and directories)
    for (int i = 1; i < argc - 1; i++) {
        char* fullname = changefilenameindir(argv[i], argv[argc - 1]);
        if (!fullname) return 1;
        ret = move_item(argv[i], fullname);
        free(fullname);
        if (ret) return ret;
    }
}

Final Notes

  • Error Handling: We used perror() for more accurate system error messages, which is better than custom messages for debugging.
  • Edge Cases: You can add support for symlinks, device files, etc., by extending the copy_directory and move_item functions.
  • Memory Safety: Ensure all malloced pointers are freed to avoid leaks (we kept your existing free logic and added new ones where needed).

内容的提问来源于stack exchange,提问作者Mike

火山引擎 最新活动