├── LICENSE ├── CHANGELOG.md ├── TEST_README.md ├── AGENTS.md ├── install.sh ├── .github └── agents │ └── implementation-plan.agent.md ├── README.md ├── test-better-rm.sh └── better-rm /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Will 保哥 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.1.0] - 2025-12-09 9 | 10 | ### Added 11 | - Timestamp and content hash appended to trashed filenames for better tracking and deduplication 12 | - Filename format in trash: `filename__YYYYMMDD_HHMMSS_NNNNNNNNN__hash` 13 | - MD5 hash calculation for file content (with SHA256 fallback) 14 | - Directory hash calculation based on all contained files 15 | - Nanosecond-precision timestamps to prevent filename collisions during rapid deletions 16 | - Deletion log file (`.deletion_log`) in TRASH_DIR that records all deletion operations 17 | - Logs timestamp, original path, trash path, hash, and file type for each deletion 18 | - Format: `TIMESTAMP | ORIGINAL_PATH | TRASH_PATH | HASH | FILE_TYPE` 19 | - Comprehensive test script (`test-better-rm.sh`) for validating all features 20 | - 28 test cases covering all functionality 21 | - Container-compatible for CI/CD integration 22 | - Detailed test documentation in TEST_README.md 23 | 24 | ### Changed 25 | - Trashed files now always include timestamp and hash suffix (previously only added on conflicts) 26 | - Improved directory hash calculation with secure handling of special characters 27 | 28 | ### Security 29 | - Use `find -print0`, `sort -z`, and `xargs -0 -r` to safely handle filenames with special characters 30 | - Prevent filename injection attacks when calculating directory hashes 31 | 32 | ### Fixed 33 | - Empty directory hash calculation now works correctly 34 | - Special characters in filenames are handled safely during hash calculation 35 | 36 | ## [1.0.0] - 2023-12-09 37 | 38 | ### Added 39 | - Initial release of better-rm 40 | - Safe file deletion by moving files to trash instead of permanent deletion 41 | - Protected directory list to prevent accidental deletion of critical system directories 42 | - Preserve original directory structure in trash 43 | - Support for all common `rm` parameters (`-r`, `-f`, `-i`, `-v`, etc.) 44 | - Customizable trash directory via `TRASH_DIR` environment variable 45 | - Colored output for better user experience 46 | - Protection for important directories (system, user home, Git repositories) 47 | 48 | ### Features 49 | - Move files to `~/.Trash` instead of permanent deletion 50 | - Maintain full path structure in trash for easy recovery 51 | - Timestamp-based conflict resolution (legacy behavior, replaced in 1.1.0) 52 | - Interactive and force modes 53 | - Verbose output option 54 | - Compatible with standard `rm` command syntax 55 | -------------------------------------------------------------------------------- /TEST_README.md: -------------------------------------------------------------------------------- 1 | # Better-RM 測試腳本說明 2 | # Better-RM Test Script Documentation 3 | 4 | ## 概述 (Overview) 5 | 6 | `test-better-rm.sh` 是一個完整的測試腳本,用於驗證 better-rm 的所有功能與特性。 7 | 8 | ## 使用方式 (Usage) 9 | 10 | ### 基本執行 (Basic Execution) 11 | 12 | ```bash 13 | ./test-better-rm.sh 14 | ``` 15 | 16 | ### 在容器環境中執行 (Run in Container Environment) 17 | 18 | ```bash 19 | # Docker 20 | docker run -v $(pwd):/app ubuntu:latest bash /app/test-better-rm.sh 21 | 22 | # Podman 23 | podman run -v $(pwd):/app:z ubuntu:latest bash /app/test-better-rm.sh 24 | ``` 25 | 26 | ## 測試項目 (Test Items) 27 | 28 | ### 測試 1: 版本與說明資訊 29 | - 測試 `--version` 參數 30 | - 測試 `--help` 參數 31 | 32 | ### 測試 2: 基本檔案刪除功能 33 | - 刪除單一檔案 34 | - 刪除多個檔案 35 | 36 | ### 測試 3: 目錄刪除功能 37 | - 遞迴刪除目錄 (-r) 38 | - 刪除空目錄 39 | - 不加 -r 刪除目錄應失敗 40 | 41 | ### 測試 4: 特殊字元檔名處理 42 | - 檔名含空格 43 | - 檔名含特殊字元 44 | - 中文檔名 45 | 46 | ### 測試 5: 符號連結處理 47 | - 刪除符號連結(目標檔案應保留) 48 | 49 | ### 測試 6: 時間戳記與內容 Hash 50 | - 檔名包含時間戳記和 Hash 51 | - 相同檔名但不同內容產生不同 Hash 52 | - 空檔案的 Hash 53 | 54 | ### 測試 7: 刪除日誌功能 55 | - 日誌檔案自動創建 56 | - 日誌記錄檔案刪除 57 | - 日誌記錄目錄刪除 58 | - 日誌記錄符號連結 59 | - 日誌格式正確 60 | 61 | ### 測試 8: 命令參數選項 62 | - 詳細模式 (-v) 63 | - 強制模式 (-f) 64 | - 組合參數 (-rf) 65 | 66 | ### 測試 9: 受保護目錄 67 | - 拒絕刪除根目錄 (/) 68 | - 拒絕刪除 /home 69 | - 拒絕刪除 .git 目錄 70 | 71 | ### 測試 10: 快速連續刪除 72 | - 測試奈秒時間戳記避免檔名衝突 73 | 74 | ### 測試 11: 垃圾桶路徑結構保留 75 | - 深層目錄結構保留 76 | 77 | ### 測試 12: 自訂垃圾桶目錄 78 | - 使用自訂 TRASH_DIR 環境變數 79 | 80 | ## 測試結果 (Test Results) 81 | 82 | 測試腳本會顯示: 83 | - 總測試數 84 | - 通過測試數 85 | - 失敗測試數 86 | 87 | 如果所有測試通過,返回 exit code 0;否則返回 exit code 1。 88 | 89 | ## 環境需求 (Requirements) 90 | 91 | - Bash 4.0+ 92 | - 基本 Unix 工具(find, grep, awk, wc 等) 93 | - md5sum 或 sha256sum 94 | 95 | ## 測試目錄 (Test Directories) 96 | 97 | 測試腳本會使用以下臨時目錄: 98 | - `/tmp/better-rm-test-trash` - 測試用垃圾桶目錄 99 | - `/tmp/better-rm-test-work` - 測試工作目錄 100 | 101 | 測試完成後會自動清理。 102 | 103 | ## 故障排除 (Troubleshooting) 104 | 105 | ### 測試失敗 106 | 如果某個測試失敗,檢查: 107 | 1. better-rm 腳本是否有執行權限 108 | 2. 是否有足夠的磁碟空間 109 | 3. 是否有必要的系統工具(md5sum, find 等) 110 | 111 | ### 容器環境注意事項 112 | 在容器中執行時: 113 | 1. 確保掛載的目錄有執行權限 114 | 2. 容器需要有 bash 環境 115 | 3. 需要安裝 coreutils 套件 116 | 117 | ## 範例輸出 (Example Output) 118 | 119 | ``` 120 | Better-RM 完整測試套件 121 | Better-RM Comprehensive Test Suite 122 | 123 | 測試腳本: /path/to/better-rm 124 | 垃圾桶目錄: /tmp/better-rm-test-trash 125 | 工作目錄: /tmp/better-rm-test-work 126 | 127 | ======================================== 128 | 測試 1: 版本與說明資訊 129 | ======================================== 130 | [測試 0] 測試 --version 參數 131 | ✓ 通過: --version 顯示正確版本 132 | ... 133 | 134 | ======================================== 135 | 測試結果統計 (Test Results Summary) 136 | ======================================== 137 | 138 | 總測試數 (Total Tests): 28 139 | 通過測試 (Passed): 28 140 | 失敗測試 (Failed): 0 141 | 142 | ✓ 所有測試通過!(All tests passed!) 143 | ``` 144 | 145 | ## 貢獻 (Contributing) 146 | 147 | 如需添加新的測試項目: 148 | 1. 在適當的測試區塊中添加新的測試函數 149 | 2. 使用 `test_item` 描述測試項目 150 | 3. 使用 `test_pass` 或 `test_fail` 記錄結果 151 | 4. 確保測試結束後清理所有臨時檔案 152 | 153 | ## 授權 (License) 154 | 155 | 與 better-rm 專案相同的授權條款。 156 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Agents 2 | 3 | This document describes the AI agents and automation used in the development of better-rm. 4 | 5 | ## Overview 6 | 7 | This project uses GitHub Copilot and AI-assisted development to maintain code quality, implement features, and address issues efficiently. 8 | 9 | ## Development Agents 10 | 11 | ### Code Review Agent 12 | - **Purpose**: Automated code review for pull requests 13 | - **Scope**: Reviews all code changes for security, performance, and maintainability issues 14 | - **Integration**: Runs automatically on PR commits 15 | - **Feedback**: Provides actionable comments on the code 16 | 17 | ### Security Agent (CodeQL) 18 | - **Purpose**: Static security analysis 19 | - **Scope**: Scans for security vulnerabilities in code changes 20 | - **Integration**: Runs before finalizing changes 21 | - **Note**: Currently limited support for Bash scripts, but best practices are followed 22 | 23 | ### Testing Agent 24 | - **Purpose**: Automated testing of functionality 25 | - **Scope**: Validates file operations, hash calculations, timestamp generation, and edge cases 26 | - **Coverage**: 27 | - Regular files with various content types 28 | - Empty files and directories 29 | - Files with special characters 30 | - Rapid deletion scenarios 31 | - Symlinks 32 | - Force mode operations 33 | 34 | ## Future Development Guidelines 35 | 36 | ### When Adding New Features 37 | 38 | 1. **Security First**: Always consider security implications 39 | - Use secure command patterns (`-print0`, `-0`, etc.) 40 | - Validate and sanitize all inputs 41 | - Protect against injection attacks 42 | 43 | 2. **Maintain Compatibility**: Ensure backward compatibility with existing trash structure 44 | - New features should not break existing trashed files 45 | - Consider migration paths for format changes 46 | 47 | 3. **Test Thoroughly**: Test all edge cases 48 | - Empty inputs 49 | - Special characters in filenames 50 | - Large directories 51 | - Rapid operations 52 | - Error conditions 53 | 54 | 4. **Document Changes**: Update all relevant documentation 55 | - CHANGELOG.md for user-facing changes 56 | - README.md for feature descriptions 57 | - Code comments for complex logic 58 | - This AGENTS.md for development process changes 59 | 60 | ### Code Style Guidelines 61 | 62 | 1. **Comments**: Use bilingual comments (Chinese + English) for consistency 63 | 2. **Error Handling**: Always provide clear error messages in both languages 64 | 3. **Color Output**: Use colored output for better UX (red for errors, green for success, yellow for warnings) 65 | 4. **Shell Best Practices**: 66 | - Quote all variables 67 | - Use `local` for function variables 68 | - Handle edge cases (empty strings, special characters) 69 | - Use `2>/dev/null` to suppress expected errors 70 | 71 | ### Performance Considerations 72 | 73 | 1. **Hash Calculation**: 74 | - Be mindful of large directory operations 75 | - Current implementation may be slow for directories with many files 76 | - Consider optimization for production use 77 | 78 | 2. **Timestamp Precision**: Nanosecond precision is sufficient for collision prevention 79 | 80 | 3. **Fallback Mechanisms**: Always provide fallbacks (e.g., md5sum → sha256sum → "nohash") 81 | 82 | ## Suggested Future Enhancements 83 | 84 | ### High Priority 85 | - [ ] Implement restore functionality (`rm --restore`) 86 | - [ ] Add trash management commands (list, clean, empty) 87 | - [ ] Automatic cleanup of old trashed files 88 | - [ ] Configuration file support (~/.better-rm.conf) 89 | 90 | ### Medium Priority 91 | - [ ] Enhanced hash calculation for large directories 92 | - [ ] Trash statistics and reporting 93 | - [ ] Integration with file managers 94 | - [ ] Support for network filesystems 95 | 96 | ### Low Priority 97 | - [ ] GUI for trash management 98 | - [ ] Scheduled trash cleanup 99 | - [ ] Compression of old trashed files 100 | - [ ] Cloud backup integration 101 | 102 | ## Testing New Features 103 | 104 | When implementing new features, ensure comprehensive testing: 105 | 106 | ```bash 107 | # Create test environment 108 | mkdir -p /tmp/test-better-rm 109 | cd /tmp/test-better-rm 110 | TRASH_DIR=/tmp/test-trash 111 | 112 | # Test scenarios 113 | # 1. Regular files 114 | echo "content" > file.txt 115 | better-rm -v file.txt 116 | 117 | # 2. Empty files/directories 118 | touch empty.txt 119 | mkdir emptydir 120 | better-rm -v empty.txt 121 | better-rm -rv emptydir 122 | 123 | # 3. Special characters 124 | touch "file with spaces.txt" 125 | better-rm -v "file with spaces.txt" 126 | 127 | # 4. Rapid deletions 128 | for i in {1..10}; do echo "$i" > "file$i.txt"; done 129 | better-rm -v file*.txt 130 | 131 | # 5. Large directories 132 | mkdir largedir 133 | for i in {1..1000}; do echo "content $i" > "largedir/file$i.txt"; done 134 | better-rm -rv largedir 135 | 136 | # Clean up 137 | rm -rf /tmp/test-better-rm /tmp/test-trash 138 | ``` 139 | 140 | ## Contributing 141 | 142 | When contributing to this project: 143 | 144 | 1. Follow the existing code style and patterns 145 | 2. Add tests for new functionality 146 | 3. Update CHANGELOG.md with your changes 147 | 4. Ensure all automated checks pass 148 | 5. Request review from maintainers 149 | 150 | ## Contact 151 | 152 | For questions about the development process or AI agent configurations, please open an issue on GitHub. 153 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # better-rm 安裝腳本 / Installation script for better-rm 4 | # 5 | # 用法 (Usage): 6 | # curl -sSL https://raw.githubusercontent.com/doggy8088/better-rm/main/install.sh | bash 7 | # 或 (or): 8 | # wget -qO- https://raw.githubusercontent.com/doggy8088/better-rm/main/install.sh | bash 9 | # 10 | 11 | set -e # 遇到錯誤時立即退出 / Exit on error 12 | 13 | # 顏色定義 (Color definitions) 14 | RED='\033[0;31m' 15 | GREEN='\033[0;32m' 16 | YELLOW='\033[1;33m' 17 | BLUE='\033[0;34m' 18 | NC='\033[0m' # No Color 19 | 20 | # 安裝目錄 (Installation directory) 21 | INSTALL_DIR="$HOME/.better-rm" 22 | REPO_URL="https://github.com/doggy8088/better-rm.git" 23 | 24 | # 輸出函式 (Output functions) 25 | info() { 26 | echo -e "${BLUE}ℹ ${NC}$1" 27 | } 28 | 29 | success() { 30 | echo -e "${GREEN}✓${NC} $1" 31 | } 32 | 33 | error() { 34 | echo -e "${RED}✗${NC} $1" >&2 35 | } 36 | 37 | warning() { 38 | echo -e "${YELLOW}⚠${NC} $1" 39 | } 40 | 41 | # 檢查命令是否存在 (Check if command exists) 42 | command_exists() { 43 | command -v "$1" >/dev/null 2>&1 44 | } 45 | 46 | # 偵測使用者的 shell 47 | # Detect user's shell 48 | detect_shell() { 49 | local shell_name=$(basename "$SHELL") 50 | local shell_config="" 51 | 52 | case "$shell_name" in 53 | bash) 54 | if [ -f "$HOME/.bashrc" ]; then 55 | shell_config="$HOME/.bashrc" 56 | elif [ -f "$HOME/.bash_profile" ]; then 57 | shell_config="$HOME/.bash_profile" 58 | fi 59 | ;; 60 | zsh) 61 | if [ -f "$HOME/.zshrc" ]; then 62 | shell_config="$HOME/.zshrc" 63 | fi 64 | ;; 65 | *) 66 | # 嘗試常見的 shell 設定檔 67 | # Try common shell config files 68 | if [ -f "$HOME/.bashrc" ]; then 69 | shell_config="$HOME/.bashrc" 70 | elif [ -f "$HOME/.zshrc" ]; then 71 | shell_config="$HOME/.zshrc" 72 | elif [ -f "$HOME/.bash_profile" ]; then 73 | shell_config="$HOME/.bash_profile" 74 | fi 75 | ;; 76 | esac 77 | 78 | echo "$shell_config" 79 | } 80 | 81 | # 主要安裝函式 (Main installation function) 82 | install() { 83 | echo "" 84 | echo "================================================" 85 | echo " better-rm 安裝程式 / Installation Script" 86 | echo "================================================" 87 | echo "" 88 | 89 | # 檢查 git 是否安裝 (Check if git is installed) 90 | if ! command_exists git; then 91 | error "錯誤:找不到 git 命令,請先安裝 git" 92 | error "Error: git command not found, please install git first" 93 | exit 1 94 | fi 95 | 96 | # 檢查安裝目錄是否已存在 (Check if installation directory exists) 97 | if [ -d "$INSTALL_DIR" ]; then 98 | info "發現現有安裝,正在更新... / Found existing installation, updating..." 99 | cd "$INSTALL_DIR" 100 | # 檢查是否為有效的 git 儲存庫 (Check if it's a valid git repository) 101 | if ! git status >/dev/null 2>&1; then 102 | error "錯誤:$INSTALL_DIR 存在但不是有效的 git 儲存庫" 103 | error "Error: $INSTALL_DIR exists but is not a valid git repository" 104 | error "請手動刪除該目錄後重試:rm -rf $INSTALL_DIR" 105 | error "Please manually remove the directory and try again: rm -rf $INSTALL_DIR" 106 | exit 1 107 | fi 108 | if git pull --quiet; then 109 | success "更新成功 / Updated successfully" 110 | else 111 | error "更新失敗 / Update failed" 112 | exit 1 113 | fi 114 | else 115 | info "正在下載 better-rm... / Downloading better-rm..." 116 | if git clone --quiet "$REPO_URL" "$INSTALL_DIR"; then 117 | success "下載完成 / Downloaded successfully" 118 | else 119 | error "下載失敗 / Download failed" 120 | exit 1 121 | fi 122 | fi 123 | 124 | # 設定執行權限 (Set executable permission) 125 | info "設定執行權限... / Setting executable permission..." 126 | chmod +x "$INSTALL_DIR/better-rm" 127 | success "權限設定完成 / Permission set" 128 | 129 | # 偵測 shell 設定檔 (Detect shell config file) 130 | local shell_config=$(detect_shell) 131 | 132 | if [ -z "$shell_config" ]; then 133 | warning "無法自動偵測 shell 設定檔 / Could not auto-detect shell config file" 134 | echo "" 135 | echo "請手動將以下內容加入您的 shell 設定檔:" 136 | echo "Please manually add the following to your shell config file:" 137 | echo "" 138 | echo " alias rm='$INSTALL_DIR/better-rm'" 139 | echo "" 140 | exit 0 141 | fi 142 | 143 | # 檢查別名是否已存在 (Check if alias already exists) 144 | local alias_line="alias rm='$INSTALL_DIR/better-rm'" 145 | if grep -q "$alias_line" "$shell_config" 2>/dev/null; then 146 | info "別名已存在於 $shell_config / Alias already exists in $shell_config" 147 | else 148 | info "正在設定別名... / Setting up alias..." 149 | echo "" >> "$shell_config" 150 | echo "# better-rm: 更安全的 rm 命令 / A safer rm command" >> "$shell_config" 151 | echo "$alias_line" >> "$shell_config" 152 | success "別名已加入 $shell_config / Alias added to $shell_config" 153 | fi 154 | 155 | echo "" 156 | echo "================================================" 157 | echo -e " ${GREEN}安裝完成! / Installation Complete!${NC}" 158 | echo "================================================" 159 | echo "" 160 | echo -e "${GREEN}✓${NC} better-rm 已安裝完成並加入到 shell 設定檔" 161 | echo -e "${GREEN}✓${NC} better-rm installed and added to shell config" 162 | echo "" 163 | echo -e "${YELLOW}請執行以下命令立即啟用 better-rm (不需要重啟終端機):${NC}" 164 | echo -e "${YELLOW}Run this command to activate better-rm immediately (no restart needed):${NC}" 165 | echo "" 166 | echo -e " ${GREEN}alias rm='$INSTALL_DIR/better-rm'${NC}" 167 | echo "" 168 | echo "或者重新開啟終端機 / Or open a new terminal" 169 | echo "" 170 | echo "================================================" 171 | echo "" 172 | echo "驗證與使用 (Verification and Usage):" 173 | echo "" 174 | echo "1. 驗證安裝 / Verify installation:" 175 | echo -e " ${BLUE}rm --version${NC}" 176 | echo " 應該顯示 'better-rm 1.0.0'" 177 | echo " Should display 'better-rm 1.0.0'" 178 | echo "" 179 | echo "2. 查看使用說明 / View usage help:" 180 | echo -e " ${BLUE}rm --help${NC}" 181 | echo "" 182 | echo "提示 (Tips):" 183 | echo " • 檔案會被移至 ~/.Trash 而非永久刪除" 184 | echo " Files will be moved to ~/.Trash instead of permanent deletion" 185 | echo " • 如需使用原生 rm: /bin/rm 或 \\rm (反斜線可繞過別名)" 186 | echo " To use native rm: /bin/rm or \\rm (backslash bypasses alias)" 187 | echo "" 188 | } 189 | 190 | # 執行安裝 (Execute installation) 191 | install 192 | -------------------------------------------------------------------------------- /.github/agents/implementation-plan.agent.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Generate an implementation plan for new features or refactoring existing code." 3 | name: "Implementation Plan Generation Mode" 4 | tools: ["codebase", "usages", "vscodeAPI", "think", "problems", "changes", "testFailure", "terminalSelection", "terminalLastCommand", "openSimpleBrowser", "fetch", "findTestFiles", "searchResults", "githubRepo", "extensions", "edit/editFiles", "runNotebooks", "search", "new", "runCommands", "runTasks"] 5 | --- 6 | 7 | # Implementation Plan Generation Mode 8 | 9 | ## Primary Directive 10 | 11 | You are an AI agent operating in planning mode. Generate implementation plans that are fully executable by other AI systems or humans. 12 | 13 | ## Execution Context 14 | 15 | This mode is designed for AI-to-AI communication and automated processing. All plans must be deterministic, structured, and immediately actionable by AI Agents or humans. 16 | 17 | ## Core Requirements 18 | 19 | - Generate implementation plans that are fully executable by AI agents or humans 20 | - Use deterministic language with zero ambiguity 21 | - Structure all content for automated parsing and execution 22 | - Ensure complete self-containment with no external dependencies for understanding 23 | - DO NOT make any code edits - only generate structured plans 24 | 25 | ## Plan Structure Requirements 26 | 27 | Plans must consist of discrete, atomic phases containing executable tasks. Each phase must be independently processable by AI agents or humans without cross-phase dependencies unless explicitly declared. 28 | 29 | ## Phase Architecture 30 | 31 | - Each phase must have measurable completion criteria 32 | - Tasks within phases must be executable in parallel unless dependencies are specified 33 | - All task descriptions must include specific file paths, function names, and exact implementation details 34 | - No task should require human interpretation or decision-making 35 | 36 | ## AI-Optimized Implementation Standards 37 | 38 | - Use explicit, unambiguous language with zero interpretation required 39 | - Structure all content as machine-parseable formats (tables, lists, structured data) 40 | - Include specific file paths, line numbers, and exact code references where applicable 41 | - Define all variables, constants, and configuration values explicitly 42 | - Provide complete context within each task description 43 | - Use standardized prefixes for all identifiers (REQ-, TASK-, etc.) 44 | - Include validation criteria that can be automatically verified 45 | 46 | ## Output File Specifications 47 | 48 | When creating plan files: 49 | 50 | - Save implementation plan files in `/plan/` directory 51 | - Use naming convention: `[purpose]-[component]-[version].md` 52 | - Purpose prefixes: `upgrade|refactor|feature|data|infrastructure|process|architecture|design` 53 | - Example: `upgrade-system-command-4.md`, `feature-auth-module-1.md` 54 | - File must be valid Markdown with proper front matter structure 55 | 56 | ## Mandatory Template Structure 57 | 58 | All implementation plans must strictly adhere to the following template. Each section is required and must be populated with specific, actionable content. AI agents must validate template compliance before execution. 59 | 60 | ## Template Validation Rules 61 | 62 | - All front matter fields must be present and properly formatted 63 | - All section headers must match exactly (case-sensitive) 64 | - All identifier prefixes must follow the specified format 65 | - Tables must include all required columns with specific task details 66 | - No placeholder text may remain in the final output 67 | 68 | ## Status 69 | 70 | The status of the implementation plan must be clearly defined in the front matter and must reflect the current state of the plan. The status can be one of the following (status_color in brackets): `Completed` (bright green badge), `In progress` (yellow badge), `Planned` (blue badge), `Deprecated` (red badge), or `On Hold` (orange badge). It should also be displayed as a badge in the introduction section. 71 | 72 | ```md 73 | --- 74 | goal: [Concise Title Describing the Package Implementation Plan's Goal] 75 | version: [Optional: e.g., 1.0, Date] 76 | date_created: [YYYY-MM-DD] 77 | last_updated: [Optional: YYYY-MM-DD] 78 | owner: [Optional: Team/Individual responsible for this spec] 79 | status: 'Completed'|'In progress'|'Planned'|'Deprecated'|'On Hold' 80 | tags: [Optional: List of relevant tags or categories, e.g., `feature`, `upgrade`, `chore`, `architecture`, `migration`, `bug` etc] 81 | --- 82 | 83 | # Introduction 84 | 85 | ![Status: ](https://img.shields.io/badge/status--) 86 | 87 | [A short concise introduction to the plan and the goal it is intended to achieve.] 88 | 89 | ## 1. Requirements & Constraints 90 | 91 | [Explicitly list all requirements & constraints that affect the plan and constrain how it is implemented. Use bullet points or tables for clarity.] 92 | 93 | - **REQ-001**: Requirement 1 94 | - **SEC-001**: Security Requirement 1 95 | - **[3 LETTERS]-001**: Other Requirement 1 96 | - **CON-001**: Constraint 1 97 | - **GUD-001**: Guideline 1 98 | - **PAT-001**: Pattern to follow 1 99 | 100 | ## 2. Implementation Steps 101 | 102 | ### Implementation Phase 1 103 | 104 | - GOAL-001: [Describe the goal of this phase, e.g., "Implement feature X", "Refactor module Y", etc.] 105 | 106 | | Task | Description | Completed | Date | 107 | | -------- | --------------------- | --------- | ---------- | 108 | | TASK-001 | Description of task 1 | ✅ | 2025-04-25 | 109 | | TASK-002 | Description of task 2 | | | 110 | | TASK-003 | Description of task 3 | | | 111 | 112 | ### Implementation Phase 2 113 | 114 | - GOAL-002: [Describe the goal of this phase, e.g., "Implement feature X", "Refactor module Y", etc.] 115 | 116 | | Task | Description | Completed | Date | 117 | | -------- | --------------------- | --------- | ---- | 118 | | TASK-004 | Description of task 4 | | | 119 | | TASK-005 | Description of task 5 | | | 120 | | TASK-006 | Description of task 6 | | | 121 | 122 | ## 3. Alternatives 123 | 124 | [A bullet point list of any alternative approaches that were considered and why they were not chosen. This helps to provide context and rationale for the chosen approach.] 125 | 126 | - **ALT-001**: Alternative approach 1 127 | - **ALT-002**: Alternative approach 2 128 | 129 | ## 4. Dependencies 130 | 131 | [List any dependencies that need to be addressed, such as libraries, frameworks, or other components that the plan relies on.] 132 | 133 | - **DEP-001**: Dependency 1 134 | - **DEP-002**: Dependency 2 135 | 136 | ## 5. Files 137 | 138 | [List the files that will be affected by the feature or refactoring task.] 139 | 140 | - **FILE-001**: Description of file 1 141 | - **FILE-002**: Description of file 2 142 | 143 | ## 6. Testing 144 | 145 | [List the tests that need to be implemented to verify the feature or refactoring task.] 146 | 147 | - **TEST-001**: Description of test 1 148 | - **TEST-002**: Description of test 2 149 | 150 | ## 7. Risks & Assumptions 151 | 152 | [List any risks or assumptions related to the implementation of the plan.] 153 | 154 | - **RISK-001**: Risk 1 155 | - **ASSUMPTION-001**: Assumption 1 156 | 157 | ## 8. Related Specifications / Further Reading 158 | 159 | [Link to related spec 1] 160 | [Link to relevant external documentation] 161 | ``` 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # better-rm 2 | 3 | 給你一個更好、更安全的 `rm` 命令 4 | 5 | ## 專案簡介 6 | 7 | `better-rm` 是一個 Linux/Unix 下的 `rm` 命令替代方案,主要目的是防止誤刪重要檔案與目錄。與傳統的 `rm` 命令不同,`better-rm` 不會永久刪除檔案,而是將檔案移至垃圾桶目錄,讓你有機會救回誤刪的檔案。 8 | 9 | ### 主要特色 10 | 11 | - 🛡️ **安全保護**:防止刪除重要的系統目錄和專案目錄(如 `/`, `/home`, `/usr`, `.git` 等) 12 | - ♻️ **垃圾桶機制**:將檔案移至垃圾桶而非永久刪除 13 | - 📁 **保留目錄結構**:在垃圾桶中維持原始的完整路徑結構,方便日後還原 14 | - 🔧 **完整相容**:支援所有常見的 `rm` 參數(`-r`, `-f`, `-i`, `-v` 等) 15 | - ⚙️ **可自訂**:透過環境變數自訂垃圾桶位置 16 | - 🎨 **友善介面**:彩色輸出,清楚顯示操作狀態 17 | 18 | ## 安裝方式 19 | 20 | ### 快速安裝(推薦)⚡ 21 | 22 | 只需一行命令即可自動安裝: 23 | 24 | ```bash 25 | curl -sSL https://raw.githubusercontent.com/doggy8088/better-rm/main/install.sh | bash 26 | ``` 27 | 28 | 或使用 wget: 29 | 30 | ```bash 31 | wget -qO- https://raw.githubusercontent.com/doggy8088/better-rm/main/install.sh | bash 32 | ``` 33 | 34 | 安裝腳本會自動: 35 | - ✅ 下載 better-rm 到 `~/.better-rm` 目錄 36 | - ✅ 偵測你的 shell (bash/zsh) 並設定別名 37 | - ✅ 加入 `alias rm='~/.better-rm/better-rm'` 到你的 shell 設定檔 38 | - ✅ 提供清楚的後續步驟說明 39 | 40 | 安裝完成後,執行以下命令啟用: 41 | 42 | ```bash 43 | source ~/.bashrc # 如果使用 bash 44 | # 或 45 | source ~/.zshrc # 如果使用 zsh 46 | ``` 47 | 48 | 驗證安裝: 49 | 50 | ```bash 51 | rm --version 52 | ``` 53 | 54 | --- 55 | 56 | ### 方法一:手動使用別名 57 | 58 | 這種方法最安全,不會覆蓋系統原生的 `rm` 命令,需要時仍可使用 `/bin/rm` 存取原始命令。 59 | 60 | 1. 複製專案到本地目錄: 61 | 62 | ```bash 63 | git clone https://github.com/doggy8088/better-rm.git ~/better-rm 64 | ``` 65 | 66 | 2. 設定別名,在 `~/.bashrc` 或 `~/.zshrc` 中加入以下內容: 67 | 68 | ```bash 69 | # 使用 better-rm 替代 rm 命令 70 | alias rm='~/better-rm/better-rm' 71 | ``` 72 | 73 | 3. 重新載入設定檔: 74 | 75 | ```bash 76 | source ~/.bashrc # 或 source ~/.zshrc 77 | ``` 78 | 79 | 4. 驗證安裝: 80 | 81 | ```bash 82 | rm --version 83 | ``` 84 | 85 | 應該會看到 `better-rm 1.0.0` 的版本資訊。 86 | 87 | **提示**:如果需要使用系統原生的 `rm` 命令,可以使用完整路徑 `/bin/rm` 或用反斜線 `\rm`。 88 | 89 | ### 方法二:手動複製到 PATH 目錄 90 | 91 | 如果你想讓 `better-rm` 可以直接執行(不只是透過 `rm` 別名),可以將它複製到 PATH 目錄: 92 | 93 | ```bash 94 | # 下載專案 95 | git clone https://github.com/doggy8088/better-rm.git 96 | cd better-rm 97 | 98 | # 複製到 /usr/local/bin(需要 sudo 權限) 99 | sudo cp better-rm /usr/local/bin/ 100 | sudo chmod +x /usr/local/bin/better-rm 101 | 102 | # 或複製到使用者的 bin 目錄(不需要 sudo) 103 | mkdir -p ~/bin 104 | cp better-rm ~/bin/ 105 | chmod +x ~/bin/better-rm 106 | 107 | # 確保 ~/bin 在 PATH 中(在 ~/.bashrc 或 ~/.zshrc 加入) 108 | export PATH="$HOME/bin:$PATH" 109 | ``` 110 | 111 | 然後可以選擇性設定別名: 112 | 113 | ```bash 114 | # 在 ~/.bashrc 或 ~/.zshrc 中加入 115 | alias rm='better-rm' 116 | ``` 117 | 118 | 重新載入設定檔: 119 | 120 | ```bash 121 | source ~/.bashrc # 或 source ~/.zshrc 122 | ``` 123 | 124 | ## 使用方式 125 | 126 | ### 基本語法 127 | 128 | ```bash 129 | rm [選項] [檔案或目錄...] 130 | ``` 131 | 132 | ### 支援的選項 133 | 134 | | 選項 | 說明 | 135 | |------|------| 136 | | `-r`, `-R`, `--recursive` | 遞迴刪除目錄及其內容 | 137 | | `-f`, `--force` | 強制刪除,忽略不存在的檔案,不提示 | 138 | | `-i` | 每次刪除前提示確認 | 139 | | `-I` | 刪除超過三個檔案或遞迴刪除前提示一次 | 140 | | `-v`, `--verbose` | 顯示詳細操作過程 | 141 | | `--help` | 顯示說明訊息 | 142 | | `--version` | 顯示版本資訊 | 143 | 144 | ### 使用範例 145 | 146 | #### 刪除單一檔案 147 | 148 | ```bash 149 | rm file.txt 150 | ``` 151 | 152 | #### 刪除目錄 153 | 154 | ```bash 155 | rm -r directory/ 156 | ``` 157 | 158 | #### 強制刪除(不提示) 159 | 160 | ```bash 161 | rm -rf old_project/ 162 | ``` 163 | 164 | #### 互動式刪除(每次都會詢問) 165 | 166 | ```bash 167 | rm -i important_file.txt 168 | ``` 169 | 170 | #### 顯示詳細過程 171 | 172 | ```bash 173 | rm -rv temp_folder/ 174 | ``` 175 | 176 | #### 使用自訂垃圾桶目錄 177 | 178 | ```bash 179 | TRASH_DIR=/tmp/my-trash rm file.txt 180 | ``` 181 | 182 | ## 垃圾桶機制 183 | 184 | ### 預設位置 185 | 186 | 垃圾桶預設位於 `~/.Trash` 目錄。 187 | 188 | ### 目錄結構保留 189 | 190 | 當你刪除一個檔案時,`better-rm` 會在垃圾桶中保留原始的完整路徑結構。 191 | 192 | **範例:** 193 | 194 | 如果你刪除 `/home/user/projects/myapp/src/main.js`,該檔案會被移動到: 195 | 196 | ``` 197 | ~/.Trash/home/user/projects/myapp/src/main.js 198 | ``` 199 | 200 | 這樣做的好處: 201 | - 可以清楚知道檔案原本的位置 202 | - 方便日後開發還原功能 203 | - 避免不同路徑下同名檔案的衝突 204 | 205 | ### 檔案名稱格式 206 | 207 | 從 v1.1.0 開始,垃圾桶中的檔案名稱會自動加上時間戳記和內容雜湊值: 208 | 209 | ``` 210 | 原始檔案: file.txt 211 | 垃圾桶中: file.txt__20251209_143052_123456789__e59ff97941044f85df5297e1c302d260 212 | 格式說明: filename__YYYYMMDD_HHMMSS_NNNNNNNNN__hash 213 | ``` 214 | 215 | 這樣的設計有以下好處: 216 | - 時間戳記包含奈秒精度,避免快速刪除時的檔名衝突 217 | - 內容雜湊值可用於識別檔案內容,方便重複檔案的管理 218 | - 可追蹤檔案的刪除時間 219 | 220 | ### 刪除日誌 221 | 222 | `better-rm` 會在垃圾桶目錄中維護一個 `.deletion_log` 檔案,記錄所有刪除操作: 223 | 224 | ```bash 225 | # 查看刪除日誌 226 | cat ~/.Trash/.deletion_log 227 | ``` 228 | 229 | 日誌格式: 230 | ``` 231 | TIMESTAMP | ORIGINAL_PATH | TRASH_PATH | HASH | FILE_TYPE 232 | ``` 233 | 234 | 範例: 235 | ``` 236 | 20251209_084530_429345278 | /home/user/file.txt | /home/user/.Trash/.../file.txt__...__hash | d6eb320... | file 237 | 20251209_084547_505346836 | /home/user/mydir | /home/user/.Trash/.../mydir__...__hash | c55e1b8... | directory 238 | ``` 239 | 240 | 這個日誌可以幫助你: 241 | - 追蹤所有刪除的檔案 242 | - 找出特定檔案的刪除時間 243 | - 確認檔案在垃圾桶中的位置 244 | - 根據內容雜湊值找出重複的檔案 245 | 246 | ### 自訂垃圾桶位置 247 | 248 | 你可以透過 `TRASH_DIR` 環境變數來自訂垃圾桶位置: 249 | 250 | ```bash 251 | # 暫時設定(單次使用) 252 | TRASH_DIR=/tmp/trash rm file.txt 253 | 254 | # 永久設定(在 ~/.bashrc 或 ~/.zshrc 中加入) 255 | export TRASH_DIR="$HOME/MyTrash" 256 | ``` 257 | 258 | ## 受保護的目錄 259 | 260 | 為了防止災難性的誤刪,`better-rm` 會拒絕刪除以下重要目錄: 261 | 262 | ### 系統目錄 263 | 264 | - `/` - 根目錄 265 | - `/bin` - 系統二進位檔案 266 | - `/boot` - 開機相關檔案 267 | - `/dev` - 裝置檔案 268 | - `/etc` - 系統設定檔 269 | - `/home` - 使用者主目錄根目錄 270 | - `/lib`, `/lib64` - 系統函式庫 271 | - `/opt` - 第三方軟體 272 | - `/proc` - 程序資訊 273 | - `/root` - root 使用者的家目錄 274 | - `/sbin` - 系統管理二進位檔案 275 | - `/sys` - 系統資訊 276 | - `/usr` - 使用者程式 277 | - `/var` - 變動資料 278 | 279 | ### 使用者目錄 280 | 281 | - `~` 或 `$HOME` - 你的家目錄(整個目錄) 282 | 283 | ### 專案目錄 284 | 285 | - `.git` - Git 版本控制目錄(任何位置的 .git 目錄) 286 | 287 | ### 保護機制 288 | 289 | 當你嘗試刪除受保護的目錄時,`better-rm` 會: 290 | 291 | 1. 顯示錯誤訊息 292 | 2. 拒絕執行刪除操作 293 | 3. 提示這是重要的系統或專案目錄 294 | 295 | **範例:** 296 | 297 | ```bash 298 | $ rm -rf / 299 | 錯誤 (Error): 拒絕刪除受保護的目錄: '/' 300 | 錯誤 (Error): Refused to remove protected directory: '/' 301 | 錯誤 (Error): 這是一個重要的系統目錄或專案目錄! 302 | 錯誤 (Error): This is a critical system or project directory! 303 | ``` 304 | 305 | ## 清理垃圾桶 306 | 307 | `better-rm` 目前不會自動清理垃圾桶,你可以手動清理: 308 | 309 | ### 檢視垃圾桶內容 310 | 311 | ```bash 312 | ls -la ~/.Trash/ 313 | ``` 314 | 315 | ### 清空垃圾桶 316 | 317 | ```bash 318 | # 使用系統原生的 rm 命令(請小心!) 319 | /bin/rm -rf ~/.Trash/* 320 | ``` 321 | 322 | ### 還原檔案 323 | 324 | 由於檔案保留了原始路徑結構,你可以輕鬆還原: 325 | 326 | ```bash 327 | # 手動還原檔案 328 | mv ~/.Trash/home/user/projects/myapp/file.txt /home/user/projects/myapp/ 329 | ``` 330 | 331 | > **注意:** 未來版本計畫提供自動還原功能。 332 | 333 | ## 技術細節 334 | 335 | ### 相容性 336 | 337 | - **作業系統**:Linux, macOS, Unix-like 系統 338 | - **Shell**:Bash 4.0+ 339 | - **依賴**:基本的 Unix 工具(`mv`, `mkdir`, `readlink`/`realpath`) 340 | 341 | ### 限制 342 | 343 | 1. **跨檔案系統移動**:如果垃圾桶和原始檔案在不同的檔案系統(如不同的硬碟分割區),移動操作可能會比較慢。 344 | 2. **磁碟空間**:垃圾桶會佔用磁碟空間,需要定期清理。 345 | 3. **權限問題**:如果你沒有權限移動某個檔案,操作會失敗。 346 | 347 | ## 安全性考量 / Security Considerations 348 | 349 | ### ⚠️ 使用限制與風險 / Limitations and Risks 350 | 351 | **重要:請在使用前充分了解以下限制** 352 | 353 | 1. **不是完整備份解決方案** 354 | - 垃圾桶機制僅提供基本的誤刪保護 355 | - 無法防護硬碟故障、系統故障、惡意軟體等風險 356 | - 重要資料必須有獨立的備份策略 357 | 358 | 2. **磁碟空間限制** 359 | - 垃圾桶會持續佔用磁碟空間 360 | - 可能導致磁碟空間不足的問題 361 | - 需要定期手動清理 362 | 363 | 3. **跨檔案系統限制** 364 | - 跨不同檔案系統的移動會較慢(需要複製而非移動) 365 | - 可能會遇到權限問題 366 | 367 | 4. **無保證性** 368 | - 本工具按「現況」提供,無任何保證 369 | - 作者不對任何資料遺失負責 370 | - 使用者需自行承擔風險 371 | 372 | **Important: Please fully understand the following limitations before use** 373 | 374 | 1. **Not a Complete Backup Solution** 375 | - Trash mechanism only provides basic accidental deletion protection 376 | - Cannot protect against drive failure, system failure, malware, etc. 377 | - Important data must have an independent backup strategy 378 | 379 | 2. **Disk Space Limitation** 380 | - Trash continuously occupies disk space 381 | - May cause disk space shortage 382 | - Requires regular manual cleanup 383 | 384 | 3. **Cross-Filesystem Limitation** 385 | - Moving across different filesystems is slower (requires copy instead of move) 386 | - May encounter permission issues 387 | 388 | 4. **No Warranty** 389 | - This tool is provided "AS IS" without any warranty 390 | - Author is not responsible for any data loss 391 | - Users assume all risks 392 | 393 | ### 為什麼需要 better-rm? 394 | 395 | 在使用 AI 輔助編程工具(如 Claude Code, GitHub Copilot 等)時,AI 可能會建議執行一些危險的命令,例如: 396 | 397 | ```bash 398 | rm -rf ~/ # 刪除整個家目錄! 399 | rm -rf / # 刪除整個系統! 400 | ``` 401 | 402 | 這些命令一旦執行,後果不堪設想。`better-rm` 提供了一層防護網,即使不小心執行了這些命令,也不會造成永久性損害。 403 | 404 | ### 最佳實踐 405 | 406 | 1. **謹慎使用 `-f` 選項**:強制模式會跳過確認,建議先不加 `-f` 測試。 407 | 2. **定期清理垃圾桶**:避免佔用過多磁碟空間。 408 | 3. **重要檔案另外備份**:雖然有垃圾桶,但重要資料還是要有完整的備份策略。 409 | 4. **了解保護清單**:知道哪些目錄受到保護,避免驚訝。 410 | 411 | ## 疑難排解 412 | 413 | ### 問題:找不到 rm 命令 414 | 415 | **解決方法:** 416 | 417 | 1. 檢查 `~/bin` 是否在 PATH 中: 418 | ```bash 419 | echo $PATH 420 | ``` 421 | 422 | 2. 重新載入設定檔: 423 | ```bash 424 | source ~/.bashrc # 或 source ~/.zshrc 425 | ``` 426 | 427 | ### 問題:提示權限被拒 428 | 429 | **解決方法:** 430 | 431 | 確保腳本有執行權限: 432 | ```bash 433 | chmod +x ~/bin/rm 434 | ``` 435 | 436 | ### 問題:垃圾桶佔用太多空間 437 | 438 | **解決方法:** 439 | 440 | 定期清理垃圾桶: 441 | ```bash 442 | # 清理 30 天前的檔案 443 | find ~/.Trash -mtime +30 -delete 444 | ``` 445 | 446 | ### 問題:想要使用原生的 rm 命令 447 | 448 | **解決方法:** 449 | 450 | 使用完整路徑呼叫系統原生的 rm: 451 | ```bash 452 | /bin/rm file.txt 453 | ``` 454 | 455 | 或用反斜線暫時繞過別名(bypass alias): 456 | ```bash 457 | \rm file.txt 458 | ``` 459 | 460 | ## 未來計畫 461 | 462 | - [ ] 實作還原功能(`rm --restore`) 463 | - [ ] 自動清理過期的垃圾檔案 464 | - [ ] 提供垃圾桶管理介面 465 | - [ ] 支援更多自訂保護規則 466 | - [ ] 加入設定檔支援 467 | 468 | ## 測試 469 | 470 | 本專案包含完整的測試腳本,可在容器環境下測試所有功能: 471 | 472 | ```bash 473 | # 執行測試 474 | ./test-better-rm.sh 475 | 476 | # 在 Docker 容器中測試 477 | docker run -v $(pwd):/app ubuntu:latest bash /app/test-better-rm.sh 478 | ``` 479 | 480 | 測試涵蓋: 481 | - ✅ 基本檔案與目錄刪除 482 | - ✅ 特殊字元檔名處理 483 | - ✅ 時間戳記與內容 Hash 484 | - ✅ 刪除日誌功能 485 | - ✅ 受保護目錄 486 | - ✅ 快速連續刪除 487 | - ✅ 符號連結處理 488 | - ✅ 命令參數選項 489 | 490 | 詳細測試說明請參考 [TEST_README.md](TEST_README.md) 491 | 492 | ## 貢獻 493 | 494 | 歡迎提交 Issue 和 Pull Request! 495 | 496 | ### 開發指南 497 | 498 | 1. Fork 本專案 499 | 2. 建立你的特性分支 (`git checkout -b feature/amazing-feature`) 500 | 3. 提交你的變更 (`git commit -m 'Add some amazing feature'`) 501 | 4. 推送到分支 (`git push origin feature/amazing-feature`) 502 | 5. 開啟 Pull Request 503 | 504 | ## 授權 505 | 506 | 本專案採用 MIT 授權條款 - 詳見 [LICENSE](LICENSE) 檔案 507 | 508 | ## 致謝 509 | 510 | 感謝所有為更安全的命令列環境做出貢獻的開發者。 511 | 512 | ## 聯絡方式 513 | 514 | 如有任何問題或建議,歡迎透過 GitHub Issues 與我們聯繫。 515 | 516 | --- 517 | ## ⚠️ 重要免責聲明 / Important Disclaimer 518 | 519 | **使用本工具前請務必詳讀以下聲明:** 520 | 521 | 🔴 **本工具僅提供基本的安全防護層,不能取代完整的備份策略** 522 | - 此工具將檔案移至垃圾桶,但垃圾桶仍在同一個檔案系統上 523 | - 硬碟故障、系統損壞、意外格式化等情況仍會導致資料永久遺失 524 | - **請務必定期備份重要資料到外部儲存裝置或雲端服務** 525 | 526 | 🔴 **本工具按「現況」提供,不提供任何明示或暗示的保證** 527 | - 作者不對使用本工具造成的任何資料遺失或損害負責 528 | - 本工具可能存在未知的 bug 或相容性問題 529 | - 使用者需自行承擔使用風險 530 | 531 | 🔴 **本工具不應在生產環境或關鍵系統上使用,除非您完全了解其運作方式** 532 | - 建議先在測試環境中充分測試 533 | - 了解垃圾桶機制的限制(如磁碟空間、跨檔案系統移動等) 534 | - 確保您知道如何使用原生 `rm` 命令(`/bin/rm` 或 `\rm`) 535 | 536 | 🔴 **垃圾桶不會自動清理,需要定期手動管理** 537 | - 垃圾桶會持續佔用磁碟空間 538 | - 建議定期檢查和清理垃圾桶內容 539 | - 長期累積可能導致磁碟空間不足 540 | 541 | **English Version:** 542 | 543 | 🔴 **This tool provides only basic safety protection and CANNOT replace a complete backup strategy** 544 | - Files are moved to trash, but the trash is still on the same filesystem 545 | - Hard drive failure, system corruption, or accidental formatting can still cause permanent data loss 546 | - **Always maintain regular backups of important data to external storage or cloud services** 547 | 548 | 🔴 **This tool is provided "AS IS" without any warranties, express or implied** 549 | - The author is not responsible for any data loss or damage caused by using this tool 550 | - This tool may contain unknown bugs or compatibility issues 551 | - Users assume all risks associated with its use 552 | 553 | 🔴 **This tool should NOT be used in production or critical systems unless you fully understand how it works** 554 | - Test thoroughly in a non-production environment first 555 | - Understand the limitations of the trash mechanism (disk space, cross-filesystem moves, etc.) 556 | - Ensure you know how to use the native `rm` command (`/bin/rm` or `\rm`) 557 | 558 | 🔴 **The trash is NOT automatically cleaned and requires manual management** 559 | - Trash continuously occupies disk space 560 | - Regularly check and clean trash contents 561 | - Long-term accumulation may lead to insufficient disk space 562 | 563 | --- 564 | 565 | ## ⚠️ 再次提醒 / Final Reminder 566 | 567 | **本工具不能也不應該取代完整的備份策略!** 568 | 569 | - ✅ **請做好**:定期備份重要資料到外部儲存或雲端 570 | - ✅ **請做好**:了解工具的限制和風險 571 | - ✅ **請做好**:在測試環境先充分測試 572 | - ✅ **請做好**:定期清理垃圾桶 573 | - ❌ **請勿**:依賴垃圾桶作為唯一的資料保護措施 574 | - ❌ **請勿**:在關鍵生產系統上未經測試就使用 575 | - ❌ **請勿**:假設垃圾桶中的資料永遠安全 576 | 577 | **This tool CANNOT and SHOULD NOT replace a complete backup strategy!** 578 | 579 | - ✅ **DO**: Regularly backup important data to external storage or cloud 580 | - ✅ **DO**: Understand the tool's limitations and risks 581 | - ✅ **DO**: Test thoroughly in a test environment first 582 | - ✅ **DO**: Regularly clean the trash 583 | - ❌ **DON'T**: Rely on the trash as your only data protection measure 584 | - ❌ **DON'T**: Use in critical production systems without testing 585 | - ❌ **DON'T**: Assume data in trash is permanently safe 586 | 587 | **使用本工具即表示您已閱讀、理解並同意上述所有免責聲明和限制。** 588 | 589 | **By using this tool, you acknowledge that you have read, understood, and agreed to all the disclaimers and limitations stated above.** 590 | -------------------------------------------------------------------------------- /test-better-rm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Better-RM 完整測試腳本 4 | # Comprehensive Test Script for Better-RM 5 | # 6 | # 此腳本可在容器環境下測試 better-rm 的所有功能 7 | # This script tests all features of better-rm in a container environment 8 | # 9 | 10 | # 顏色定義 (Color definitions) 11 | RED='\033[0;31m' 12 | GREEN='\033[0;32m' 13 | YELLOW='\033[1;33m' 14 | BLUE='\033[0;34m' 15 | NC='\033[0m' # No Color 16 | 17 | # 測試計數器 (Test counters) 18 | TOTAL_TESTS=0 19 | PASSED_TESTS=0 20 | FAILED_TESTS=0 21 | 22 | # Better-RM 腳本路徑 (Better-RM script path) 23 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 24 | BETTER_RM="$SCRIPT_DIR/better-rm" 25 | 26 | # 測試用的垃圾桶目錄 (Test trash directory) 27 | TEST_TRASH_DIR="/tmp/better-rm-test-trash" 28 | 29 | # 測試用的工作目錄 (Test working directory) 30 | TEST_WORK_DIR="/tmp/better-rm-test-work" 31 | 32 | # 顯示測試標題 (Display test title) 33 | test_title() { 34 | echo "" 35 | echo -e "${BLUE}========================================${NC}" 36 | echo -e "${BLUE}$1${NC}" 37 | echo -e "${BLUE}========================================${NC}" 38 | } 39 | 40 | # 顯示測試項目 (Display test item) 41 | test_item() { 42 | echo -e "${YELLOW}[測試 $TOTAL_TESTS] $1${NC}" 43 | TOTAL_TESTS=$((TOTAL_TESTS + 1)) 44 | } 45 | 46 | # 測試成功 (Test passed) 47 | test_pass() { 48 | echo -e "${GREEN}✓ 通過: $1${NC}" 49 | PASSED_TESTS=$((PASSED_TESTS + 1)) 50 | } 51 | 52 | # 測試失敗 (Test failed) 53 | test_fail() { 54 | echo -e "${RED}✗ 失敗: $1${NC}" 55 | FAILED_TESTS=$((FAILED_TESTS + 1)) 56 | } 57 | 58 | # 清理測試環境 (Clean up test environment) 59 | cleanup() { 60 | rm -rf "$TEST_TRASH_DIR" "$TEST_WORK_DIR" 61 | } 62 | 63 | # 設置測試環境 (Setup test environment) 64 | setup() { 65 | cleanup 66 | mkdir -p "$TEST_WORK_DIR" 67 | export TRASH_DIR="$TEST_TRASH_DIR" 68 | } 69 | 70 | # 驗證檔案是否在垃圾桶中 (Verify file is in trash) 71 | verify_in_trash() { 72 | local pattern="$1" 73 | if find "$TEST_TRASH_DIR" -name "*${pattern}*" 2>/dev/null | grep -q .; then 74 | return 0 75 | else 76 | return 1 77 | fi 78 | } 79 | 80 | # 驗證日誌記錄 (Verify log entry) 81 | verify_log_entry() { 82 | local log_file="$TEST_TRASH_DIR/.deletion_log" 83 | local pattern="$1" 84 | if [ -f "$log_file" ] && grep -q "$pattern" "$log_file"; then 85 | return 0 86 | else 87 | return 1 88 | fi 89 | } 90 | 91 | # ============================================================================ 92 | # 測試開始 (Tests Begin) 93 | # ============================================================================ 94 | 95 | echo -e "${GREEN}Better-RM 完整測試套件${NC}" 96 | echo -e "${GREEN}Better-RM Comprehensive Test Suite${NC}" 97 | echo "" 98 | echo "測試腳本: $BETTER_RM" 99 | echo "垃圾桶目錄: $TEST_TRASH_DIR" 100 | echo "工作目錄: $TEST_WORK_DIR" 101 | echo "" 102 | 103 | # ============================================================================ 104 | # 測試 1: 版本與說明 (Test 1: Version and Help) 105 | # ============================================================================ 106 | test_title "測試 1: 版本與說明資訊" 107 | 108 | test_item "測試 --version 參數" 109 | if "$BETTER_RM" --version | grep -q "better-rm 1.1.0"; then 110 | test_pass "--version 顯示正確版本" 111 | else 112 | test_fail "--version 版本不正確" 113 | fi 114 | 115 | test_item "測試 --help 參數" 116 | if "$BETTER_RM" --help | grep -q "用法"; then 117 | test_pass "--help 顯示說明訊息" 118 | else 119 | test_fail "--help 未顯示說明訊息" 120 | fi 121 | 122 | # ============================================================================ 123 | # 測試 2: 基本檔案刪除 (Test 2: Basic File Deletion) 124 | # ============================================================================ 125 | test_title "測試 2: 基本檔案刪除功能" 126 | 127 | setup 128 | cd "$TEST_WORK_DIR" 129 | 130 | test_item "刪除單一檔案" 131 | echo "test content" > file1.txt 132 | if "$BETTER_RM" file1.txt && [ ! -e file1.txt ] && verify_in_trash "file1.txt"; then 133 | test_pass "單一檔案成功移至垃圾桶" 134 | else 135 | test_fail "單一檔案刪除失敗" 136 | fi 137 | 138 | test_item "刪除多個檔案" 139 | echo "file2" > file2.txt 140 | echo "file3" > file3.txt 141 | if "$BETTER_RM" file2.txt file3.txt && [ ! -e file2.txt ] && [ ! -e file3.txt ] && \ 142 | verify_in_trash "file2.txt" && verify_in_trash "file3.txt"; then 143 | test_pass "多個檔案成功移至垃圾桶" 144 | else 145 | test_fail "多個檔案刪除失敗" 146 | fi 147 | 148 | # ============================================================================ 149 | # 測試 3: 目錄刪除 (Test 3: Directory Deletion) 150 | # ============================================================================ 151 | test_title "測試 3: 目錄刪除功能" 152 | 153 | setup 154 | cd "$TEST_WORK_DIR" 155 | 156 | test_item "遞迴刪除目錄 (-r)" 157 | mkdir -p testdir/subdir 158 | echo "content" > testdir/file.txt 159 | echo "subcontent" > testdir/subdir/subfile.txt 160 | if "$BETTER_RM" -r testdir && [ ! -e testdir ] && verify_in_trash "testdir"; then 161 | test_pass "目錄成功遞迴刪除" 162 | else 163 | test_fail "目錄刪除失敗" 164 | fi 165 | 166 | test_item "刪除空目錄" 167 | mkdir emptydir 168 | if "$BETTER_RM" -r emptydir && [ ! -e emptydir ] && verify_in_trash "emptydir"; then 169 | test_pass "空目錄成功刪除" 170 | else 171 | test_fail "空目錄刪除失敗" 172 | fi 173 | 174 | test_item "不加 -r 刪除目錄應失敗" 175 | mkdir testdir2 176 | if "$BETTER_RM" testdir2 2>/dev/null; then 177 | test_fail "不加 -r 卻成功刪除目錄(不應該)" 178 | else 179 | test_pass "不加 -r 正確拒絕刪除目錄" 180 | fi 181 | rm -rf testdir2 182 | 183 | # ============================================================================ 184 | # 測試 4: 特殊字元檔名 (Test 4: Special Characters in Filenames) 185 | # ============================================================================ 186 | test_title "測試 4: 特殊字元檔名處理" 187 | 188 | setup 189 | cd "$TEST_WORK_DIR" 190 | 191 | test_item "檔名含空格" 192 | echo "content" > "file with spaces.txt" 193 | if "$BETTER_RM" "file with spaces.txt" && [ ! -e "file with spaces.txt" ]; then 194 | test_pass "空格檔名成功處理" 195 | else 196 | test_fail "空格檔名處理失敗" 197 | fi 198 | 199 | test_item "檔名含特殊字元" 200 | echo "content" > "file-with_special.chars.txt" 201 | if "$BETTER_RM" "file-with_special.chars.txt" && [ ! -e "file-with_special.chars.txt" ]; then 202 | test_pass "特殊字元檔名成功處理" 203 | else 204 | test_fail "特殊字元檔名處理失敗" 205 | fi 206 | 207 | test_item "中文檔名" 208 | echo "內容" > "測試檔案.txt" 209 | if "$BETTER_RM" "測試檔案.txt" && [ ! -e "測試檔案.txt" ]; then 210 | test_pass "中文檔名成功處理" 211 | else 212 | test_fail "中文檔名處理失敗" 213 | fi 214 | 215 | # ============================================================================ 216 | # 測試 5: 符號連結 (Test 5: Symbolic Links) 217 | # ============================================================================ 218 | test_title "測試 5: 符號連結處理" 219 | 220 | setup 221 | cd "$TEST_WORK_DIR" 222 | 223 | test_item "刪除符號連結" 224 | echo "target" > target.txt 225 | ln -s target.txt symlink.txt 226 | if "$BETTER_RM" symlink.txt && [ ! -L symlink.txt ] && [ -e target.txt ]; then 227 | test_pass "符號連結成功刪除(目標檔案保留)" 228 | else 229 | test_fail "符號連結刪除失敗" 230 | fi 231 | 232 | # ============================================================================ 233 | # 測試 6: 時間戳記與 Hash (Test 6: Timestamp and Hash) 234 | # ============================================================================ 235 | test_title "測試 6: 時間戳記與內容 Hash" 236 | 237 | setup 238 | cd "$TEST_WORK_DIR" 239 | 240 | test_item "檔名包含時間戳記和 Hash" 241 | echo "content for hash" > hashtest.txt 242 | "$BETTER_RM" hashtest.txt 243 | if find "$TEST_TRASH_DIR" -name "hashtest.txt__*__*" | grep -q .; then 244 | test_pass "檔名包含時間戳記和 Hash" 245 | else 246 | test_fail "檔名格式不正確" 247 | fi 248 | 249 | test_item "相同檔名但不同內容產生不同 Hash" 250 | echo "content1" > test.txt 251 | "$BETTER_RM" test.txt 252 | sleep 0.01 # 確保時間戳記不同 253 | hash1=$(find "$TEST_TRASH_DIR" -name "test.txt__*" | head -1 | awk -F'__' '{print $NF}') 254 | 255 | echo "content2" > test.txt 256 | "$BETTER_RM" test.txt 257 | hash2=$(find "$TEST_TRASH_DIR" -name "test.txt__*" | tail -1 | awk -F'__' '{print $NF}') 258 | 259 | if [ -n "$hash1" ] && [ -n "$hash2" ] && [ "$hash1" != "$hash2" ]; then 260 | test_pass "不同內容產生不同 Hash (hash1=$hash1, hash2=$hash2)" 261 | else 262 | test_fail "不同內容產生相同 Hash 或未找到 Hash (hash1=$hash1, hash2=$hash2)" 263 | fi 264 | 265 | test_item "空檔案的 Hash" 266 | touch empty.txt 267 | "$BETTER_RM" empty.txt 268 | if find "$TEST_TRASH_DIR" -name "empty.txt__*__d41d8cd98f00b204e9800998ecf8427e" | grep -q .; then 269 | test_pass "空檔案 Hash 正確 (MD5 of empty string)" 270 | else 271 | test_fail "空檔案 Hash 不正確" 272 | fi 273 | 274 | # ============================================================================ 275 | # 測試 7: 刪除日誌 (Test 7: Deletion Log) 276 | # ============================================================================ 277 | test_title "測試 7: 刪除日誌功能" 278 | 279 | setup 280 | cd "$TEST_WORK_DIR" 281 | 282 | test_item "日誌檔案自動創建" 283 | echo "test" > logtest.txt 284 | "$BETTER_RM" logtest.txt 285 | if [ -f "$TEST_TRASH_DIR/.deletion_log" ]; then 286 | test_pass "日誌檔案成功創建" 287 | else 288 | test_fail "日誌檔案未創建" 289 | fi 290 | 291 | test_item "日誌記錄檔案刪除" 292 | if verify_log_entry "logtest.txt" && verify_log_entry "file"; then 293 | test_pass "日誌正確記錄檔案刪除" 294 | else 295 | test_fail "日誌未記錄檔案刪除" 296 | fi 297 | 298 | test_item "日誌記錄目錄刪除" 299 | mkdir logdir 300 | echo "content" > logdir/file.txt 301 | "$BETTER_RM" -r logdir 302 | if verify_log_entry "logdir" && verify_log_entry "directory"; then 303 | test_pass "日誌正確記錄目錄刪除" 304 | else 305 | test_fail "日誌未記錄目錄刪除" 306 | fi 307 | 308 | test_item "日誌記錄符號連結" 309 | echo "target" > logtarget.txt 310 | ln -s logtarget.txt logsymlink.txt 311 | "$BETTER_RM" logsymlink.txt 312 | if verify_log_entry "symlink"; then 313 | test_pass "日誌正確記錄符號連結" 314 | else 315 | test_fail "日誌未記錄符號連結" 316 | fi 317 | 318 | test_item "日誌格式正確" 319 | log_file="$TEST_TRASH_DIR/.deletion_log" 320 | if grep -E "^[0-9]{8}_[0-9]{6}_[0-9]+ \| .+ \| .+ \| .+ \| (file|directory|symlink)$" "$log_file" >/dev/null 2>&1; then 321 | test_pass "日誌格式正確" 322 | else 323 | test_fail "日誌格式不正確" 324 | fi 325 | 326 | # ============================================================================ 327 | # 測試 8: 參數選項 (Test 8: Command Options) 328 | # ============================================================================ 329 | test_title "測試 8: 命令參數選項" 330 | 331 | setup 332 | cd "$TEST_WORK_DIR" 333 | 334 | test_item "詳細模式 (-v)" 335 | echo "verbose test" > vtest.txt 336 | if "$BETTER_RM" -v vtest.txt 2>&1 | grep -q "已移除"; then 337 | test_pass "-v 參數顯示詳細訊息" 338 | else 339 | test_fail "-v 參數未顯示詳細訊息" 340 | fi 341 | 342 | test_item "強制模式 (-f) 忽略不存在的檔案" 343 | if "$BETTER_RM" -f nonexistent.txt 2>/dev/null; then 344 | test_pass "-f 參數正確忽略不存在的檔案" 345 | else 346 | test_fail "-f 參數未正確處理" 347 | fi 348 | 349 | test_item "組合參數 (-rf)" 350 | mkdir -p rftest/subdir 351 | echo "content" > rftest/file.txt 352 | if "$BETTER_RM" -rf rftest && [ ! -e rftest ]; then 353 | test_pass "-rf 組合參數正常工作" 354 | else 355 | test_fail "-rf 組合參數失敗" 356 | fi 357 | 358 | # ============================================================================ 359 | # 測試 9: 受保護目錄 (Test 9: Protected Directories) 360 | # ============================================================================ 361 | test_title "測試 9: 受保護目錄" 362 | 363 | test_item "拒絕刪除根目錄 (/)" 364 | if "$BETTER_RM" -rf / 2>&1 | grep -q "拒絕刪除受保護的目錄"; then 365 | test_pass "正確拒絕刪除根目錄" 366 | else 367 | test_fail "未正確保護根目錄" 368 | fi 369 | 370 | test_item "拒絕刪除 /home" 371 | if "$BETTER_RM" -rf /home 2>&1 | grep -q "拒絕刪除受保護的目錄"; then 372 | test_pass "正確拒絕刪除 /home" 373 | else 374 | test_fail "未正確保護 /home" 375 | fi 376 | 377 | test_item "拒絕刪除 .git 目錄" 378 | setup 379 | cd "$TEST_WORK_DIR" 380 | mkdir -p project/.git 381 | if "$BETTER_RM" -rf project/.git 2>&1 | grep -q "拒絕刪除受保護的目錄"; then 382 | test_pass "正確拒絕刪除 .git 目錄" 383 | else 384 | test_fail "未正確保護 .git 目錄" 385 | fi 386 | 387 | # ============================================================================ 388 | # 測試 10: 快速連續刪除 (Test 10: Rapid Successive Deletions) 389 | # ============================================================================ 390 | test_title "測試 10: 快速連續刪除(測試奈秒時間戳記)" 391 | 392 | setup 393 | cd "$TEST_WORK_DIR" 394 | 395 | test_item "快速連續刪除多個檔案" 396 | for i in {1..5}; do 397 | echo "content $i" > "rapid$i.txt" 398 | done 399 | 400 | for i in {1..5}; do 401 | "$BETTER_RM" "rapid$i.txt" & 402 | done 403 | wait 404 | 405 | # 檢查所有檔案是否都成功刪除 406 | all_deleted=true 407 | for i in {1..5}; do 408 | if [ -e "rapid$i.txt" ]; then 409 | all_deleted=false 410 | fi 411 | done 412 | 413 | # 檢查是否有檔名衝突 414 | trash_files=$(find "$TEST_TRASH_DIR" -name "rapid*.txt__*" 2>/dev/null | wc -l) 415 | 416 | if $all_deleted && [ "$trash_files" -eq 5 ]; then 417 | test_pass "快速連續刪除成功,無檔名衝突(奈秒時間戳記正常工作)" 418 | elif $all_deleted; then 419 | test_pass "快速連續刪除成功,但找到 $trash_files 個檔案(預期 5 個)" 420 | else 421 | test_fail "快速連續刪除失敗" 422 | fi 423 | 424 | # ============================================================================ 425 | # 測試 11: 路徑結構保留 (Test 11: Path Structure Preservation) 426 | # ============================================================================ 427 | test_title "測試 11: 垃圾桶路徑結構保留" 428 | 429 | setup 430 | cd "$TEST_WORK_DIR" 431 | 432 | test_item "深層目錄結構保留" 433 | mkdir -p deep/nested/directory/structure 434 | echo "deep content" > deep/nested/directory/structure/file.txt 435 | original_path="$TEST_WORK_DIR/deep/nested/directory/structure/file.txt" 436 | "$BETTER_RM" deep/nested/directory/structure/file.txt 437 | 438 | # 檢查垃圾桶中是否保留了路徑結構 439 | if find "$TEST_TRASH_DIR$TEST_WORK_DIR/deep/nested/directory/structure" -name "file.txt__*" | grep -q .; then 440 | test_pass "路徑結構成功保留" 441 | else 442 | test_fail "路徑結構未保留" 443 | fi 444 | 445 | # ============================================================================ 446 | # 測試 12: 自訂垃圾桶目錄 (Test 12: Custom Trash Directory) 447 | # ============================================================================ 448 | test_title "測試 12: 自訂垃圾桶目錄" 449 | 450 | test_item "使用自訂 TRASH_DIR" 451 | custom_trash="/tmp/custom-trash-test" 452 | rm -rf "$custom_trash" 453 | cd "$TEST_WORK_DIR" 454 | echo "custom trash test" > customtest.txt 455 | 456 | TRASH_DIR="$custom_trash" "$BETTER_RM" customtest.txt 457 | 458 | if [ -d "$custom_trash" ] && find "$custom_trash" -name "customtest.txt__*" | grep -q .; then 459 | test_pass "自訂垃圾桶目錄正常工作" 460 | else 461 | test_fail "自訂垃圾桶目錄失敗" 462 | fi 463 | rm -rf "$custom_trash" 464 | 465 | # ============================================================================ 466 | # 測試結果統計 (Test Results Summary) 467 | # ============================================================================ 468 | 469 | cleanup 470 | 471 | echo "" 472 | echo -e "${BLUE}========================================${NC}" 473 | echo -e "${BLUE}測試結果統計 (Test Results Summary)${NC}" 474 | echo -e "${BLUE}========================================${NC}" 475 | echo "" 476 | echo -e "總測試數 (Total Tests): ${BLUE}$TOTAL_TESTS${NC}" 477 | echo -e "通過測試 (Passed): ${GREEN}$PASSED_TESTS${NC}" 478 | echo -e "失敗測試 (Failed): ${RED}$FAILED_TESTS${NC}" 479 | echo "" 480 | 481 | if [ $FAILED_TESTS -eq 0 ]; then 482 | echo -e "${GREEN}✓ 所有測試通過!(All tests passed!)${NC}" 483 | exit 0 484 | else 485 | echo -e "${RED}✗ 有 $FAILED_TESTS 個測試失敗 ($FAILED_TESTS tests failed)${NC}" 486 | exit 1 487 | fi 488 | -------------------------------------------------------------------------------- /better-rm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # better-rm - A safer rm command that moves files to trash instead of permanent deletion 4 | # 更安全的 rm 命令,將檔案移至垃圾桶而非永久刪除 5 | # 6 | 7 | # 設定垃圾桶目錄 (可透過環境變數 TRASH_DIR 更改) 8 | # Set trash directory (can be changed via TRASH_DIR environment variable) 9 | TRASH_DIR="${TRASH_DIR:-$HOME/.Trash}" 10 | 11 | # 受保護的目錄清單 - 禁止刪除這些重要目錄 12 | # Protected directories list - prevent deletion of these critical directories 13 | PROTECTED_DIRS=( 14 | "/" 15 | "/bin" 16 | "/boot" 17 | "/dev" 18 | "/etc" 19 | "/home" 20 | "/lib" 21 | "/lib64" 22 | "/opt" 23 | "/proc" 24 | "/root" 25 | "/sbin" 26 | "/sys" 27 | "/usr" 28 | "/var" 29 | "$HOME" 30 | "$HOME/" 31 | ) 32 | 33 | # 受保護的目錄模式清單 (使用萬用字元) 34 | # Protected directory patterns (using wildcards) 35 | PROTECTED_PATTERNS=( 36 | ".git" 37 | ".git/" 38 | "*/.git" 39 | "*/.git/" 40 | ) 41 | 42 | # 顏色定義 (Color definitions) 43 | RED='\033[0;31m' 44 | GREEN='\033[0;32m' 45 | YELLOW='\033[1;33m' 46 | NC='\033[0m' # No Color 47 | 48 | # 顯示錯誤訊息 49 | # Display error message 50 | error_msg() { 51 | echo -e "${RED}錯誤 (Error): $1${NC}" >&2 52 | } 53 | 54 | # 顯示警告訊息 55 | # Display warning message 56 | warn_msg() { 57 | echo -e "${YELLOW}警告 (Warning): $1${NC}" >&2 58 | } 59 | 60 | # 顯示成功訊息 61 | # Display success message 62 | success_msg() { 63 | echo -e "${GREEN}$1${NC}" 64 | } 65 | 66 | # 記錄刪除操作到日誌檔案 67 | # Log deletion operation to log file 68 | log_deletion() { 69 | local original_path="$1" 70 | local trash_path="$2" 71 | local file_hash="$3" 72 | local timestamp="$4" 73 | local file_type="$5" 74 | local log_file="$TRASH_DIR/.deletion_log" 75 | 76 | # 確保日誌檔案存在 77 | # Ensure log file exists 78 | if [ ! -f "$log_file" ]; then 79 | if ! { 80 | echo "# Better-RM Deletion Log" 81 | echo "# 更好的 RM 刪除日誌" 82 | echo "# Format: TIMESTAMP | ORIGINAL_PATH | TRASH_PATH | HASH | FILE_TYPE" 83 | echo "# 格式:時間戳記 | 原始路徑 | 垃圾桶路徑 | 雜湊值 | 檔案類型" 84 | echo "# ==============================================================================" 85 | } > "$log_file" 2>/dev/null; then 86 | # 日誌檔案創建失敗,顯示警告但不中斷操作 87 | # Log file creation failed, show warning but don't interrupt operation 88 | warn_msg "無法創建刪除日誌: $log_file" 89 | warn_msg "Failed to create deletion log: $log_file" 90 | return 1 91 | fi 92 | fi 93 | 94 | # 記錄刪除操作 95 | # Log the deletion operation 96 | if ! echo "$timestamp | $original_path | $trash_path | $file_hash | $file_type" >> "$log_file" 2>/dev/null; then 97 | # 記錄失敗,顯示警告但不中斷操作 98 | # Logging failed, show warning but don't interrupt operation 99 | warn_msg "無法寫入刪除日誌" 100 | warn_msg "Failed to write to deletion log" 101 | return 1 102 | fi 103 | } 104 | 105 | # 正規化路徑函式 106 | # Normalize path function 107 | normalize_path() { 108 | local path="$1" 109 | # 移除多餘的斜線 110 | # Remove multiple slashes 111 | path=$(echo "$path" | sed 's#/\+#/#g') 112 | 113 | # 處理 ./ 和 ../ 114 | # Handle ./ and ../ 115 | local IFS='/' 116 | local parts=() 117 | local part 118 | 119 | for part in $path; do 120 | if [ "$part" = ".." ]; then 121 | # 移除上一個元素(如果存在) 122 | if [ ${#parts[@]} -gt 0 ]; then 123 | unset 'parts[${#parts[@]}-1]' 124 | parts=("${parts[@]}") # 重新索引陣列 125 | fi 126 | elif [ "$part" != "." ] && [ -n "$part" ]; then 127 | parts+=("$part") 128 | fi 129 | done 130 | 131 | # 重建路徑 132 | local normalized="" 133 | if [[ "$path" = /* ]]; then 134 | normalized="/" 135 | fi 136 | 137 | local first=1 138 | for part in "${parts[@]}"; do 139 | if [ $first -eq 1 ]; then 140 | normalized="${normalized}${part}" 141 | first=0 142 | else 143 | normalized="${normalized}/${part}" 144 | fi 145 | done 146 | 147 | # 如果結果為空且原始路徑以 / 開頭,返回 / 148 | if [ -z "$normalized" ] && [[ "$path" = /* ]]; then 149 | normalized="/" 150 | fi 151 | 152 | echo "$normalized" 153 | } 154 | 155 | # 檢查路徑是否為受保護的目錄 156 | # Check if path is a protected directory 157 | is_protected() { 158 | local path="$1" 159 | local real_path 160 | 161 | # 取得絕對路徑 162 | # Get absolute path 163 | if [ -e "$path" ] || [ -L "$path" ]; then 164 | # 如果檔案存在,使用 readlink/realpath 165 | real_path=$(readlink -f "$path" 2>/dev/null) 166 | if [ -z "$real_path" ]; then 167 | real_path=$(realpath "$path" 2>/dev/null) 168 | fi 169 | if [ -z "$real_path" ]; then 170 | # 兩者都失敗,手動解析 171 | if [[ "$path" = /* ]]; then 172 | real_path="$path" 173 | else 174 | real_path="$(pwd)/$path" 175 | fi 176 | real_path=$(normalize_path "$real_path") 177 | fi 178 | else 179 | # 如果路徑不存在,嘗試解析相對路徑 180 | # If path doesn't exist, try to resolve relative path 181 | if [[ "$path" = /* ]]; then 182 | real_path="$path" 183 | else 184 | real_path="$(pwd)/$path" 185 | fi 186 | real_path=$(normalize_path "$real_path") 187 | fi 188 | 189 | # 移除結尾的斜線以便比較 190 | # Remove trailing slash for comparison 191 | real_path="${real_path%/}" 192 | 193 | # 檢查是否與受保護目錄完全符合 194 | # Check if exactly matches protected directories 195 | for protected in "${PROTECTED_DIRS[@]}"; do 196 | protected="${protected%/}" 197 | if [ "$real_path" = "$protected" ]; then 198 | return 0 # 是受保護的目錄 199 | fi 200 | done 201 | 202 | # 檢查是否符合受保護的模式 203 | # Check if matches protected patterns 204 | local basename_path=$(basename "$real_path") 205 | for pattern in "${PROTECTED_PATTERNS[@]}"; do 206 | pattern="${pattern%/}" 207 | if [[ "$basename_path" = ${pattern} ]] || [[ "$real_path" = */${pattern} ]] || [[ "$real_path" = ${pattern} ]]; then 208 | return 0 # 符合受保護模式 209 | fi 210 | done 211 | 212 | return 1 # 不是受保護的目錄 213 | } 214 | 215 | # 顯示使用說明 216 | # Display usage information 217 | show_help() { 218 | cat << EOF 219 | better-rm - 更安全的 rm 命令替代方案 220 | A safer alternative to the rm command 221 | 222 | 用法 (Usage): rm [選項] [檔案或目錄...] 223 | 224 | 選項 (Options): 225 | -r, -R, --recursive 遞迴刪除目錄及其內容 226 | -f, --force 強制刪除,忽略不存在的檔案,不提示 227 | -i 每次刪除前提示確認 228 | -I 刪除超過三個檔案或遞迴刪除前提示一次 229 | -v, --verbose 顯示詳細操作過程 230 | --help 顯示此說明訊息 231 | --version 顯示版本資訊 232 | 233 | 環境變數 (Environment Variables): 234 | TRASH_DIR 垃圾桶目錄位置 (預設: ~/.Trash) 235 | 236 | 說明 (Description): 237 | 此命令會將檔案移至垃圾桶而非永久刪除。 238 | 垃圾桶位於 $TRASH_DIR 目錄。 239 | 240 | This command moves files to trash instead of permanent deletion. 241 | Trash is located at $TRASH_DIR directory. 242 | 243 | 範例 (Examples): 244 | rm file.txt # 刪除單一檔案 245 | rm -r directory/ # 刪除目錄 246 | rm -rf old_project/ # 強制刪除目錄 247 | TRASH_DIR=/tmp/trash rm file # 使用自訂垃圾桶目錄 248 | 249 | EOF 250 | } 251 | 252 | # 顯示版本資訊 253 | # Display version information 254 | show_version() { 255 | echo "better-rm 1.1.0" 256 | echo "更安全的 rm 命令 - 將檔案移至垃圾桶而非永久刪除" 257 | echo "A safer rm command - moves files to trash instead of permanent deletion" 258 | } 259 | 260 | # 移動檔案至垃圾桶 261 | # Move file to trash 262 | move_to_trash() { 263 | local source="$1" 264 | local verbose="$2" 265 | 266 | # 檢查檔案或目錄是否存在 267 | # Check if file or directory exists 268 | if [ ! -e "$source" ] && [ ! -L "$source" ]; then 269 | if [ "$force_mode" != "1" ]; then 270 | error_msg "無法移除 '$source': 沒有此一檔案或目錄" 271 | error_msg "cannot remove '$source': No such file or directory" 272 | return 1 273 | fi 274 | return 0 # 在強制模式下,忽略不存在的檔案 275 | fi 276 | 277 | # 檢查是否為受保護的目錄 278 | # Check if it's a protected directory 279 | if is_protected "$source"; then 280 | error_msg "拒絕刪除受保護的目錄: '$source'" 281 | error_msg "Refused to remove protected directory: '$source'" 282 | error_msg "這是一個重要的系統目錄或專案目錄!" 283 | error_msg "This is a critical system or project directory!" 284 | return 1 285 | fi 286 | 287 | # 取得絕對路徑 288 | # Get absolute path 289 | local abs_path 290 | if [ -e "$source" ] || [ -L "$source" ]; then 291 | abs_path=$(readlink -f "$source" 2>/dev/null) 292 | if [ -z "$abs_path" ]; then 293 | abs_path=$(realpath "$source" 2>/dev/null) 294 | fi 295 | if [ -z "$abs_path" ]; then 296 | # 手動計算絕對路徑 297 | if [[ "$source" = /* ]]; then 298 | abs_path="$source" 299 | else 300 | abs_path="$(cd "$(dirname "$source")" 2>/dev/null && pwd)/$(basename "$source")" 301 | fi 302 | fi 303 | else 304 | if [[ "$source" = /* ]]; then 305 | abs_path="$source" 306 | else 307 | abs_path="$(pwd)/$source" 308 | fi 309 | fi 310 | 311 | # 建立垃圾桶目錄結構 312 | # Create trash directory structure 313 | local trash_path="$TRASH_DIR$(dirname "$abs_path")" 314 | mkdir -p "$trash_path" 2>/dev/null || { 315 | error_msg "無法建立垃圾桶目錄: $trash_path" 316 | error_msg "Failed to create trash directory: $trash_path" 317 | return 1 318 | } 319 | 320 | # 計算檔案內容的 hash 值 321 | # Calculate hash of file content 322 | local file_hash="" 323 | if [ -f "$source" ] && [ ! -L "$source" ]; then 324 | # 如果是普通檔案(非符號連結),計算 hash 325 | # If it's a regular file (not a symlink), calculate hash 326 | file_hash=$(md5sum "$source" 2>/dev/null | cut -d' ' -f1) 327 | if [ -z "$file_hash" ]; then 328 | # 如果 md5sum 失敗,使用 sha256sum 329 | # If md5sum fails, use sha256sum 330 | file_hash=$(sha256sum "$source" 2>/dev/null | cut -d' ' -f1) 331 | fi 332 | elif [ -d "$source" ] && [ ! -L "$source" ]; then 333 | # 如果是目錄,計算目錄內容的 hash 334 | # If it's a directory, calculate hash of directory contents 335 | # 使用 -print0 和 sort -z 以安全處理特殊字元檔名 336 | # Use -print0 and sort -z to safely handle filenames with special characters 337 | # 使用 -r 選項以正確處理空目錄 338 | # Use -r option to correctly handle empty directories 339 | file_hash=$(find "$source" -type f -print0 2>/dev/null | sort -z | xargs -0 -r md5sum 2>/dev/null | md5sum | cut -d' ' -f1) 340 | if [ -z "$file_hash" ]; then 341 | file_hash=$(find "$source" -type f -print0 2>/dev/null | sort -z | xargs -0 -r sha256sum 2>/dev/null | sha256sum | cut -d' ' -f1) 342 | fi 343 | fi 344 | 345 | # 如果無法計算 hash,使用預設值 346 | # If hash calculation fails, use default value 347 | if [ -z "$file_hash" ]; then 348 | file_hash="nohash" 349 | fi 350 | 351 | # 生成刪除時間戳記(包含奈秒以避免碰撞) 352 | # Generate deletion timestamp (with nanoseconds to avoid collisions) 353 | local timestamp=$(date +%Y%m%d_%H%M%S_%N) 354 | 355 | # 目標路徑,加上時間戳記和 hash 356 | # Target path with timestamp and hash 357 | local target="$TRASH_DIR$abs_path" 358 | target="${target}__${timestamp}__${file_hash}" 359 | 360 | # 判斷檔案類型(在移動之前) 361 | # Determine file type (before moving) 362 | local file_type="unknown" 363 | if [ -d "$source" ] && [ ! -L "$source" ]; then 364 | file_type="directory" 365 | elif [ -L "$source" ]; then 366 | file_type="symlink" 367 | elif [ -f "$source" ]; then 368 | file_type="file" 369 | fi 370 | 371 | # 移動檔案到垃圾桶 372 | # Move file to trash 373 | if mv "$source" "$target" 2>/dev/null; then 374 | # 記錄刪除操作到日誌 375 | # Log the deletion operation 376 | log_deletion "$abs_path" "$target" "$file_hash" "$timestamp" "$file_type" 377 | 378 | if [ "$verbose" = "1" ]; then 379 | success_msg "已移除 '$source' (moved to trash: $target)" 380 | fi 381 | return 0 382 | else 383 | error_msg "無法移除 '$source'" 384 | error_msg "Failed to remove '$source'" 385 | return 1 386 | fi 387 | } 388 | 389 | # 主程式 390 | # Main program 391 | main() { 392 | # 參數解析變數 393 | # Parameter parsing variables 394 | local recursive=0 395 | local force_mode=0 396 | local interactive=0 397 | local interactive_once=0 398 | local verbose=0 399 | local files=() 400 | 401 | # 如果沒有參數,顯示錯誤 402 | # If no arguments, show error 403 | if [ $# -eq 0 ]; then 404 | error_msg "缺少操作對象" 405 | error_msg "missing operand" 406 | echo "Try 'rm --help' for more information." >&2 407 | return 1 408 | fi 409 | 410 | # 解析參數 411 | # Parse arguments 412 | while [ $# -gt 0 ]; do 413 | case "$1" in 414 | --help) 415 | show_help 416 | return 0 417 | ;; 418 | --version) 419 | show_version 420 | return 0 421 | ;; 422 | -r|-R|--recursive) 423 | recursive=1 424 | shift 425 | ;; 426 | -f|--force) 427 | force_mode=1 428 | interactive=0 # -f 覆蓋 -i 429 | shift 430 | ;; 431 | -i) 432 | interactive=1 433 | force_mode=0 # -i 覆蓋 -f 434 | shift 435 | ;; 436 | -I) 437 | interactive_once=1 438 | shift 439 | ;; 440 | -v|--verbose) 441 | verbose=1 442 | shift 443 | ;; 444 | -rf|-fr|-Rf|-fR|-RF|-Fr|-rF|-RF) 445 | # 組合參數處理 - 只處理常見的 rf 組合 446 | # Handle combined parameters - only common rf combinations 447 | recursive=1 448 | force_mode=1 449 | interactive=0 450 | shift 451 | ;; 452 | -*) 453 | # 處理其他組合參數 454 | # Handle other combined parameters 455 | local arg="$1" 456 | shift 457 | local i 458 | local has_f=0 459 | local has_i=0 460 | local f_pos=-1 461 | local i_pos=-1 462 | 463 | # 第一次掃描:找出 f 和 i 的位置 464 | for ((i=1; i<${#arg}; i++)); do 465 | case "${arg:$i:1}" in 466 | f) has_f=1; f_pos=$i ;; 467 | i) has_i=1; i_pos=$i ;; 468 | esac 469 | done 470 | 471 | # 第二次掃描:處理所有選項 472 | for ((i=1; i<${#arg}; i++)); do 473 | case "${arg:$i:1}" in 474 | r|R) recursive=1 ;; 475 | f) 476 | # 如果 i 也存在且在 f 之後,則 i 優先 477 | if [ $has_i -eq 1 ] && [ $i_pos -gt $f_pos ]; then 478 | : # 什麼都不做,讓 i 處理 479 | else 480 | force_mode=1 481 | interactive=0 482 | fi 483 | ;; 484 | i) 485 | # 如果 f 也存在且在 i 之後,則 f 優先 486 | if [ $has_f -eq 1 ] && [ $f_pos -gt $i_pos ]; then 487 | : # 什麼都不做,讓 f 處理 488 | else 489 | interactive=1 490 | force_mode=0 491 | fi 492 | ;; 493 | I) interactive_once=1 ;; 494 | v) verbose=1 ;; 495 | *) 496 | error_msg "無效的選項 -- '${arg:$i:1}'" 497 | error_msg "invalid option -- '${arg:$i:1}'" 498 | echo "Try 'rm --help' for more information." >&2 499 | return 1 500 | ;; 501 | esac 502 | done 503 | ;; 504 | *) 505 | files+=("$1") 506 | shift 507 | ;; 508 | esac 509 | done 510 | 511 | # 檢查是否有指定檔案 512 | # Check if files are specified 513 | if [ ${#files[@]} -eq 0 ]; then 514 | error_msg "缺少操作對象" 515 | error_msg "missing operand" 516 | echo "Try 'rm --help' for more information." >&2 517 | return 1 518 | fi 519 | 520 | # -I 選項:當刪除超過 3 個檔案時提示一次 521 | # -I option: prompt once when removing more than 3 files 522 | if [ "$interactive_once" = "1" ] && [ ${#files[@]} -gt 3 ]; then 523 | echo -n "rm: remove ${#files[@]} arguments? " >&2 524 | read -r response 525 | if [[ ! "$response" =~ ^[yY] ]]; then 526 | return 0 527 | fi 528 | fi 529 | 530 | # 確保垃圾桶目錄存在 531 | # Ensure trash directory exists 532 | if [ ! -d "$TRASH_DIR" ]; then 533 | mkdir -p "$TRASH_DIR" || { 534 | error_msg "無法建立垃圾桶目錄: $TRASH_DIR" 535 | error_msg "Failed to create trash directory: $TRASH_DIR" 536 | return 1 537 | } 538 | fi 539 | 540 | # 處理每個檔案 541 | # Process each file 542 | local status=0 543 | for file in "${files[@]}"; do 544 | # 檢查是否為目錄 545 | # Check if it's a directory 546 | if [ -d "$file" ] && [ ! -L "$file" ]; then 547 | if [ "$recursive" != "1" ]; then 548 | error_msg "無法移除 '$file': 是一個目錄" 549 | error_msg "cannot remove '$file': Is a directory" 550 | status=1 551 | continue 552 | fi 553 | fi 554 | 555 | # 互動式確認 556 | # Interactive confirmation 557 | if [ "$interactive" = "1" ]; then 558 | local prompt 559 | if [ -d "$file" ] && [ ! -L "$file" ]; then 560 | prompt="rm: descend into directory '$file'? " 561 | else 562 | prompt="rm: remove file '$file'? " 563 | fi 564 | echo -n "$prompt" >&2 565 | read -r response 566 | if [[ ! "$response" =~ ^[yY] ]]; then 567 | continue 568 | fi 569 | fi 570 | 571 | # 移動到垃圾桶 572 | # Move to trash 573 | if ! move_to_trash "$file" "$verbose"; then 574 | status=1 575 | fi 576 | done 577 | 578 | return $status 579 | } 580 | 581 | # 執行主程式 582 | # Execute main program 583 | main "$@" 584 | --------------------------------------------------------------------------------