可以通过 Python 脚本实现简易回收站功能,用于在 Linux 服务器中替代 rm 命令,避免文件的误删除。实现原理很简单:将期望删除的文件或目录移动到 ~/.trash 回收站,而非直接删除。

功能说明

  • 文件被移动到 ~/.trash/日期/时分秒-随机数/ 目录下
  • 每月生成独立的日志文件 ~/.trash/YYYYmm-trash.log,记录删除操作
  • 支持文件和目录的移动
  • 自动处理重名冲突(通过随机后缀)

使用方法

1
python3 rm2trash.py [选项] 文件/目录...

选项

参数 说明
-i, --interactive 删除前逐一确认
-r, --recursive 递归删除目录及其内容(非空目录必须使用)
-q, --quiet 静默模式,不输出移动信息

示例

1
2
3
4
5
6
7
8
9
10
11
# 删除单个文件
python3 rm2trash.py test.txt

# 递归删除目录(非空必须加 -r)
python3 rm2trash.py -r my_folder/

# 交互式删除,逐个确认
python3 rm2trash.py -i file1.txt file2.txt

# 组合使用
python3 rm2trash.py -rq old_backup/

注意事项

  • 删除非空目录必须使用 -r 参数,否则会提示错误
  • 被移动的文件保留了原始文件名,可在 ~/.trash 中查找恢复

源码

rm2trash.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env python3

import os
import shutil
from datetime import datetime
import argparse
import random


HOME_DIR = os.path.expanduser("~")
TRASH_DIR = os.path.join(HOME_DIR, ".trash")
EXE_NAME = "rm2trash"


def ensure_trash_dir():
"""Ensure the main trash directory exists."""
if not os.path.exists(TRASH_DIR):
os.makedirs(TRASH_DIR)


def create_trash_subdir():
"""Create a subdirectory in trash based on date and time, avoiding name collisions."""
date_dir = datetime.now().strftime("%Y%m%d")
time_random_dir = datetime.now().strftime("%H%M%S") + f"-{random.randint(100, 999)}"
full_path = os.path.join(TRASH_DIR, date_dir, time_random_dir)
os.makedirs(full_path, exist_ok=True)
return full_path


def log_operation(*, log_path, timestamp, cwd, command, moved_items):
"""Log the operation details to the log file."""
with open(log_path, "a") as log_file:
log_file.write(f"Timestamp: {timestamp}\n")
log_file.write(f"Current Directory: {cwd}\n")
log_file.write(f"Command: {command}\n")
for original_path, target_path in moved_items:
log_file.write(f"Moved '{original_path}' to '{target_path}'\n")
log_file.write("\n")


def confirm_delete(file_path):
response = input(f"Are you sure you want to move '{file_path}' to trash? (y/n): ")
return response.lower() in ["y", "yes"]


def move_to_trash(
file_path,
trash_path,
moved_items,
recursive=False,
interactive=False,
quiet=False,
):
# File or directory doesn't exist
if not os.path.exists(file_path):
print(f"{EXE_NAME}: cannot remove '{file_path}': No such file or directory.")
return

# Remove trailing '/' if directory
if os.path.isdir(file_path):
file_path = os.path.normpath(file_path)

# Non-empty directory requires -r flag
if os.path.isdir(file_path) and os.listdir(file_path):
if not recursive:
print(
f"{EXE_NAME}: cannot remove '{file_path}': Directory not empty. Use -r to remove it."
)
return

# Confirm deletion if interactive mode is enabled
if interactive and not confirm_delete(file_path): # Not confirmed
print(f"Skipped '{file_path}'.")
return

try:
target_path = os.path.join(trash_path, os.path.basename(file_path))
if os.path.isdir(file_path): # Directory handling
shutil.copytree(file_path, target_path)
shutil.rmtree(file_path)
else: # File or empty directory
shutil.move(file_path, target_path)

moved_items.append((file_path, target_path))
if not quiet:
print(f"{EXE_NAME}: Moved '{file_path}' to trash at '{target_path}'.")

except Exception as e:
print(f"{EXE_NAME}: Failed to move '{file_path}' to trash. Error: {e}")
if os.path.isdir(trash_path) and not os.listdir(trash_path):
os.rmdir(trash_path)


def args_parse():
parser = argparse.ArgumentParser(
description="Move files and directories to ~/.trash instead of deleting them."
)
parser.add_argument(
"files", nargs="+", help="Files or directories to move to trash."
)
parser.add_argument(
"-i", "--interactive", action="store_true", help="Prompt before every removal."
)
parser.add_argument(
"-r",
"--recursive",
action="store_true",
help="Remove directories and their contents recursively.",
)
parser.add_argument(
"-q", "--quiet", action="store_true", help="Suppress output for moved items."
)
return parser.parse_args()


def main():
args = args_parse()

timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cwd = os.getcwd()
command = " ".join([EXE_NAME] + args.files)
log_file_path = os.path.join(
TRASH_DIR, f"{datetime.now().strftime('%Y%m')}-trash.log"
)

ensure_trash_dir()
trash_path = create_trash_subdir()
moved_items = []

for file in args.files:
move_to_trash(
file,
trash_path=trash_path,
moved_items=moved_items,
recursive=args.recursive,
interactive=args.interactive,
quiet=args.quiet,
)

if moved_items:
log_operation(
log_path=log_file_path,
timestamp=timestamp,
cwd=cwd,
command=command,
moved_items=moved_items,
)

if os.path.isdir(trash_path) and not os.listdir(trash_path):
os.rmdir(trash_path)


if __name__ == "__main__":
main()