From a2b23d3a91f8f50a64008ba5cda0a71ed020d1db Mon Sep 17 00:00:00 2001 From: Todd Lewis Date: Thu, 15 Jul 2021 00:15:55 -0400 Subject: [PATCH] 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. --- bash-completion/rename | 2 +- misc-utils/rename.1.adoc | 6 +++++ misc-utils/rename.c | 46 +++++++++++++++++++++++++++++-------- tests/expected/rename/basic | 14 +++++++++++ tests/ts/rename/basic | 30 ++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 10 deletions(-) diff --git a/bash-completion/rename b/bash-completion/rename index 9fc1ceca38..7edaa92c50 100644 --- a/bash-completion/rename +++ b/bash-completion/rename @@ -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 ;; diff --git a/misc-utils/rename.1.adoc b/misc-utils/rename.1.adoc index 0f5203423d..6cc881e344 100644 --- a/misc-utils/rename.1.adoc +++ b/misc-utils/rename.1.adoc @@ -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. diff --git a/misc-utils/rename.c b/misc-utils/rename.c index fbabfe2841..3b0aed0227 100644 --- a/misc-utils/rename.c +++ b/misc-utils/rename.c @@ -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; diff --git a/tests/expected/rename/basic b/tests/expected/rename/basic index 8a7a1d8042..4a1809828d 100644 --- a/tests/expected/rename/basic +++ b/tests/expected/rename/basic @@ -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? diff --git a/tests/ts/rename/basic b/tests/ts/rename/basic index 60e1717091..fb20b49dd3 100755 --- a/tests/ts/rename/basic +++ b/tests/ts/rename/basic @@ -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 -- 2.47.3