-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrename_files.perl
executable file
·260 lines (237 loc) · 10.2 KB
/
rename_files.perl
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# *-*-perl-*-*
eval 'exec perl -Ssw $0 "$@"'
if 0;
#!/usr/local/bin/perl -sw
#
# rename_files.perl: script for renaming a list of files by doing a simple
# pattern replacement (old to new), assuming Unix (or that the DOS version
# of the mv command is available).
#
# Notes:
# - With -regex, the patterns are actually Perl regular expressions, so the
# full range of pattern matching is available, although not recommended due
# to potential for unexpected results.
# - With -ignore, case differences in filenames are ignored.
# - With regex patterns, the u qualifier is ued so that character ranges not
# decomposed (e.g., [®] misinterpreted as [\xc2\xae]). See
# https://stackoverflow.com/questions/70358309/how-can-i-use-unicode-characters-in-perl-regex-substitution-command
#
# TODO:
# - Add recursive option.
#
# Load in the common module, making sure the script dir is in the Perl lib path
BEGIN {
my $dir = $0; $dir =~ s/[^\\\/]*$//; unshift(@INC, $dir);
require 'common.perl';
use vars qw/$utf8 $script_name $verbose/;
}
# Specify additional diagnostics and strict variable usage, excepting those
# for command-line arguments (see init_var's in &init).
use strict;
use vars qw/$q $f $regex $quick $force $test $evalre $global $i $ignore/;
use vars qw/$t $nt $para/;
use vars qw/$rename_old $diagnose $fallback/;
&init_var(*q, &FALSE); # alias for -q
&init_var(*quick, $q); # quick spec: infer files from old pattern
&init_var(*f, &FALSE); # force overwrite of the files
&init_var(*force, $f); # alias for -f
&init_var(*i, &FALSE); # alias for -ignore
&init_var(*ignore, $i); # ignore case
## TEST:
&init_var(*para, &FALSE); # paragraph regex mode (for newlines in filename)
## OLD: &init_var(*regex, &FALSE); # allow regular expression in pattern
&init_var(*regex, $para); # allow regular expression in pattern
&init_var(*evalre, &FALSE); # run replacement through eval environment
&init_var(*diagnose, &FALSE); # diagnose pattern replacement issues
&init_var(*fallback, &FALSE); # try to recover from replacement issues
my($nt_default) = (defined($t) ? (! $t) : &TRUE); # abbrev. for 'not t'
&init_var(*nt, $nt_default); # alias for -test=0
&init_var(*t, (! $nt)); # alias for -test
&init_var(*test, $t); # just test the rename operation
&init_var(*global, &FALSE); # global replacement
&init_var(*rename_old, &FALSE); # rename old file as {old}.{MMDDDYY}
## OLD
## # Refuse to process buggy -rename_old
## if ($rename_old) {
## &debug_print(&TL_DETAILED, "Warning: The -rename_old option is not fully functional\n");
## }
## TEST:
# TODO: put $para support in common.perl (likewise for slurp, as in count_it.perl)
$/ = "" if ($para); # paragraph input mode
## TODO: our($mods) = "";
## $mods = "";
## $mods .= "g" if ($global);
# Extract pattern specifications from command line
if (!defined($ARGV[1])) {
&usage();
&exit();
}
my $old_pattern = shift @ARGV;
my $new_pattern = shift @ARGV;
my $test_spec = ($test ? "test " : "");
## OLD:
## $new_pattern = "" if (! defined($new_pattern));
## if ($new_pattern eq "/") {
## $new_pattern = "";
## }
# Special hooks for diagnosis
# note: -diagnose only supported for -evalre
if ($diagnose && (! $evalre)) {
if ($regex) {
&debug_print(&TL_USUAL, "FYI: -regex => -evalre to support diagnosis\n");
$evalre = &TRUE;
}
elsif ($global) {
&debug_print(&TL_USUAL, "FYI: -global => -evalre to support step-by-step diagnose\n");
$evalre = &TRUE;
}
}
# TEMP: warn about options not yet working
if ($evalre) {
&debug_print(&TL_ALWAYS, "Warning: -evalre not yet working quite right\n")
}
# Normalize the patterns
## TODO: rework -ignore processing
if ($ignore) {
$old_pattern = &to_lower($old_pattern);
$new_pattern = &to_lower($new_pattern);
}
if ($utf8) {
## TODO:
## $old_pattern = &decode_utf8($old_pattern);
## $new_pattern = &decode_utf8($new_pattern);
}
# Support for quick spec: try all files that match the old pattern,
# provided that no files specified
if ($quick) {
if (defined($ARGV[0])) {
&warning("Ignoring -quick mode as files specified\n");
}
else {
@ARGV = glob "*";
for (my $i = 0; $i <= $#ARGV; $i++) {
$ARGV[$i] = &to_lower($ARGV[$i]) if ($ignore);
$ARGV[$i] = undef unless ($ARGV[$i] =~ /$old_pattern/);
}
}
}
# Apply the pattern to each file specified on command line,
# renaming old file to new name
&debug_print(&TL_DETAILED, "old_pattern=/$old_pattern/; new_pattern=/$new_pattern/; regex=$regex\n");
if (($old_pattern =~ /(?<!\\)([\^\*\|\]])/) && (! $regex)) {
&debug_print(&TL_USUAL, "Warning: old pattern contains regex ($1), but -regex not specified\n");
}
## TODO: my $quals = ""; $quals .= "g" if ($global);
for (my $i = 0; $i <= $#ARGV; $i++) {
next if (!defined($ARGV[$i]));
my $old_file = $ARGV[$i];
if (! -e "$old_file") {
&debug_print(&TL_DETAILED, "Ignoring non-existent old file ($old_file).\n");
next;
}
if ($utf8) {
## TODO: $old_file = decode_utf8($old_file);
}
&debug_print(&TL_VERY_DETAILED, "old_file=$old_file\n");
my $new_file = $old_file;
# Apply patterns, making sure escaped unless regex desired.
# TODO: resolve problem getting replacement parameters used
# ex: rename-files -evalre -d=6 '\.(...)$' '.\\1-2' *.??? 2>&1 | less
if ($evalre) {
## ($global ? (eval { $new_file =~ s/$old_pattern/$new_pattern/g }) : (eval { $new_file =~ s/$old_pattern/$new_pattern/; })); &debug_print(&TL_VERBOSE, "\$1 = $1\n"); };
## TODO: my $quals = ($global ? "g" : ""); eval { $new_file =~ s/$old_pattern/$new_pattern/$quals; &debug_print(&TL_VERBOSE, "\$1 = $1\n"); };
## OLD:
## eval { if ($global) { $new_file =~ s/$old_pattern/$new_pattern/ig; } else { $new_file =~ s/$old_pattern/$new_pattern/; };
## &debug_print(&TL_VERBOSE, "\$1 = $1\n");
## };
&debug_print(&TL_VERY_VERBOSE, "evalre replacement\n");
## TODO: if ($global) { $new_file =~ s/$old_pattern/$new_pattern/ge; } else { $new_file =~ s/$old_pattern/$new_pattern/e; };
# note: u used so Unicode characters can be used in ranges
while ( $new_file =~ m/$old_pattern/u ) {
## TODO: resolve issue with replacement '-p-$1'
my($replacement) = eval "$new_pattern";
if ((! defined($replacement)) && $fallback) {
&debug_print(&TL_VERBOSE, "diagnose: using pattern as replacement\n");
$replacement = $new_pattern;
}
if (! defined($replacement)) {
&debug_print(&TL_ERROR, "Error: bad replacement\n");
last;
}
if ($verbose) {
print("Match text: '$&'\n");
print("Replacement: '$replacement'\n");
}
&debug_print(&TL_VERBOSE, "replacement: $replacement\n");
my($last_name) = $new_file;
$new_file =~ s/$old_pattern/$replacement/u;
last if ((! $global) || ($new_file eq $last_name));
&debug_print(&TL_VERY_DETAILED, "new_file=$new_file\n");
}
}
elsif ($regex) {
&debug_print(&TL_VERY_VERBOSE, "regex replacement\n");
# note: u used so Unicode characters can be used in ranges
## OLD: if ($global) { $new_file =~ s/$old_pattern/$new_pattern/g; } else { $new_file =~ s/$old_pattern/$new_pattern/; };
if ($global) { $new_file =~ s/$old_pattern/$new_pattern/gu; } else { $new_file =~ s/$old_pattern/$new_pattern/u; };
## BAD: $new_file =~ s/$old_pattern/$new_pattern/;
## BAD (not appropriate for loop): if (&VERBOSE_DEBUGGING) { &debug_print(-1, "\$1 = $1\n"); }
}
else {
## ($global ne "" ? ($new_file =~ s/\Q$old_pattern/$new_pattern/g) : ($new_file =~ s/\Q$old_pattern/$new_pattern/));
&debug_print(&TL_VERY_VERBOSE, "quoted-regex replacement\n");
if ($global) { $new_file =~ s/\Q$old_pattern/$new_pattern/g; } else { $new_file =~ s/\Q$old_pattern/$new_pattern/; }
}
# If the file names are the same, do nothing
if ($new_file eq $old_file) {
&debug_print(&TL_DETAILED, "File names are the same for \"$old_file\"\n");
next;
}
# Move target to target.{today}
# note: get_file_ddmmmyy returns current year 2022 as 1971!
if ((-e $new_file) && $rename_old) {
my($old_file_dated) = $new_file . "." . &get_file_ddmmmyy($new_file);
&debug_print(&TL_BASIC, "${test_spec}renaming existing target \"$new_file\" as \"$old_file_dated\"\n");
if (! $test) {
rename $new_file, $old_file_dated;
}
}
# If file with new name exists, don't overwrite unless force specified
if ((-e $new_file) && ($force == &FALSE)) {
&debug_print(&TL_BASIC, "Warning: \"$new_file\" already exists! not mv'ing \"$old_file\" \"$new_file\"\n");
}
# Otherwise, proceed with the rename
else {
# Make sure spaces and single quotes are properly escaped
## &issue_command("mv '$old_file' '$new_file'", &TL_USUAL);
## $old_file =~ s/([^\\]) /$1\\ /g;
## $old_file =~ s/\'/\\\'/g;
## $new_file =~ s/([^\\]) /$1\\ /g;
## $old_file =~ s/\'/\\\'/g;
# Execute the rename command
## &cmd("mv $old_file' '$new_file'", &TL_USUAL);
## OLD: my $test_spec = ($test ? "test " : "");
&debug_print(&TL_BASIC, "${test_spec}renaming \"$old_file\" to \"$new_file\"\n");
next if ($test);
my($OK) = rename $old_file, $new_file;
if (! $OK) {
&error("Problem during the rename of \"$old_file\" to \"$new_file\" ($!)\n");
}
}
}
&exit();
#------------------------------------------------------------------------
sub usage {
my($options) = "main options = [-q | -quick] [-f | -force] [-i | -ignore] [-global] [-regex]";
$options .= "\nother options = [-evalre] [-t | -test] [-para] [-rename_old]";
$options .= "\nesoteric options = [-diagnose] [-fallback] [-nt]";
$options .= "\ncommon options = " . &COMMON_OPTIONS;
my($example) = "Example(s):\n\n$script_name ' - Shortcut' '' *Shortcut*\n\n";
$example .= "$0 rename-files -q -- '--' '-'\n\n";
my($note) = "";
$note .= "Notes:\n\n-- Use -- for first argument if dashes occur in old-pattern.\n-- By default only a single occurrence of the pattern is replaced.\n\n- The -ignore option is with respect to old vs. new comparison).\n";
$note .= "- Use -rename_old to rename existing target file with date-based suffix (e.g., fubar to fubar.22mar22).\n";
$note .= "- Use -diagnose to diagnose -regex, -evalre, and -global.\n";
$note .= "- Use -fallback to recover from common errors (e.g., -evalre).\n";
print STDERR "\nUsage: $script_name [options] old-pattern new-pattern [file] ...\\n\n$options\n\n$example\n$note";
}