-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbulkrename
executable file
·150 lines (137 loc) · 2.75 KB
/
bulkrename
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
#!/bin/sh
# bulkrename: bulk rename files using $EDITOR and temporary files
# this file in public domain
EDITOR="${EDITOR:-"${VISUAL:-"vi"}"}"
err() {
printf "%s: %s\n" "${0##*/}" "$@" >&2
exit 1
}
hascollision() {
i=0
for arg
do
if test "$i" -lt "$nargs"
then
test "$target" = "$arg" && return
else
break
fi
i=$((i+1))
done
false
}
# create temporary files
if ! tmpfile="$(mktemp)"
then
err "could not make temporary file"
fi
trap 'rm -f "$tmpfile"' EXIT
# if no argument is passed, fill $@ with lines from stdin
if [ $# -eq 0 ]
then # run on stdin
while read -r line
do
set -- "$@" "$line"
done
fi
# check if arguments are valid and write them to tmpfile
nargs="$#"
for arg
do
case "$arg" in
("")
err "empty argument"
;;
(*[[:cntrl:]]*)
err "control characters in filenames are not supported"
;;
([[:space:]]*|*[[:space:]])
err "filenames beginning or ending in space is not supported"
;;
esac
if ! test -e "$arg"
then
err "$arg: file does not exist"
fi
printf "%s\n" "$arg" >> "$tmpfile"
done
# call editor on tmpfile
"$EDITOR" -- "$tmpfile" </dev/tty
# read new filenames from edited tmpfile
while read -r target && test "$nargs" -gt 0
do
source="$1"
# for each target filename read from the file there is a
# corresponding entry in $@. We read filenames from the
# file up to $nargs, and append $@ with commands:
# "rm" "file" (remove file)
# "mv0" "from" "to" (move file on the next loop)
# "mv1" "from" "to" (move file on the last loop)
if test -z "$target"
then
# line asks to remove file
set -- "$@" "rm" "$source"
elif test "$target" = "$source"
then
# line does nothing
:
elif hascollision "$@"
then
# line renaming creates filename collision; get unique filename
unique="$source"
while test -e "$unique"
do
unique="$unique~"
done
set -- "$@" "mv0" "$source" "$unique"
set -- "$@" "mv1" "$unique" "$target"
else
# line is a renaming without collision
set -- "$@" "mv0" "$source" "$target"
fi
# we're done with this source file
shift
nargs="$((nargs - 1))"
done <"$tmpfile"
# remove remaining arguments (in case file has more lines than we have files)
shift "$nargs"
# perform the commands specified in the argument list
while test "$#" -gt 0
do
case "$1" in
("rm")
rm -- "$2"
printf "rm %s\n" "$2"
shift 2
;;
("mv0")
mv -- "$2" "$3"
printf "%s -> %s\n" "$2" "$3"
shift 3
;;
("mv1")
set -- "$@" "mv2" "$2" "$3"
shift 3
;;
("mv2")
break
;;
(*)
err "$1: unknown command"
;;
esac
done
# perform the commands specified in the argument list (the final ones)
while test "$#" -gt 0
do
case "$1" in
("mv2")
mv -- "$2" "$3"
printf "%s -> %s\n" "$2" "$3"
shift 3
;;
(*)
err "$1: unknown command"
;;
esac
done