safe-rm 1.0.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.eslintrc.js +9 -0
- package/.github/workflows/nodejs.yml +27 -0
- package/.safe-rm/config +35 -0
- package/README.md +119 -24
- package/bin/rm.sh +460 -126
- package/package.json +26 -3
- package/test/cases.js +275 -0
- package/test/helper.js +178 -0
- package/test/rm.test.js +8 -0
- package/test/safe-rm-as.test.js +11 -0
- package/test/safe-rm.test.js +7 -0
- package/test/argument.sh +0 -5
- package/test/fold.sh +0 -39
- package/test/for.sh +0 -22
- package/test/nested-switch.sh +0 -72
package/bin/rm.sh
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
DEFAULT_TRASH="$HOME/.Trash"
|
|
6
|
-
if [[ "$(uname -s)" == "Linux" ]]; then
|
|
7
|
-
DEFAULT_TRASH="$HOME/.local/share/Trash/files"
|
|
8
|
-
fi
|
|
3
|
+
# Basic configuration
|
|
4
|
+
# ------------------------------------------------------------------------------
|
|
9
5
|
|
|
6
|
+
DEFAULT_SAFE_RM_CONFIG="$HOME/.safe-rm/config"
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
# You could modify the location of the configuration file by using
|
|
9
|
+
# ```sh
|
|
10
|
+
# $ export SAFE_RM_CONFIG=/path/to/safe-rm.conf
|
|
11
|
+
# ```
|
|
12
|
+
SAFE_RM_CONFIG=${SAFE_RM_CONFIG:="$DEFAULT_SAFE_RM_CONFIG"}
|
|
13
|
+
|
|
14
|
+
if [[ -f "$SAFE_RM_CONFIG" ]]; then
|
|
15
|
+
source "$SAFE_RM_CONFIG"
|
|
16
|
+
fi
|
|
12
17
|
|
|
18
|
+
SAFE_RM_CONFIG_ROOT=${SAFE_RM_CONFIG_ROOT:="$HOME/.safe-rm"}
|
|
13
19
|
|
|
14
20
|
# Print debug info or not
|
|
15
21
|
SAFE_RM_DEBUG=${SAFE_RM_DEBUG:=}
|
|
16
|
-
# -------------------------------------------------------------------------------
|
|
17
22
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
# Whether to delete files in the trash permanently, defaults to NO
|
|
24
|
+
if [[ ${SAFE_RM_PERM_DEL_FILES_IN_TRASH:0:1} =~ [yY] ]]; then
|
|
25
|
+
SAFE_RM_PERM_DEL_FILES_IN_TRASH=1
|
|
26
|
+
else
|
|
27
|
+
SAFE_RM_PERM_DEL_FILES_IN_TRASH=
|
|
28
|
+
fi
|
|
23
29
|
|
|
24
|
-
GUID=0
|
|
25
|
-
TIME=
|
|
26
|
-
date_time(){
|
|
27
|
-
TIME=$(date +%Y-%m-%d_%H:%M:%S)-$GUID
|
|
28
|
-
(( GUID += 1 ))
|
|
29
|
-
}
|
|
30
30
|
|
|
31
|
-
# tools
|
|
32
31
|
debug(){
|
|
33
32
|
if [[ -n "$SAFE_RM_DEBUG" ]]; then
|
|
34
33
|
echo "[D] $@" >&2
|
|
@@ -36,7 +35,124 @@ debug(){
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
error(){
|
|
39
|
+
echo $@ >&2
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# global exit code, default to 0
|
|
44
|
+
EXIT_CODE=0
|
|
45
|
+
|
|
46
|
+
# Usage:
|
|
47
|
+
# ```
|
|
48
|
+
# do_exit $LINENO
|
|
49
|
+
# do_exit $LINENO 1
|
|
50
|
+
# ```
|
|
51
|
+
do_exit(){
|
|
52
|
+
local line=$1
|
|
53
|
+
local code=$EXIT_CODE
|
|
54
|
+
|
|
55
|
+
if [[ $# -eq 2 ]]; then
|
|
56
|
+
code=$2
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Exit immediately
|
|
60
|
+
debug "$line: exit code $code"
|
|
61
|
+
exit $code
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if [[ "$(uname -s)" == "Darwin"* && -z $SAFE_RM_DEBUG_LINUX ]]; then
|
|
66
|
+
OS_TYPE="MacOS"
|
|
67
|
+
DEFAULT_TRASH="$HOME/.Trash"
|
|
68
|
+
else
|
|
69
|
+
OS_TYPE="Linux"
|
|
70
|
+
DEFAULT_TRASH="$HOME/.local/share/Trash"
|
|
71
|
+
SAFE_RM_USE_APPLESCRIPT=
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# The target trash directory to dispose files and directories,
|
|
76
|
+
# defaults to the system trash directory
|
|
77
|
+
SAFE_RM_TRASH=${SAFE_RM_TRASH:="$DEFAULT_TRASH"}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if [[ "$OS_TYPE" == "MacOS" ]]; then
|
|
81
|
+
if command -v osascript &> /dev/null; then
|
|
82
|
+
# `SAFE_RM_USE_APPLESCRIPT=no` in your SAFE_RM_CONFIG file
|
|
83
|
+
# to disable AppleScript
|
|
84
|
+
if [[ "$SAFE_RM_USE_APPLESCRIPT" == "no" ]]; then
|
|
85
|
+
debug "$LINENO: applescript disabled by conf"
|
|
86
|
+
SAFE_RM_USE_APPLESCRIPT=
|
|
87
|
+
|
|
88
|
+
elif [[ "$SAFE_RM_TRASH" == "$DEFAULT_TRASH" ]]; then
|
|
89
|
+
debug "$LINENO: applescript enabled"
|
|
90
|
+
SAFE_RM_USE_APPLESCRIPT=1
|
|
91
|
+
else
|
|
92
|
+
debug "$LINENO: applescript disabled due to custom trash"
|
|
93
|
+
SAFE_RM_USE_APPLESCRIPT=
|
|
94
|
+
fi
|
|
95
|
+
else
|
|
96
|
+
SAFE_RM_USE_APPLESCRIPT=
|
|
97
|
+
fi
|
|
98
|
+
else
|
|
99
|
+
if mkdir -p "$SAFE_RM_TRASH/files" &> /dev/null; then
|
|
100
|
+
debug "$LINENO: linux trash enabled"
|
|
101
|
+
else
|
|
102
|
+
error "$COMMAND: failed to create trash directory $SAFE_RM_TRASH/files"
|
|
103
|
+
do_exit $LINENO 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if mkdir -p "$SAFE_RM_TRASH/info" &> /dev/null; then
|
|
107
|
+
:
|
|
108
|
+
else
|
|
109
|
+
error "$COMMAND: failed to create trash info directory $SAFE_RM_TRASH/info"
|
|
110
|
+
do_exit $LINENO 1
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
SAFE_RM_PROTECTED_RULES="${SAFE_RM_CONFIG_ROOT}/.gitignore"
|
|
116
|
+
|
|
117
|
+
debug $SAFE_RM_PROTECTED_RULES
|
|
118
|
+
|
|
119
|
+
# But if it is not a file
|
|
120
|
+
if [[ -f "$SAFE_RM_PROTECTED_RULES" ]]; then
|
|
121
|
+
if command -v git &> /dev/null; then
|
|
122
|
+
debug "$LINENO: protected rules enabled: $SAFE_RM_PROTECTED_RULES"
|
|
123
|
+
|
|
124
|
+
if git -C "$SAFE_RM_CONFIG_ROOT" rev-parse --is-inside-work-tree &> /dev/null; then
|
|
125
|
+
:
|
|
126
|
+
else
|
|
127
|
+
error "[WARNING] safe-rm requires a git repository to use protected rules"
|
|
128
|
+
error "Initializing a git repository in \"$SAFE_RM_CONFIG_ROOT\" ..."
|
|
129
|
+
|
|
130
|
+
git -C "$SAFE_RM_CONFIG_ROOT" init -q
|
|
131
|
+
|
|
132
|
+
error "Success"
|
|
133
|
+
fi
|
|
134
|
+
else
|
|
135
|
+
error "[WARNING] safe-rm requires git installed to use protected rules"
|
|
136
|
+
error " please install git"
|
|
137
|
+
error " or remove the file \"$SAFE_RM_PROTECTED_RULES\""
|
|
138
|
+
SAFE_RM_PROTECTED_RULES=
|
|
139
|
+
fi
|
|
140
|
+
else
|
|
141
|
+
SAFE_RM_PROTECTED_RULES=
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
# Simple basename: /bin/rm -> rm
|
|
148
|
+
COMMAND=${0##*/}
|
|
149
|
+
|
|
150
|
+
# pwd
|
|
151
|
+
__DIRNAME=$(pwd)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# parse argv
|
|
155
|
+
# ------------------------------------------------------------------------------
|
|
40
156
|
|
|
41
157
|
invalid_option(){
|
|
42
158
|
# if there's an invalid option, `rm` only takes the second char of the option string
|
|
@@ -48,19 +164,20 @@ invalid_option(){
|
|
|
48
164
|
}
|
|
49
165
|
|
|
50
166
|
usage(){
|
|
51
|
-
echo "usage: rm [-f | -i
|
|
52
|
-
echo " unlink file"
|
|
167
|
+
echo "usage: rm [-f | -i] [-dIRrv] file ..."
|
|
168
|
+
echo " unlink [--] file"
|
|
53
169
|
|
|
54
170
|
# if has an invalid option, exit with 64
|
|
55
171
|
exit 64
|
|
56
172
|
}
|
|
57
173
|
|
|
58
174
|
|
|
59
|
-
if [[ "$#"
|
|
175
|
+
if [[ "$#" == 0 ]]; then
|
|
60
176
|
echo "safe-rm"
|
|
61
177
|
usage
|
|
62
178
|
fi
|
|
63
179
|
|
|
180
|
+
|
|
64
181
|
ARG_END=
|
|
65
182
|
FILE_NAME=
|
|
66
183
|
ARG=
|
|
@@ -92,10 +209,15 @@ push_file(){
|
|
|
92
209
|
|
|
93
210
|
# pre-parse argument vector
|
|
94
211
|
while [[ -n $1 ]]; do
|
|
95
|
-
#
|
|
212
|
+
# Case 1:
|
|
96
213
|
# rm -v abc -r --force
|
|
97
214
|
# -> -r will be ignored
|
|
98
215
|
# -> args: ['-v'], files: ['abc', '-r', 'force']
|
|
216
|
+
|
|
217
|
+
# Case 2:
|
|
218
|
+
# rm -- -r
|
|
219
|
+
# -> -r will be treated as a file
|
|
220
|
+
# -> args: [], files: ['-r']
|
|
99
221
|
if [[ -n $ARG_END ]]; then
|
|
100
222
|
push_file "$1"
|
|
101
223
|
|
|
@@ -112,24 +234,24 @@ while [[ -n $1 ]]; do
|
|
|
112
234
|
# Regex in bash is not perl regex,
|
|
113
235
|
# in which `'*'` means "anything" (including nothing)
|
|
114
236
|
-[a-zA-Z]*)
|
|
115
|
-
split_push_arg $1; debug "short option $1"
|
|
237
|
+
split_push_arg $1; debug "$LINENO: short option $1"
|
|
116
238
|
;;
|
|
117
239
|
|
|
118
240
|
# rm --force a
|
|
119
241
|
--[a-zA-Z]*)
|
|
120
|
-
push_arg $1; debug "option $1"
|
|
242
|
+
push_arg $1; debug "$LINENO: option $1"
|
|
121
243
|
;;
|
|
122
244
|
|
|
123
245
|
# rm -- -a
|
|
124
246
|
--)
|
|
125
|
-
ARG_END=1; debug "divider"
|
|
247
|
+
ARG_END=1; debug "$LINENO: divider"
|
|
126
248
|
;;
|
|
127
249
|
|
|
128
250
|
# case:
|
|
129
251
|
# rm -
|
|
130
252
|
# -> args: [], files: ['-']
|
|
131
253
|
*)
|
|
132
|
-
push_file "$1"; debug "file $1"
|
|
254
|
+
push_file "$1"; debug "$LINENO: file $1"
|
|
133
255
|
ARG_END=1
|
|
134
256
|
;;
|
|
135
257
|
esac
|
|
@@ -144,9 +266,7 @@ OPT_INTERACTIVE=
|
|
|
144
266
|
OPT_INTERACTIVE_ONCE=
|
|
145
267
|
OPT_RECURSIVE=
|
|
146
268
|
OPT_VERBOSE=
|
|
147
|
-
|
|
148
|
-
# global exit code, default to 0
|
|
149
|
-
EXIT_CODE=0
|
|
269
|
+
OPT_EMPTY_DIR=
|
|
150
270
|
|
|
151
271
|
# parse options
|
|
152
272
|
for arg in ${ARG[@]}; do
|
|
@@ -159,29 +279,33 @@ for arg in ${ARG[@]}; do
|
|
|
159
279
|
# ;;
|
|
160
280
|
|
|
161
281
|
-f|--force)
|
|
162
|
-
OPT_FORCE=1; debug "force : $arg"
|
|
282
|
+
OPT_FORCE=1; debug "$LINENO: force : $arg"
|
|
163
283
|
;;
|
|
164
284
|
|
|
165
285
|
# interactive=always
|
|
166
286
|
-i|--interactive|--interactive=always)
|
|
167
|
-
OPT_INTERACTIVE=1; debug "interactive : $arg"
|
|
287
|
+
OPT_INTERACTIVE=1; debug "$LINENO: interactive : $arg"
|
|
168
288
|
OPT_INTERACTIVE_ONCE=
|
|
169
289
|
;;
|
|
170
290
|
|
|
171
291
|
# interactive=once. interactive=once and interactive=always are exclusive
|
|
172
292
|
-I|--interactive=once)
|
|
173
|
-
OPT_INTERACTIVE_ONCE=1; debug "interactive_once : $arg"
|
|
293
|
+
OPT_INTERACTIVE_ONCE=1; debug "$LINENO: interactive_once : $arg"
|
|
174
294
|
OPT_INTERACTIVE=;
|
|
175
295
|
;;
|
|
176
296
|
|
|
177
297
|
# both r and R is allowed
|
|
178
298
|
-[rR]|--[rR]ecursive)
|
|
179
|
-
OPT_RECURSIVE=1; debug "recursive : $arg"
|
|
299
|
+
OPT_RECURSIVE=1; debug "$LINENO: recursive : $arg"
|
|
180
300
|
;;
|
|
181
301
|
|
|
182
302
|
# only lowercase v is allowed
|
|
183
303
|
-v|--verbose)
|
|
184
|
-
OPT_VERBOSE=1; debug "verbose : $arg"
|
|
304
|
+
OPT_VERBOSE=1; debug "$LINENO: verbose : $arg"
|
|
305
|
+
;;
|
|
306
|
+
|
|
307
|
+
-d|--directory)
|
|
308
|
+
OPT_EMPTY_DIR=1; debug "$LINENO: empty dir : $arg"
|
|
185
309
|
;;
|
|
186
310
|
|
|
187
311
|
*)
|
|
@@ -189,7 +313,8 @@ for arg in ${ARG[@]}; do
|
|
|
189
313
|
;;
|
|
190
314
|
esac
|
|
191
315
|
done
|
|
192
|
-
# /parse argv
|
|
316
|
+
# /parse argv
|
|
317
|
+
# ------------------------------------------------------------------------------
|
|
193
318
|
|
|
194
319
|
|
|
195
320
|
# make sure recycled bin exists
|
|
@@ -198,7 +323,7 @@ if [[ ! -e $SAFE_RM_TRASH ]]; then
|
|
|
198
323
|
echo -n "(yes/no): "
|
|
199
324
|
|
|
200
325
|
read answer
|
|
201
|
-
if [[ $answer
|
|
326
|
+
if [[ $answer == "yes" || ! -n $anwser ]]; then
|
|
202
327
|
mkdir -p "$SAFE_RM_TRASH"
|
|
203
328
|
else
|
|
204
329
|
echo "Canceled!"
|
|
@@ -206,59 +331,79 @@ if [[ ! -e $SAFE_RM_TRASH ]]; then
|
|
|
206
331
|
fi
|
|
207
332
|
fi
|
|
208
333
|
|
|
334
|
+
|
|
335
|
+
check_return_status(){
|
|
336
|
+
local status=$?
|
|
337
|
+
if [[ $status -ne "0" ]]; then
|
|
338
|
+
debug "$LINENO: last command returned status $status"
|
|
339
|
+
EXIT_CODE=$status
|
|
340
|
+
fi
|
|
341
|
+
}
|
|
342
|
+
|
|
209
343
|
# try to remove a file or directory
|
|
210
344
|
remove(){
|
|
211
345
|
local file=$1
|
|
212
346
|
|
|
213
347
|
# if is dir
|
|
214
|
-
if [[ -d $file ]]; then
|
|
348
|
+
if [[ -d "$file" && ! -L "$file" ]]; then
|
|
215
349
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
echo "$COMMAND: $file: Invalid argument"
|
|
225
|
-
return 1
|
|
226
|
-
fi
|
|
227
|
-
|
|
228
|
-
if [[ $OPT_INTERACTIVE = 1 ]]; then
|
|
229
|
-
echo -n "examine files in directory $file? "
|
|
230
|
-
read answer
|
|
350
|
+
# if a directory, and without '-r' option
|
|
351
|
+
if [[ ! -n $OPT_RECURSIVE ]]; then
|
|
352
|
+
# if with '-d' option, and is an empty dir
|
|
353
|
+
if [[ -n $OPT_EMPTY_DIR && ! $(ls -A "$file") ]]; then
|
|
354
|
+
debug "$LINENO: trash an empty directory $file"
|
|
355
|
+
trash "$file"
|
|
356
|
+
return
|
|
357
|
+
fi
|
|
231
358
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
359
|
+
debug "$LINENO: $file: is a directory"
|
|
360
|
+
error "$COMMAND: $file: is a directory"
|
|
361
|
+
return 1
|
|
362
|
+
fi
|
|
235
363
|
|
|
236
|
-
|
|
237
|
-
|
|
364
|
+
if [[ "$file" == './' ]]; then
|
|
365
|
+
echo "$COMMAND: $file: Invalid argument"
|
|
366
|
+
return 1
|
|
367
|
+
fi
|
|
238
368
|
|
|
239
|
-
|
|
240
|
-
echo -n "
|
|
369
|
+
if [[ "$OPT_INTERACTIVE" == 1 ]]; then
|
|
370
|
+
echo -n "examine files in directory $file? "
|
|
241
371
|
read answer
|
|
372
|
+
|
|
373
|
+
# actually, as long as the answer start with 'y', the file will be removed
|
|
374
|
+
# default to no remove
|
|
242
375
|
if [[ ${answer:0:1} =~ [yY] ]]; then
|
|
243
|
-
[[ $(ls -A "$file") ]] && {
|
|
244
|
-
echo "$COMMAND: $file: Directory not empty"
|
|
245
376
|
|
|
246
|
-
|
|
377
|
+
# if choose to examine the dir, recursively check files first
|
|
378
|
+
recursive_remove "$file"
|
|
247
379
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
380
|
+
# interact with the dir at last
|
|
381
|
+
echo -n "remove $file? "
|
|
382
|
+
read answer
|
|
383
|
+
if [[ ${answer:0:1} =~ [yY] ]]; then
|
|
384
|
+
[[ $(ls -A "$file") ]] && {
|
|
385
|
+
echo "$COMMAND: $file: Directory not empty"
|
|
386
|
+
|
|
387
|
+
return 1
|
|
388
|
+
|
|
389
|
+
} || {
|
|
390
|
+
trash "$file"
|
|
391
|
+
debug "$LINENO: trash returned status $?"
|
|
392
|
+
}
|
|
393
|
+
fi
|
|
252
394
|
fi
|
|
395
|
+
else
|
|
396
|
+
# The file
|
|
397
|
+
# - is a symbolic link
|
|
398
|
+
# - is a file
|
|
399
|
+
# - does not exist
|
|
400
|
+
trash "$file"
|
|
401
|
+
debug "$LINENO: trash returned status $?"
|
|
253
402
|
fi
|
|
254
|
-
else
|
|
255
|
-
trash "$file"
|
|
256
|
-
debug "$LINENO: trash returned status $?"
|
|
257
|
-
fi
|
|
258
403
|
|
|
259
404
|
# if is a file
|
|
260
405
|
else
|
|
261
|
-
if [[ "$OPT_INTERACTIVE"
|
|
406
|
+
if [[ "$OPT_INTERACTIVE" == 1 ]]; then
|
|
262
407
|
echo -n "remove $file? "
|
|
263
408
|
read answer
|
|
264
409
|
if [[ ${answer:0:1} =~ [yY] ]]; then
|
|
@@ -288,55 +433,249 @@ recursive_remove(){
|
|
|
288
433
|
}
|
|
289
434
|
|
|
290
435
|
|
|
291
|
-
# trash a file or dir directly
|
|
292
436
|
trash(){
|
|
293
|
-
|
|
437
|
+
local target=$1
|
|
438
|
+
|
|
439
|
+
if [[ -n $SAFE_RM_PERM_DEL_FILES_IN_TRASH ]]; then
|
|
440
|
+
if [[ "$target" == "$SAFE_RM_TRASH"* ]]; then
|
|
441
|
+
# If the target is already in the trash, delete it permanently
|
|
442
|
+
/bin/rm -rf "$target"
|
|
443
|
+
else
|
|
444
|
+
do_trash "$target"
|
|
445
|
+
fi
|
|
446
|
+
else
|
|
447
|
+
do_trash "$target"
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
check_return_status
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
do_trash(){
|
|
455
|
+
local target=$1
|
|
456
|
+
|
|
457
|
+
debug "$LINENO: trash $target"
|
|
458
|
+
|
|
459
|
+
if is_protected "$target"; then
|
|
460
|
+
error "\"$target\" is protected by your configuration"
|
|
461
|
+
return 1
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
if [[ -n $SAFE_RM_USE_APPLESCRIPT ]]; then
|
|
465
|
+
applescript_trash "$target"
|
|
466
|
+
elif [[ "$OS_TYPE" == "MacOS" ]]; then
|
|
467
|
+
mac_trash "$target"
|
|
468
|
+
else
|
|
469
|
+
linux_trash "$target"
|
|
470
|
+
fi
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
get_absolute_path(){
|
|
475
|
+
echo $(cd "$(dirname "$1")" && pwd)/$(basename "$1")
|
|
476
|
+
}
|
|
294
477
|
|
|
295
|
-
# origin file path
|
|
296
|
-
local file=$1
|
|
297
478
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
479
|
+
# Returns
|
|
480
|
+
# - 0: the target is protected
|
|
481
|
+
# - 1: the target is not protected
|
|
482
|
+
is_protected(){
|
|
483
|
+
if [[ ! -n $SAFE_RM_PROTECTED_RULES ]]; then
|
|
484
|
+
# If no protected rules are set, the target is not protected
|
|
485
|
+
return 1
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
local target=$1
|
|
489
|
+
local abs_path=$(get_absolute_path "$target")
|
|
490
|
+
|
|
491
|
+
# /path/to/foo -> path/to/foo
|
|
492
|
+
local rel_path=${abs_path#/}
|
|
493
|
+
|
|
494
|
+
debug "$LINENO: check whether $rel_path is protected"
|
|
495
|
+
|
|
496
|
+
local ignored=$(git -C "$SAFE_RM_CONFIG_ROOT" check-ignore -v --no-index "$rel_path")
|
|
497
|
+
|
|
498
|
+
debug "$LINENO: git check-ignore result: $ignored"
|
|
499
|
+
|
|
500
|
+
if [[ -n "$ignored" ]]; then
|
|
501
|
+
return 0
|
|
502
|
+
else
|
|
503
|
+
return 1
|
|
504
|
+
fi
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
applescript_trash(){
|
|
509
|
+
local target=$1
|
|
510
|
+
|
|
511
|
+
[[ "$OPT_VERBOSE" == 1 ]] && list_files "$target"
|
|
512
|
+
|
|
513
|
+
debug "$LINENO: osascript delete $target"
|
|
514
|
+
|
|
515
|
+
osascript -e "tell application \"Finder\" to delete (POSIX file \"$target\" as alias)" &> /dev/null
|
|
516
|
+
|
|
517
|
+
# TODO: handle osascript errors
|
|
518
|
+
return 0
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
_short_time_ret=
|
|
523
|
+
short_time(){
|
|
524
|
+
_short_time_ret=$(date +%H.%M.%S)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
_mac_trash_path_ret=
|
|
528
|
+
check_mac_trash_path(){
|
|
529
|
+
local path=$1
|
|
530
|
+
local ext=$2
|
|
531
|
+
local full_path="$path$ext"
|
|
532
|
+
|
|
533
|
+
# if already in the trash
|
|
534
|
+
if [[ -e "$full_path" ]]; then
|
|
535
|
+
debug "$LINENO: $full_path already exists"
|
|
536
|
+
|
|
537
|
+
# renew $_short_time_ret
|
|
538
|
+
short_time
|
|
539
|
+
check_mac_trash_path "$path $_short_time_ret" "$ext"
|
|
540
|
+
else
|
|
541
|
+
_mac_trash_path_ret=$full_path
|
|
542
|
+
fi
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
_traveled=
|
|
547
|
+
_to_move=
|
|
548
|
+
|
|
549
|
+
check_target_to_move(){
|
|
550
|
+
_traveled=
|
|
551
|
+
_to_move=$1
|
|
302
552
|
|
|
303
553
|
# basename ./ -> .
|
|
304
554
|
# basename ../ -> ..
|
|
305
555
|
# basename ../abc -> abc
|
|
306
556
|
# basename ../.abc -> .abc
|
|
307
|
-
if [[ -d "$
|
|
308
|
-
#
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
557
|
+
if [[ -d "$_to_move" ]]; then
|
|
558
|
+
# We don't know whether a relative path is the pwd or not
|
|
559
|
+
if [[ "${_to_move:0:1}" == '.' || "$_to_move" == "$__DIRNAME" ]]; then
|
|
560
|
+
cd "$_to_move"
|
|
561
|
+
|
|
562
|
+
# pwd can't be piped?
|
|
563
|
+
local current=$(pwd)
|
|
564
|
+
_to_move=$(basename "$current")
|
|
565
|
+
|
|
566
|
+
# We can not `mv` a dir that is the pwd,
|
|
567
|
+
# or it will throw an "Operation not permitted" error,
|
|
568
|
+
# so we have to `cd` to the parent dir first
|
|
569
|
+
cd ..
|
|
570
|
+
_traveled=1
|
|
571
|
+
fi
|
|
316
572
|
fi
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
# trash a file or dir directly
|
|
576
|
+
mac_trash(){
|
|
577
|
+
check_target_to_move "$1"
|
|
578
|
+
local move=$_to_move
|
|
579
|
+
local base=$(basename "$move")
|
|
580
|
+
|
|
581
|
+
# foo.jpg => "foo" + ".jpg"
|
|
582
|
+
# foo => "foo" + ""
|
|
583
|
+
|
|
584
|
+
local name="${base%.*}"
|
|
585
|
+
local ext="${base##*.}"
|
|
586
|
+
|
|
587
|
+
if [[ "$name" == "$ext" ]]; then
|
|
588
|
+
ext=
|
|
589
|
+
else
|
|
590
|
+
ext=".$ext"
|
|
591
|
+
fi
|
|
592
|
+
|
|
593
|
+
# foo.jpg => "foo 12.34.56.jpg"
|
|
594
|
+
|
|
595
|
+
check_mac_trash_path "$SAFE_RM_TRASH/$name" "$ext"
|
|
596
|
+
local trash_path=$_mac_trash_path_ret
|
|
317
597
|
|
|
318
|
-
|
|
598
|
+
[[ "$OPT_VERBOSE" == 1 ]] && list_files "$1"
|
|
599
|
+
|
|
600
|
+
debug "$LINENO: mv $move to $trash_path"
|
|
601
|
+
mv "$move" "$trash_path"
|
|
602
|
+
|
|
603
|
+
[[ "$_traveled" == 1 ]] && cd $__DIRNAME &> /dev/null
|
|
604
|
+
|
|
605
|
+
# default status
|
|
606
|
+
return 0
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
# Check if the base name already exists in the trash
|
|
611
|
+
# If it does, foo -> foo.1
|
|
612
|
+
# If foo.1 exists, foo.1 -> foo.2
|
|
613
|
+
check_linux_trash_base(){
|
|
614
|
+
local base=$1
|
|
615
|
+
local trash="$SAFE_RM_TRASH/files"
|
|
616
|
+
local path="$trash/$base"
|
|
319
617
|
|
|
320
618
|
# if already in the trash
|
|
321
|
-
if [[ -e "$
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
619
|
+
if [[ -e "$path" ]]; then
|
|
620
|
+
debug "$LINENO: $path already exists"
|
|
621
|
+
|
|
622
|
+
local max_n=0
|
|
623
|
+
local num=
|
|
624
|
+
|
|
625
|
+
while IFS= read -r file; do
|
|
626
|
+
if [[ $file =~ ${filename}\.([0-9]+)$ ]]; then
|
|
627
|
+
# Remove leading zeros and make sure the number is in base 10
|
|
628
|
+
num=$((10#${BASH_REMATCH[1]}))
|
|
629
|
+
if ((num > max_n)); then
|
|
630
|
+
max_n=$num
|
|
631
|
+
fi
|
|
632
|
+
fi
|
|
633
|
+
done < <(find "$trash" -maxdepth 1)
|
|
634
|
+
|
|
635
|
+
(( max_n += 1 ))
|
|
636
|
+
|
|
637
|
+
echo "$base.$max_n"
|
|
638
|
+
else
|
|
639
|
+
echo "$base"
|
|
325
640
|
fi
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
# trash a file or dir directly for linux
|
|
645
|
+
# - move the target into
|
|
646
|
+
linux_trash(){
|
|
647
|
+
check_target_to_move "$1"
|
|
648
|
+
local move=$_to_move
|
|
649
|
+
local base=$(basename "$move")
|
|
650
|
+
|
|
651
|
+
base=$(check_linux_trash_base "$base")
|
|
652
|
+
|
|
653
|
+
local trash_path="$SAFE_RM_TRASH/files/$base"
|
|
326
654
|
|
|
327
|
-
[[ "$OPT_VERBOSE"
|
|
655
|
+
[[ "$OPT_VERBOSE" == 1 ]] && list_files "$1"
|
|
328
656
|
|
|
329
|
-
|
|
330
|
-
|
|
657
|
+
# Move the target into the trash
|
|
658
|
+
debug "$LINENO: mv $move to $trash_path"
|
|
659
|
+
mv "$move" "$trash_path"
|
|
331
660
|
|
|
332
|
-
|
|
661
|
+
# Save linux trash info
|
|
662
|
+
local info_path="$SAFE_RM_TRASH/info/$base.trashinfo"
|
|
663
|
+
local trash_time=$(date +%Y-%m-%dT%H:%M:%S)
|
|
664
|
+
cat > "$info_path" <<EOF
|
|
665
|
+
[Trash Info]
|
|
666
|
+
Path=$move
|
|
667
|
+
DeletionDate=$trash_time
|
|
668
|
+
EOF
|
|
669
|
+
|
|
670
|
+
[[ "$_traveled" == 1 ]] && cd $__DIRNAME &> /dev/null
|
|
333
671
|
|
|
334
|
-
#default status
|
|
335
672
|
return 0
|
|
336
673
|
}
|
|
337
674
|
|
|
675
|
+
|
|
338
676
|
# list all files and maintain outward sequence
|
|
339
|
-
# we can't just use `find $file`,
|
|
677
|
+
# we can't just use `find $file`,
|
|
678
|
+
# 'coz `find` act a inward searching, unlike rm -v
|
|
340
679
|
list_files(){
|
|
341
680
|
if [[ -d "$1" ]]; then
|
|
342
681
|
local list=$(ls -A "$1")
|
|
@@ -352,43 +691,39 @@ list_files(){
|
|
|
352
691
|
|
|
353
692
|
|
|
354
693
|
# debug: get $FILE_NAME array length
|
|
355
|
-
debug "${#FILE_NAME[@]} files or directory to process: ${FILE_NAME[@]}"
|
|
694
|
+
debug "$LINENO: ${#FILE_NAME[@]} files or directory to process: ${FILE_NAME[@]}"
|
|
356
695
|
|
|
357
|
-
# test remove interactive_once: ask for 3 or more files or with
|
|
358
|
-
if [[ (${#FILE_NAME[@]} > 2 || $OPT_RECURSIVE
|
|
696
|
+
# test remove interactive_once: ask for 3 or more files or with recursive option
|
|
697
|
+
if [[ (${#FILE_NAME[@]} > 2 || $OPT_RECURSIVE == 1) && $OPT_INTERACTIVE_ONCE == 1 ]]; then
|
|
359
698
|
echo -n "$COMMAND: remove all arguments? "
|
|
360
699
|
read answer
|
|
361
700
|
|
|
362
701
|
# actually, as long as the answer start with 'y', the file will be removed
|
|
363
702
|
# default to no remove
|
|
364
703
|
if [[ ! ${answer:0:1} =~ [yY] ]]; then
|
|
365
|
-
|
|
366
|
-
exit $EXIT_CODE
|
|
704
|
+
do_exit $LINENO
|
|
367
705
|
fi
|
|
368
706
|
fi
|
|
369
707
|
|
|
370
708
|
for file in "${FILE_NAME[@]}"; do
|
|
371
|
-
debug "result file $file"
|
|
709
|
+
debug "$LINENO: result file $file"
|
|
372
710
|
|
|
373
|
-
if [[ $file
|
|
374
|
-
|
|
375
|
-
|
|
711
|
+
if [[ $file == "/" ]]; then
|
|
712
|
+
error "it is dangerous to operate recursively on /"
|
|
713
|
+
error "are you insane?"
|
|
376
714
|
EXIT_CODE=1
|
|
377
|
-
|
|
378
|
-
# Exit immediately
|
|
379
|
-
debug "EXIT_CODE $EXIT_CODE"
|
|
380
|
-
exit $EXIT_CODE
|
|
715
|
+
continue
|
|
381
716
|
fi
|
|
382
717
|
|
|
383
|
-
if [[ $file
|
|
384
|
-
|
|
718
|
+
if [[ $file == "." || $file == ".." ]]; then
|
|
719
|
+
error "$COMMAND: \".\" and \"..\" may not be removed"
|
|
385
720
|
EXIT_CODE=1
|
|
386
721
|
continue
|
|
387
722
|
fi
|
|
388
723
|
|
|
389
|
-
#the same check also apply on /. /..
|
|
390
|
-
if [[ $(basename "$file")
|
|
391
|
-
|
|
724
|
+
# the same check also apply on /. /..
|
|
725
|
+
if [[ $(basename "$file") == "." || $(basename "$file") == ".." ]]; then
|
|
726
|
+
error "$COMMAND: \".\" and \"..\" may not be removed"
|
|
392
727
|
EXIT_CODE=1
|
|
393
728
|
continue
|
|
394
729
|
fi
|
|
@@ -397,23 +732,22 @@ for file in "${FILE_NAME[@]}"; do
|
|
|
397
732
|
ls_result=$(ls -d "$file" 2> /dev/null)
|
|
398
733
|
|
|
399
734
|
# debug
|
|
400
|
-
debug "ls_result: $ls_result"
|
|
735
|
+
debug "$LINENO: ls_result: $ls_result"
|
|
401
736
|
|
|
402
737
|
if [[ -n "$ls_result" ]]; then
|
|
403
738
|
for file in "$ls_result"; do
|
|
404
739
|
remove "$file"
|
|
405
740
|
status=$?
|
|
406
|
-
debug "remove returned status: $status"
|
|
741
|
+
debug "$LINENO: remove returned status: $status"
|
|
407
742
|
|
|
408
743
|
if [[ ! $status == 0 ]]; then
|
|
409
744
|
EXIT_CODE=1
|
|
410
745
|
fi
|
|
411
746
|
done
|
|
412
|
-
|
|
413
|
-
|
|
747
|
+
elif [[ -z "$OPT_FORCE" ]]; then
|
|
748
|
+
error "$COMMAND: $file: No such file or directory" >&2
|
|
414
749
|
EXIT_CODE=1
|
|
415
750
|
fi
|
|
416
751
|
done
|
|
417
752
|
|
|
418
|
-
|
|
419
|
-
exit $EXIT_CODE
|
|
753
|
+
do_exit $LINENO
|