Hello coreutilitarians, I have been lately making some util-linux patches and while doing so I found rename command. It did not take long to realize that the command would be much better if it would support similar switches as mv. To accomplish that reusing gnulib copy functionality felt correct to me, but that is a problem within context of util-linux package, it does not use gnulib and I do not think it will have anytime soon. I proposed to move rename in util-linux mail list from util-linux to coreutils package, and Karel Zak, current util-linux maintainer, responded 'go ahead and ask at coreutils mailing list'.
So here we are, would it sound reasonable to take rename command to be part of coreutils? To be a bit more concrete I wrote first version of patch. While the patch is nearly good there are issues. For instance tests/misc/help-version is failing, and I my attempt to fix that was unsuccessful. The command itself poses few implementation questions. Should rename support recursion, and should the rename move files across directories (IMHO that would be unintuitive)? -- Sami Kerola http://www.iki.fi/kerolasa/
From 0822b6d3eb167639747b73d6618bf23a8a99cb35 Mon Sep 17 00:00:00 2001 From: Sami Kerola <[email protected]> Date: Sat, 25 Jun 2011 21:40:10 +0200 Subject: [PATCH] rename: move command from util-linux to coreutils package * AUTHORS: Add my name. * NEWS: Mention it. * README: Likewise. * doc/coreutils.texi (rename invocation): Add rename info. * man/Makefile.am (rename.1): Add dependency. * man/rename.x: New template. * man/.gitignore: Ignore generated man page. * po/POTFILES.in: Add src/rename.c. * src/.gitignore: Exclude rename. * src/Makefile.am (EXTRA_PROGRAMS): Add rename. * src/rename.c: New file. * tests/Makefile.am (TESTS): Add misc/rename-foo2bar. * tests/misc/rename-foo2bar: New file. Signed-off-by: Sami Kerola <[email protected]> --- AUTHORS | 1 + NEWS | 4 + README | 10 +- doc/coreutils.texi | 75 ++++++++++++- man/.gitignore | 1 + man/Makefile.am | 1 + man/rename.x | 10 ++ po/POTFILES.in | 1 + src/.gitignore | 1 + src/Makefile.am | 4 + src/rename.c | 290 +++++++++++++++++++++++++++++++++++++++++++++ tests/Makefile.am | 1 + tests/misc/rename-foo2bar | 30 +++++ 13 files changed, 423 insertions(+), 6 deletions(-) create mode 100644 man/rename.x create mode 100644 src/rename.c create mode 100755 tests/misc/rename-foo2bar diff --git a/AUTHORS b/AUTHORS index 0c15472..166ab34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -62,6 +62,7 @@ printf: David MacKenzie ptx: François Pinard pwd: Jim Meyering readlink: Dmitry V. Levin +rename: aeb(?), Sami Kerola rm: Paul Rubin, David MacKenzie, Richard M. Stallman, Jim Meyering rmdir: David MacKenzie runcon: Russell Coker diff --git a/NEWS b/NEWS index 619fbdd..a2d0147 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,10 @@ GNU coreutils NEWS -*- outline -*- chmod, chown and chgrp now output the original attributes in messages, when -v or -c specified. +** New programs + + rename: Rename multiple files by using pattern. + ** New features split accepts a new --filter=CMD option. With it, split filters output diff --git a/README b/README index eaa5fb0..5d5ff47 100644 --- a/README +++ b/README @@ -11,11 +11,11 @@ The programs that can be built with this package are: csplit cut date dd df dir dircolors dirname du echo env expand expr factor false fmt fold groups head hostid hostname id install join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup - nproc od paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir - runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf - sleep sort split stat stdbuf stty su sum sync tac tail tee test timeout - touch tr true truncate tsort tty uname unexpand uniq unlink uptime users - vdir wc who whoami yes + nproc od paste pathchk pinky pr printenv printf ptx pwd readlink rename + rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred + shuf sleep sort split stat stdbuf stty su sum sync tac tail tee test + timeout touch tr true truncate tsort tty uname unexpand uniq unlink + uptime users vdir wc who whoami yes See the file NEWS for a list of major changes in the current release. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index f74dd1c..8fd181b 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -94,6 +94,7 @@ * ptx: (coreutils)ptx invocation. Produce permuted indexes. * pwd: (coreutils)pwd invocation. Print working directory. * readlink: (coreutils)readlink invocation. Print referent of a symlink. +* rename: (coreutils)rename invocation. Rename multiple files. * rm: (coreutils)rm invocation. Remove files. * rmdir: (coreutils)rmdir invocation. Remove empty directories. * runcon: (coreutils)runcon invocation. Run in specified SELinux CTX. @@ -194,7 +195,7 @@ Free Documentation License''. * Printing text:: echo printf yes * Conditions:: false true test expr * Redirection:: tee -* File name manipulation:: dirname basename pathchk mktemp +* File name manipulation:: dirname basename pathchk rename mktemp * Working context:: pwd stty printenv tty * User information:: id logname whoami groups users who * System context:: date arch nproc uname hostname hostid uptime @@ -380,6 +381,7 @@ File name manipulation * basename invocation:: Strip directory and suffix from a file name * dirname invocation:: Strip last file name component * pathchk invocation:: Check file name validity and portability +* rename invocation:: Rename multiple files by using pattern * mktemp invocation:: Create temporary file or directory Working context @@ -12307,6 +12309,7 @@ This section describes commands that manipulate file names. * basename invocation:: Strip directory and suffix from a file name. * dirname invocation:: Strip last file name component. * pathchk invocation:: Check file name validity and portability. +* rename invocation:: Rename multiple files by using pattern. * mktemp invocation:: Create temporary file or directory. @end menu @@ -12486,6 +12489,76 @@ Exit status: 1 otherwise. @end display + +@node rename invocation +@section @command{rename}: Rename multiple files by using pattern + +@pindex rename +@cindex rename multiple files + +@command{rename} moves multiple files by using pattern. Synopsis: + +@example +rename [@var{option}] @var{expression} @var{replacement} @var{file} +@end example + +In essence the @command{rename} command does multiple @var{file} renames +same way as @command{mv} would do individually. The @command{rename} works +on basis of expression - replacement where the first occurrence of the +expression is replaced. Both expression and replacement are strings. + +The program accepts the following options. Also see @ref{Common options}. + +@table @samp + +@optBackup + +@item -f +@itemx --force +@opindex -f +@opindex --force +@cindex prompts, omitting +Do not prompt the user before removing a destination file. +@mvOptsIfn + +@item -i +@itemx --interactive +@opindex -i +@opindex --interactive +@cindex prompts, forcing +Prompt whether to overwrite each existing destination file, regardless +of its permissions. +If the response is not affirmative, the file is skipped. +@mvOptsIfn + +@item -n +@itemx --no-clobber +@opindex -n +@opindex --no-clobber +@cindex prompts, omitting +Do not overwrite an existing file. +@mvOptsIfn +This option is mutually exclusive with @option{-b} or @option{--backup} option. + +@optBackupSuffix + +@item -v +@itemx --verbose +@opindex -v +@opindex --verbose +Print the name of each file before moving it. + +@end table + +@cindex exit status of @command{rename} +Exit status: + +@display +0 all files where renamed succesfully +1 otherwise. +@end display + + @node mktemp invocation @section @command{mktemp}: Create temporary file or directory diff --git a/man/.gitignore b/man/.gitignore index 2c2ffcd..74f69a1 100644 --- a/man/.gitignore +++ b/man/.gitignore @@ -59,6 +59,7 @@ printf.1 ptx.1 pwd.1 readlink.1 +rename.1 rm.1 rmdir.1 seq.1 diff --git a/man/Makefile.am b/man/Makefile.am index cb4408c..de050e9 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -91,6 +91,7 @@ printf.1: $(common_dep) $(srcdir)/printf.x ../src/printf.c ptx.1: $(common_dep) $(srcdir)/ptx.x ../src/ptx.c pwd.1: $(common_dep) $(srcdir)/pwd.x ../src/pwd.c readlink.1: $(common_dep) $(srcdir)/readlink.x ../src/readlink.c +rename.1: $(common_dep) $(srcdir)/rename.x ../src/rename.c rm.1: $(common_dep) $(srcdir)/rm.x ../src/rm.c rmdir.1: $(common_dep) $(srcdir)/rmdir.x ../src/rmdir.c runcon.1: $(common_dep) $(srcdir)/runcon.x ../src/runcon.c diff --git a/man/rename.x b/man/rename.x new file mode 100644 index 0000000..a38ddfd --- /dev/null +++ b/man/rename.x @@ -0,0 +1,10 @@ +[NAME] +rename \- rename multiple files by using pattern +[DESCRIPTION] +.\" Add any additional description here +[EXAMPLES] +.B rename .htm .html *.htm +.br +This will move all files ending .htm to end with .html in current directory. +[SEE ALSO] +rename(2) diff --git a/po/POTFILES.in b/po/POTFILES.in index 97bcf45..6d047c1 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -101,6 +101,7 @@ src/ptx.c src/pwd.c src/readlink.c src/remove.c +src/rename.c src/rm.c src/rmdir.c src/runcon.c diff --git a/src/.gitignore b/src/.gitignore index d397370..430c1e2 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -67,6 +67,7 @@ printf ptx pwd readlink +rename rm rmdir runcon diff --git a/src/Makefile.am b/src/Makefile.am index ef0e7a4..25cf985 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -97,6 +97,7 @@ EXTRA_PROGRAMS = \ ptx \ pwd \ readlink \ + rename \ rm \ rmdir \ runcon \ @@ -248,6 +249,7 @@ ptx_LDADD = $(LDADD) pwd_LDADD = $(LDADD) readlink_LDADD = $(LDADD) rm_LDADD = $(LDADD) +rename_LDADD = $(LDADD) rmdir_LDADD = $(LDADD) runcon_LDADD = $(LDADD) seq_LDADD = $(LDADD) @@ -301,6 +303,7 @@ copy_LDADD = cp_LDADD += $(copy_LDADD) ginstall_LDADD += $(copy_LDADD) mv_LDADD += $(copy_LDADD) +rename_LDADD += $(copy_LDADD) remove_LDADD = mv_LDADD += $(remove_LDADD) @@ -477,6 +480,7 @@ timeout_SOURCES = timeout.c operand2sig.c mv_SOURCES = mv.c remove.c $(copy_sources) rm_SOURCES = rm.c remove.c +rename_SOURCES = rename.c $(copy_sources) mkdir_SOURCES = mkdir.c prog-fprintf.c rmdir_SOURCES = rmdir.c prog-fprintf.c diff --git a/src/rename.c b/src/rename.c new file mode 100644 index 0000000..0726678 --- /dev/null +++ b/src/rename.c @@ -0,0 +1,290 @@ +/* rename -- rename names + Copyright (C) 2011- Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* Written by aeb(?) 2000-01-01. The utility was part of util-linux package + years 2000-2011. Sami Kerola made the rename to be part of coreutils. + Reason to move the command be part of was to reuse functionality that is + in use for mv. */ + +/* TODO: + o Option -r for recursive. + o Rename destination can be a directory, which now gives: + rename: cannot overwrite directory `bar2' with non-directory + Should the file be renamed and put to directory? + o When the destination is mount point source file has to be + removed, assuming copy is successful. */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <selinux/selinux.h> + +#include "system.h" +#include "backupfile.h" +#include "copy.h" +#include "cp-hash.h" +#include "error.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "rename" + +#define AUTHORS \ + proper_name ("Sami Kerola") + +static void +cp_option_init (struct cp_options *x) +{ + bool selinux_enabled = (0 < is_selinux_enabled ()); + + cp_options_default (x); + x->copy_as_regular = false; /* FIXME: maybe make this an option */ + x->reflink_mode = REFLINK_NEVER; + x->dereference = DEREF_NEVER; + x->unlink_dest_before_opening = false; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = true; + x->one_file_system = false; + x->preserve_ownership = true; + x->preserve_links = true; + x->preserve_mode = true; + x->preserve_timestamps = true; + x->preserve_security_context = selinux_enabled; + x->reduce_diagnostics = false; + x->data_copy_required = true; + x->require_preserve = false; /* FIXME: maybe make this an option */ + x->require_preserve_context = false; + x->preserve_xattr = true; + x->require_preserve_xattr = false; + x->recursive = true; + x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */ + x->symbolic_link = false; + x->set_mode = false; + x->mode = 0; + x->stdin_tty = isatty (STDIN_FILENO); + + x->open_dangling_dest_symlink = false; + x->update = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +static bool +do_rename (char *expression, char *replacement, char *source, + struct cp_options *x) +{ + char *destination, *where, *a, *b; + int elen, rlen, slen; + bool copy_into_self; + bool rename_succeeded; + bool ok; + + where = strstr (source, expression); + if (where == NULL) + return false; + + elen = strlen (expression); + rlen = strlen (replacement); + slen = strlen (source); + destination = xmalloc (rlen + slen + 1); + + a = source; + b = destination; + while (a < where) + *b++ = *a++; + a = replacement; + while (*a) + *b++ = *a++; + a = where + elen; + while (*a) + *b++ = *a++; + *b = 0; + + ok = + copy (source, destination, false, x, ©_into_self, &rename_succeeded); + + if (ok) + { + if (copy_into_self) + { + /* The likely case is that expression and replacement are the same + string. Maybe this should be done some better way. */ + ok = false; + } + } + + free (destination); + return true; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... EXPRESSION REPLACEMENT FILE...\n\ +"), program_name); + fputs (_("\ +Rename first occurence of EXPRESSION by REPLACEMENT in FILE name.\n\ +\n\ +"), stdout); + fputs (_("\ + --backup[=CONTROL] make a backup of each existing destination file\ +\n\ + -b like --backup but does not accept an argument\n\ + -f, --force do not prompt before overwriting\n\ + -i, --interactive prompt before overwrite\n\ + -n, --no-clobber do not overwrite an existing file\n\ +If you specify more than one of -i, -f, -n, only the final one takes effect.\n\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ + -v, --verbose explain what is being done\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + emit_ancillary_info (); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int i, c; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + struct cp_options x; + char *expression, *replacement; + int n_arguments; + char **argument; + bool ok; + + static const struct option longopts[] = { + {"backup", optional_argument, NULL, 'b'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"no-clobber", no_argument, NULL, 'n'}, + {"suffix", required_argument, NULL, 'S'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} + }; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdin); + + cp_option_init (&x); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + while ((c = getopt_long (argc, argv, "bfinS:v", longopts, NULL)) != -1) + { + switch (c) + { + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + case 'f': + x.interactive = I_ALWAYS_YES; + break; + case 'i': + x.interactive = I_ASK_USER; + break; + case 'n': + x.interactive = I_ALWAYS_NO; + break; + case 'v': + x.verbose = true; + break; + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + n_arguments = argc - optind; + argument = argv + optind; + + if (make_backups && x.interactive == I_ALWAYS_NO) + { + error (0, 0, + _("options --backup and --no-clobber are mutually exclusive")); + usage (EXIT_FAILURE); + } + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + x.backup_type = (make_backups + ? xget_version (_("backup type"), + version_control_string) : no_backups); + + hash_init (); + + if (n_arguments < 3) + { + error (0, 0, _("not enough arguments")); + usage (EXIT_FAILURE); + } + + expression = argument[0]; + replacement = argument[1]; + + ok = true; + for (i = 2; i < n_arguments; i++) + ok &= do_rename (expression, replacement, argument[i], &x); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/tests/Makefile.am b/tests/Makefile.am index f8fbd38..b43e6bc 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -219,6 +219,7 @@ TESTS = \ misc/printf-surprise \ misc/pwd-long \ misc/readlink-fp-loop \ + misc/rename-foo2bar \ misc/runcon-no-reorder \ misc/sha1sum \ misc/sha1sum-vec \ diff --git a/tests/misc/rename-foo2bar b/tests/misc/rename-foo2bar new file mode 100755 index 0000000..6aa858a --- /dev/null +++ b/tests/misc/rename-foo2bar @@ -0,0 +1,30 @@ +#!/bin/sh +# test rename basic functionality + +# Copyright (C) 2011 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +. "${srcdir=.}/init.sh"; path_prepend_ ../src +print_ver_ rename + +for i in foo1 foo2 ; do + echo a > $i || framework_failure +done + +rename foo bar foo? 2>/dev/null || fail=1 +test -f bar1 || fail=1 +test -f bar2 || fail=1 + +Exit $fail -- 1.7.5.2
