如何为自制Linux mv命令程序添加源为目录的移动功能
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:
- Same filesystem: Uses
rename()(atomic, fast operation) to rename/move the directory. - Cross-filesystem: Recursively copies the directory and its contents to the target, then deletes the original.
- 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).
- 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_directoryandmove_itemfunctions. - Memory Safety: Ensure all
malloced pointers arefreed to avoid leaks (we kept your existing free logic and added new ones where needed).
内容的提问来源于stack exchange,提问作者Mike




