rename: add --all and --last parameters

Renaming files with rename often involves multiple passes in order
to, say, replace all spaces with underscores because traditionally
rename only replaces the first occurrence of the expression. The
--all parameter makes this task simple.

With the addition of --last, rename becomes much safer to use when
replacing file extensions, whereas before it would mangle a file
which had its extension also embedded elsewhere in its name.

The implied --first, together with --all and --last, round out the
common cases for renaming files.
This commit is contained in:
Todd Lewis 2021-07-15 00:15:55 -04:00
parent be199d34f2
commit a2b23d3a91
5 changed files with 88 additions and 10 deletions

View File

@ -11,7 +11,7 @@ _rename_module()
esac
case $cur in
-*)
OPTS="--verbose --symlink --help --version --no-act --no-override --interactive"
OPTS="--verbose --symlink --help --version --no-act --all --last --no-override --interactive"
COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
return 0
;;

View File

@ -31,6 +31,12 @@ Show which files were renamed, if any.
*-n*, *--no-act*::
Do not make any changes; add *--verbose* to see what would be made.
*-a*, *--all*::
Replace all occurrences of _expression_ rather than only the first one.
*-l*, *--last*::
Replace the last occurrence of _expression_ rather than the first one.
*-o*, *--no-overwrite*::
Do not overwrite existing files. When *--symlink* is active, do not overwrite symlinks pointing to existing targets.

View File

@ -44,23 +44,39 @@ for i in $@; do N=`echo "$i" | sed "s/$FROM/$TO/g"`; mv "$i" "$N"; done
#define RENAME_EXIT_UNEXPLAINED 64
static int tty_cbreak = 0;
static int all = 0;
static int last = 0;
static int string_replace(char *from, char *to, char *s, char *orig, char **newname)
{
char *p, *q, *where;
int count = 0, fromlen = strlen(from);
where = strstr(s, from);
p = where = strstr(s, from);
if (where == NULL)
return 1;
count++;
while ((all || last) && p) {
p = strstr(p + (last ? 1 : fromlen), from);
if (p) {
if (all)
count++;
if (last)
where = p;
}
}
p = orig;
*newname = xmalloc(strlen(orig) + strlen(to) + 1);
*newname = xmalloc(strlen(orig) - count * fromlen + count * strlen(to) + 1);
q = *newname;
while (p < where)
*q++ = *p++;
p = to;
while (*p)
*q++ = *p++;
p = where + strlen(from);
while (where) {
while (p < where)
*q++ = *p++;
p = to;
while (*p)
*q++ = *p++;
p = where + fromlen;
where = strstr(p, from);
}
while (*p)
*q++ = *p++;
*q = 0;
@ -226,6 +242,8 @@ static void __attribute__((__noreturn__)) usage(void)
fputs(_(" -v, --verbose explain what is being done\n"), out);
fputs(_(" -s, --symlink act on the target of symlinks\n"), out);
fputs(_(" -n, --no-act do not make any changes\n"), out);
fputs(_(" -a, --all replace all occurrences\n"), out);
fputs(_(" -l, --last replace only the last occurrence\n"), out);
fputs(_(" -o, --no-overwrite don't overwrite existing files\n"), out);
fputs(_(" -i, --interactive prompt before overwrite\n"), out);
fputs(USAGE_SEPARATOR, out);
@ -246,6 +264,8 @@ int main(int argc, char **argv)
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
{"help", no_argument, NULL, 'h'},
{"all", no_argument, NULL, 'a'},
{"last", no_argument, NULL, 'l'},
{"no-act", no_argument, NULL, 'n'},
{"no-overwrite", no_argument, NULL, 'o'},
{"interactive", no_argument, NULL, 'i'},
@ -258,11 +278,19 @@ int main(int argc, char **argv)
textdomain(PACKAGE);
close_stdout_atexit();
while ((c = getopt_long(argc, argv, "vsVhnoi", longopts, NULL)) != -1)
while ((c = getopt_long(argc, argv, "vsVhnaloi", longopts, NULL)) != -1)
switch (c) {
case 'n':
noact = 1;
break;
case 'a':
all = 1;
last = 0;
break;
case 'l':
last = 1;
all = 0;
break;
case 'v':
verbose = 1;
break;

View File

@ -2,3 +2,17 @@
`rename_basic.2' -> `rename_test.2'
`rename_basic.3' -> `rename_test.3'
what is rename_basic.? doing here?
`rename_all file with spaces.1' -> `rename_all_file_with_spaces.1'
`rename_all file with spaces.2' -> `rename_all_file_with_spaces.2'
`rename_all file with spaces.3' -> `rename_all_file_with_spaces.3'
what is rename_all* *.? doing here?
`rename_zz_last_z.x' -> `rename_AAzzBB_last_z.x'
`rename_zz_last_z.y' -> `rename_AAzzBB_last_z.y'
`rename_zz_last_z.z' -> `rename_AAzzBB_last_z.z'
`rename_zz_last_zz.x' -> `rename_zz_last_AAzzBB.x'
`rename_zz_last_zz.y' -> `rename_zz_last_AAzzBB.y'
`rename_zz_last_zz.z' -> `rename_zz_last_AAzzBB.z'
`rename_zz_last_zzz.x' -> `rename_zz_last_zAAzzBB.x'
`rename_zz_last_zzz.y' -> `rename_zz_last_zAAzzBB.y'
`rename_zz_last_zzz.z' -> `rename_zz_last_zAAzzBB.z'
what is rename*last* doing here?

View File

@ -38,4 +38,34 @@ for i in rename_test.{1..3}; do
fi
done
touch rename_all\ file\ with\ spaces.{1..3}
$TS_CMD_RENAME -v -a ' ' '_' rename_all*.? >> $TS_OUTPUT 2> $TS_ERRLOG
for i in rename_all*\ *.?; do
echo "what is $i doing here?" >> $TS_OUTPUT
done
for i in rename_all_file_with_spaces.{1..3}; do
if [ ! -f $i ]; then
echo "file $i is missing" >> $TS_OUTPUT
else
rm -f $i
fi
done
touch rename_zz_last_{z,z{,z{,z}}}.{x..z}
$TS_CMD_RENAME -v -l zz AAzzBB rename_zz_last_* >> $TS_OUTPUT 2> $TS_ERRLOG
for i in rename_AAzzBB_last_z.x rename_AAzzBB_last_z.y rename_AAzzBB_last_z.z \
rename_zz_last_AAzzBB.x rename_zz_last_AAzzBB.y rename_zz_last_AAzzBB.z \
rename_zz_last_zAAzzBB.x rename_zz_last_zAAzzBB.y rename_zz_last_zAAzzBB.z ; do
if [ ! -f $i ]; then
echo "file $i is missing" >> $TS_OUTPUT
else
rm -f $i
fi
done
for i in rename*last* ; do
echo "what is $i doing here?" >> $TS_OUTPUT
done
ts_finalize