coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

rename: move command from util-linux to coreutils


From: Sami Kerola
Subject: rename: move command from util-linux to coreutils
Date: Sat, 9 Jun 2012 22:19:23 +0200

Hello,

The next coreutils and util-linux releases are going to be made close
to each others to avoid 'su' coming from multiple source, or being
missing.  At same go moving 'rename' could make sense.

I completely rewrote the command as the util-linux way doing things
would not be a strong match how code is crafted in coreutils.  Primary
difference is, as well known, usage of gnulib and a little bit
different coding style.

Please find my proposal how the new rename might be implemented.  I am
almost sure the merge should not happen as is, as there are some cases
which might not work correctly; for example directories are not
renamed, nor files moved out of directories.  Perhaps that is right, or
wrong.  Comments are definitely welcome.

Another decision which might be wrong is stdin file list handling.
Command, by default, expects null terminated strings and nothing else
will work.  One might argue the command should, as xargs, allow white
space terminated input.  IMHO allowing white space termination
encourages users to use error prone input style, and therefore it is
not justified to be default nor supported in modern commands.  But I
could overlook something, so maybe this needs to be discussed as well.

Finally, without any doubt I wrote half of English somehow wrong so human
language review is highly recommendable.


The following changes since commit 928dd73762e69cfeaab4a7ec9dd8f30f86a45ed4:

  su: remove program (util-linux is now the best source for it)
(2012-06-06 22:33:08 +0200)

are available in the git repository at:

  git://github.com/kerolasa/coreutils.git rename

for you to fetch changes up to 62ccdd1435210cb8f30ba50f297f57759f264d50:

  rename: move command from util-linux to coreutils (2012-06-09 21:57:03 +0200)

----------------------------------------------------------------
Sami Kerola (1):
      rename: move command from util-linux to coreutils

 AUTHORS                      |    1 +
 NEWS                         |    2 +
 README                       |    8 +-
 doc/coreutils.texi           |  117 +++++++-
 man/.gitignore               |    1 +
 man/Makefile.am              |    1 +
 man/rename.x                 |   10 +
 po/POTFILES.in               |    1 +
 scripts/git-hooks/commit-msg |   13 +-
 src/.gitignore               |    1 +
 src/Makefile.am              |    4 +
 src/rename.c                 |  646 ++++++++++++++++++++++++++++++++++++++++++
 tests/Makefile.am            |    1 +
 tests/misc/help-version      |    1 +
 tests/misc/invalid-opt       |    1 +
 tests/misc/rename-foo2bar    |   30 ++
 16 files changed, 827 insertions(+), 11 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 9984a2e..f2737d2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -63,6 +63,7 @@ ptx: François Pinard
 pwd: Jim Meyering
 readlink: Dmitry V. Levin
 realpath: Pádraig Brady
+rename: 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 54d24c3..306f07a 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,8 @@ GNU coreutils NEWS
 -*- outline -*-
   patches as well as enough support to build on the Hurd, we no longer
   have any reason to include it here.

+  rename: Rename multiple files using a pattern.  This command will
+  replace an utility with same name in util-linux package.

 * Noteworthy changes in release 8.17 (2012-05-10) [stable]

diff --git a/README b/README
index 09961a0..5f21924 100644
--- a/README
+++ b/README
@@ -12,10 +12,10 @@ The programs that can be built with this package are:
   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 realpath
-  rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred
-  shuf sleep sort split stat stdbuf stty sum sync tac tail tee test
-  timeout touch tr true truncate tsort tty uname unexpand uniq unlink
-  uptime users vdir wc who whoami yes
+  rename rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum
+  sha512sum shred shuf sleep sort split stat stdbuf stty 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 f7251b2..ef90459 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -95,6 +95,7 @@
 * pwd: (coreutils)pwd invocation.               Print working directory.
 * readlink: (coreutils)readlink invocation.     Print referent of a symlink.
 * realpath: (coreutils)readpath invocation.     Print resolved file names.
+* 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 realpath
+* File name manipulation::       dirname basename pathchk rename
mktemp realpath
 * 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 using a pattern
 * mktemp invocation::            Create temporary file or directory
 * realpath invocation::          Print resolved file names

@@ -12439,6 +12441,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 using a pattern.
 * mktemp invocation::           Create temporary file or directory.
 * realpath invocation::         Print resolved file names.
 @end menu
@@ -12663,6 +12666,118 @@ Exit status:
 1 otherwise.
 @end display

+
+@node rename invocation
+@section @command{rename}: Rename multiple files using a pattern
+
+@pindex rename
+@cindex rename multiple files
+
+@command{rename} moves multiple files using a pattern. Synopsis:
+
+@example
+rename [@var{option}] @var{expression} @var{replacement} @var{file...}
+rename [@var{option}] --files0-from=FILE  @var{--exec} @var{command}
+find . -print0 \
+  | rename [@var{option}] @var{expression} @var{replacement}
+find . -print0 \
+  | rename [@var{option}] @var{--exec} @var{command}
+@end example
+
+The @command{rename} command does multiple @var{file} renames same way as
+@command{mv} would do individually. The @command{rename} will not rename
+directory names, nor move files out of directory in which they are.
+
+The program accepts the following options.  Also see @ref{Common options}.
+
+@table @samp
+
+@filesZeroFromOption{rename}
+
+
+@item -e @var{command -o --option}
+@itemx --exec @var{command -o --option}
+@opindex -e
+@opindex --exec
+@cindex external execution
+Use @command{command -o --option} to manipulate file name string.
+@smallexample
+# Show how ba would be removed from bash
+find /bin/ba* -print0 | rename -t -e sed 's/bash/sh/'
+@end smallexample
+
+
+@item -R
+@itemx -r
+@itemx --recursive
+@opindex -R
+@opindex -r
+@opindex --recursive
+@cindex recursive renaming
+Rename files recursively.
+
+@item -x
+@itemx --one-file-system
+@opindex -x
+@opindex --one-file-system
+@cindex file systems, omitting renaming to different
+Skip subdirectories that are on different file systems from the one that
+the rename started on.
+
+@optBackup
+
+@optBackupSuffix
+
+@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.
+
+@item -t
+@itemx --test
+@opindex -t
+@opindex --test
+Show printout how @command{rename} would affect without performing any changes.
+
+@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 aa6fa51..22640ea 100644
--- a/man/.gitignore
+++ b/man/.gitignore
@@ -60,6 +60,7 @@ ptx.1
 pwd.1
 readlink.1
 realpath.1
+rename.1
 rm.1
 rmdir.1
 seq.1
diff --git a/man/Makefile.am b/man/Makefile.am
index cc93a42..177eec7 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -93,6 +93,7 @@ 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
 realpath.1:    $(common_dep)   $(srcdir)/realpath.x    ../src/realpath.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..f44af45
--- /dev/null
+++ b/man/rename.x
@@ -0,0 +1,10 @@
+[NAME]
+rename \- rename multiple files using a 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 b938dfc..70e097e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -105,6 +105,7 @@ src/readlink.c
 src/realpath.c
 src/relpath.c
 src/remove.c
+src/rename.c
 src/rm.c
 src/rmdir.c
 src/runcon.c
diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg
index a5e4090..4b609a7 100755
--- a/scripts/git-hooks/commit-msg
+++ b/scripts/git-hooks/commit-msg
@@ -15,12 +15,13 @@ my @valid = qw(
     arch base64 basename cat chcon chgrp chmod chown chroot cksum comm
     cp 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 realpath rm rmdir runcon seq sha1sum sha224sum sha256sum
-    sha384sum sha512sum shred shuf sleep sort split stat stdbuf stty
-    sum sync tac tail tee test timeout touch tr true truncate tsort
-    tty uname unexpand uniq unlink uptime users vdir wc who whoami yes
+    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 realpath rename rm rmdir runcon seq sha1sum sha224sum
+    sha256sum sha384sum sha512sum shred shuf sleep sort split stat
+    stdbuf stty sum sync tac tail tee test timeout touch tr true
+    truncate tsort tty uname unexpand uniq unlink uptime users vdir wc
+    who whoami yes

     copy gnulib tests maint doc build scripts
     );
diff --git a/src/.gitignore b/src/.gitignore
index d78178c..a6e092f 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -69,6 +69,7 @@ ptx
 pwd
 readlink
 realpath
+rename
 rm
 rmdir
 runcon
diff --git a/src/Makefile.am b/src/Makefile.am
index 3099505..47bb59e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -97,6 +97,7 @@ EXTRA_PROGRAMS = \
   pwd          \
   readlink     \
   realpath     \
+  rename       \
   rm           \
   rmdir                \
   runcon       \
@@ -249,6 +250,7 @@ ptx_LDADD = $(LDADD)
 pwd_LDADD = $(LDADD)
 readlink_LDADD = $(LDADD)
 realpath_LDADD = $(LDADD)
+rename_LDADD = $(LDADD)
 rm_LDADD = $(LDADD)
 rmdir_LDADD = $(LDADD)
 runcon_LDADD = $(LDADD)
@@ -302,6 +304,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)
@@ -424,6 +427,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..e97ddac
--- /dev/null
+++ b/src/rename.c
@@ -0,0 +1,646 @@
+/* rename -- rename multiple files using a pattern
+   Copyright (C) 2012- 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/>.  */
+
+/* This command supports the same syntax as util-linux rename, which was
+   written by Andries Brouwer at Jan 1st, 2000.  The command appeared to
+   coreutils as the util-linux package is less obvious package to ship a
+   command such as this.
+
+   The coreutils implementation was done by Sami Kerola.  */
+
+#include <config.h>
+
+#include <assert.h>            /* for assert */
+#include <errno.h>             /* for errno */
+#include <getopt.h>            /* for no_argument, optind, optarg, etc */
+#include <libintl.h>           /* for bindtextdomain, textdomain */
+#include <limits.h>            /* for CHAR_MAX */
+#include <locale.h>            /* for setlocale, LC_ALL */
+#include <selinux/selinux.h>   /* for is_selinux_enabled */
+#include <stdbool.h>           /* for false, true, bool */
+#include <stdio.h>             /* for NULL, stdout, fprintf, etc */
+#include <stdlib.h>            /* for EXIT_FAILURE, exit, etc */
+#include <sys/param.h>         /* for MIN */
+#include <sys/stat.h>          /* for stat, fstat, S_ISREG */
+#include <sys/wait.h>          /* for waitpid */
+#include <unistd.h>            /* for STDIN_FILENO, close, isatty */
+
+#include "argv-iter.h"         /* for argv_iter_init_argv, etc */
+#include "backupfile.h"                /* for xget_version, etc */
+#include "closein.h"           /* for close_stdin */
+#include "configmake.h"                /* for LOCALEDIR */
+#include "copy.h"              /* for cp_options, etc */
+#include "cp-hash.h"           /* for hash_init */
+#include "error.h"             /* for error */
+#include "fts_.h"              /* for FTS, FTSENT, FTS_PHYSICAL, etc */
+#include "physmem.h"           /* for physmem_available */
+#include "progname.h"          /* for program_name, etc */
+#include "quotearg.h"          /* for quotearg_colon */
+#include "quote.h"             /* for quote */
+#include "readtokens0.h"       /* for Tokens, readtokens0, etc */
+#include "system.h"            /* for _, STREQ, etc */
+#include "unlocked-io.h"       /* for fputs */
+#include "xalloc.h"            /* for xalloc_die, xstrdup */
+#include "xfts.h"              /* for xfts_open */
+
+/* The official name of this program (e.g., no `g' prefix).  */
+#define PROGRAM_NAME "rename"
+
+#define AUTHORS \
+  proper_name ("Sami Kerola")
+
+#if RENAME_DEBUG
+#define FTS_CROSS_CHECK(Fts) fts_cross_check (Fts)
+#else
+#define FTS_CROSS_CHECK(Fts)
+#endif
+
+/* True if we have ever read the standard input. */
+static bool have_read_stdin;
+
+/* If true change the modes of directories recursively. */
+static bool recurse;
+
+/* If true only print out what a rename would do.  */
+static bool test_only = false;
+
+/* If set tradional string + replacement is wanted */
+static char *original_str;
+static char *replacement;
+
+/* The exec is using these.  */
+static char *cmd_to_run = NULL;
+static char **cmd_args = NULL;
+
+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;
+  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;
+  x->require_preserve_context = false;
+  x->preserve_xattr = true;
+  x->require_preserve_xattr = false;
+  x->recursive = false;
+  x->sparse_mode = SPARSE_AUTO;
+  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;
+}
+
+/* Change first occurence of 'substr' to be 'replacement' in
+   'filename'.  This function is used when classical rename is been
+   queried.  */
+static char *
+construct_dest_name (const char *filename, const char *substr,
+                    const char *replacement)
+{
+  char *tok = NULL;
+  char *newstr = NULL;
+  char *basename;
+
+  /* Search if file name has path.  */
+  basename = strrchr (filename, '/');
+  if (basename == NULL)
+    tok = strstr (filename, substr);
+  else
+    tok = strstr (basename, substr);
+  if (tok == NULL)
+    return xstrdup (filename);
+
+  newstr =
+    xmalloc (strlen (filename) - strlen (substr) + strlen (replacement) + 1);
+
+  /* Construct new file name.  */
+  if (newstr == NULL)
+    return NULL;
+  memcpy (newstr, filename, tok - filename);
+  memcpy (newstr + (tok - filename), replacement, strlen (replacement));
+  memcpy (newstr + (tok - filename) + strlen (replacement),
+         tok + strlen (substr),
+         strlen (filename) - strlen (substr) - (tok - filename));
+  memset (newstr + strlen (filename) - strlen (substr) + strlen (replacement),
+         0, 1);
+  return newstr;
+}
+
+/* Child will perform filename altering.  */
+char *
+request_child (const char *filename)
+{
+  pid_t exec_pid;
+  FILE *mother_file = NULL;
+  int child2mother_pipefd[2];
+  int mother2child_pipefd[2];
+  int status = EXIT_SUCCESS;
+  char *ret = NULL;
+  char *basename;
+  size_t skip = 0;
+
+  signal (SIGPIPE, SIG_IGN);
+  pipe (mother2child_pipefd);
+  pipe (child2mother_pipefd);
+
+  exec_pid = fork ();
+  if (0 < exec_pid)
+    {
+      /* Mother.  */
+      close (mother2child_pipefd[0]);
+      close (child2mother_pipefd[1]);
+      mother_file = fdopen (child2mother_pipefd[0], "r");
+    }
+  if (0 == exec_pid)
+    {
+      /* Child.  */
+      close (STDIN_FILENO);
+      close (STDOUT_FILENO);
+      close (mother2child_pipefd[1]);
+      close (child2mother_pipefd[0]);
+
+      /* Associate input and output with a pipe.  */
+      dup2 (mother2child_pipefd[0], STDIN_FILENO);
+      dup2 (child2mother_pipefd[1], STDOUT_FILENO);
+
+      execvp (cmd_to_run, cmd_args);
+      /* Never reached after successful execvp.  */
+      error (0, errno, _("executing file %s failed"), quote (cmd_to_run));
+      exit (EXIT_FAILURE);
+    }
+
+  /* Search if file name has path.  */
+  basename = strrchr (filename, '/');
+  if (basename != NULL)
+    skip = strlen (filename) - strlen (basename);
+  /* Communicate with the child.  */
+  if (0 < skip)
+    write (mother2child_pipefd[1], basename, strlen (basename));
+  else
+    write (mother2child_pipefd[1], filename, strlen (filename));
+  close (mother2child_pipefd[1]);
+  /* Child working, prepare memory for new file name.  */
+  ret = xmalloc ((size_t) PATH_MAX);
+  memcpy (ret, filename, skip);
+  /* Read what child had to tell.  */
+  fgets (ret + skip, PATH_MAX - skip, mother_file);
+  waitpid (exec_pid, &status, 0);
+  /* Child is terminated.  */
+  if (mother_file != NULL)
+    fclose (mother_file);
+  if (WEXITSTATUS (status) != EXIT_SUCCESS)
+    {
+      exit (EXIT_FAILURE);
+    }
+  return ret;
+}
+
+/* Request subroutine about how file name should be affected, and
+   perform rename file operation.  */
+static bool
+rename_entity (FTSENT * ent, struct cp_options *x)
+{
+  char *destination;
+  bool copy_into_self;
+  bool rename_succeeded;
+  bool ok = true;
+
+  /* Ask for new name. */
+  if (cmd_to_run != NULL)
+    {
+      destination = request_child (ent->fts_path);
+    }
+  else
+    {
+      destination =
+       construct_dest_name (ent->fts_path, original_str, replacement);
+    }
+
+  /* Perform move operation. */
+  if (test_only == true)
+    printf ("%s -> %s\n", ent->fts_path, destination);
+  else
+    ok &=
+      copy (ent->fts_path, destination, false, x, &copy_into_self,
+           &rename_succeeded);
+
+  if (ok)
+    {
+      if (copy_into_self)
+       {
+         ok = false;
+       }
+    }
+  free (destination);
+  return ok;
+}
+
+/* Read entities from directory, and request renaming.  */
+static bool
+prepare_rename (char **files, int bit_flags, struct cp_options *x)
+{
+  bool ok = true;
+
+  if (*files != NULL)
+    {
+      FTS *fts = xfts_open (files, bit_flags, NULL);
+
+      while (1)
+       {
+         FTSENT *ent;
+
+         ent = fts_read (fts);
+         if (ent == NULL)
+           {
+             if (errno != 0)
+               {
+                 error (0, errno,
+                        _("fts_read failed: %s"),
+                        quotearg_colon (fts->fts_path));
+                 ok = false;
+               }
+             break;
+           }
+         FTS_CROSS_CHECK (fts);
+         if (!recurse)
+           fts_set (fts, ent, FTS_SKIP);
+         ok &= rename_entity (ent, x);
+       }
+
+      if (fts_close (fts) != 0)
+       {
+         error (0, errno, _("fts_close failed"));
+         ok = false;
+       }
+    }
+
+  return ok;
+}
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    emit_try_help ();
+  else
+    {
+      printf (_("\
+Usage: %s [OPTION]... EXPRESSION REPLACEMENT FILE...\n\
+  or:  %s [OPTION] -0 FILE -e COMMAND [OPTION]\n\
+"), program_name, program_name);
+      fputs (_("\
+Rename lots of files in directory.\n\
+\n\
+"), stdout);
+      fputs (_("\
+  -0, --files0-from=FILE  read rename entries from NUL-terminated file\n\
+  -e, --exec=COMMAND      manipulate file names by using a command\n\
+  -r, -R, --recursive     rename in directories recursively\n\
+  -x, --one-file-system   skip directories on different file systems\n\
+      --backup[=CONTROL]  make a backup of each existing destination file\n\
+  -b                      like --backup but does not accept an argument\n\
+  -S, --suffix=SUFFIX     override the usual backup suffix\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 -f, -i, -n, only the final one takes effect.\n\
+  -t, --test              show a dry-run print out\n\
+  -v, --verbose           explain what is being done\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      /* FIXME: IMHO the messges bellow should be BACKUP_OPTION_DESCRIPTION
+         similar to help and version.  */
+      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)
+{
+  bool make_backups = false;
+  int i, c;
+  char *backup_suffix_string;
+  char *version_control_string = NULL;
+  struct cp_options x;
+  char **files;
+  char *files_from = NULL;
+  bool ok;
+  struct Tokens tok;
+
+  /* Bit flags that control how fts works.  */
+  int bit_flags = FTS_NOSTAT;
+  bit_flags |= FTS_PHYSICAL;
+
+  static const struct option longopts[] = {
+    {"files0-from", optional_argument, NULL, '0'},
+    {"backup", optional_argument, NULL, 'b'},
+    {"dereference-args", no_argument, NULL, 'D'},
+    {"exec", required_argument, NULL, 'e'},
+    {"exec+", required_argument, NULL, 'E'},
+    {"force", no_argument, NULL, 'f'},
+    {"interactive", no_argument, NULL, 'i'},
+    {"dereference", no_argument, NULL, 'L'},
+    {"no-clobber", no_argument, NULL, 'n'},
+    {"recursive", no_argument, NULL, 'r'},
+    {"suffix", required_argument, NULL, 'S'},
+    {"test", no_argument, NULL, 't'},
+    {"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);
+  recurse = false;
+
+  /* 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, "0::b::e:finrS:tv", longopts,
+                      NULL)) != -1)
+    {
+      switch (c)
+       {
+       case '0':
+         if (files_from != NULL)
+           error (0, 0,
+                  _("warning: input file %s will not be read"),
+                  quote (files_from));
+         if (optarg)
+           files_from = optarg;
+         else
+           files_from = "-";
+         break;
+       case 'b':
+         make_backups = true;
+         if (optarg)
+           version_control_string = optarg;
+         break;
+       case 'e':
+         cmd_to_run = optarg;
+         cmd_args = (argv + optind) - 1;
+         optind = argc;
+         if (files_from == NULL)
+           files_from = "-";
+         goto options_done;
+       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 'r':
+       case 'R':
+         recurse = true;
+         break;
+       case 'v':
+         x.verbose = true;
+         break;
+       case 'S':
+         make_backups = true;
+         backup_suffix_string = optarg;
+         break;
+       case 't':
+         test_only = true;
+         break;
+
+         case_GETOPT_HELP_CHAR;
+         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+       default:
+         usage (EXIT_FAILURE);
+       }
+    }
+options_done:
+
+  /* Check usage of backup options */
+  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);
+
+  /* Command expects
+     ++ match and replacement arguments
+     ++ or exec option
+     accoppanied with
+     ++ file list as command line argument
+     ++ or read from file option argument
+     ++ or read stdin */
+  if (optind < argc - 1)
+    {
+      original_str = argv[optind];
+      replacement = argv[optind + 1];
+      optind += 2;
+    }
+  if (original_str == NULL && cmd_to_run == NULL)
+    {
+      error (0, 0, _("not enough arguments"));
+      usage (EXIT_FAILURE);
+    }
+  if (optind == argc)
+    {
+      files_from = "-";
+    }
+
+  hash_init ();
+  bool read_tokens = false;
+  struct argv_iterator *ai;
+  if (files_from)
+    {
+      FILE *stream;
+      /* When using --files0-from=F, you may not specify any files
+         on the command-line.  */
+      if (optind < argc)
+       {
+         error (0, 0, _("extra operand %s"), quote (argv[optind]));
+         fprintf (stderr, "%s\n",
+                  _("file operands cannot be combined with --files0-from"));
+         usage (EXIT_FAILURE);
+       }
+
+      if (STREQ (files_from, "-"))
+       stream = stdin;
+      else
+       {
+         stream = fopen (files_from, "r");
+         if (stream == NULL)
+           error (EXIT_FAILURE, errno, _("cannot open %s for reading"),
+                  quote (files_from));
+       }
+
+      /* Read the file list into RAM if we can detect its size and that
+         size is reasonable.  Otherwise, we'll read a name at a time.  */
+      struct stat st;
+      if (fstat (fileno (stream), &st) == 0
+         && S_ISREG (st.st_mode)
+         && st.st_size <= MIN (10 * 1024 * 1024, physmem_available () / 2))
+       {
+         read_tokens = true;
+         readtokens0_init (&tok);
+         if (!readtokens0 (stream, &tok) || fclose (stream) != 0)
+           error (EXIT_FAILURE, 0, _("cannot read file names from %s"),
+                  quote (files_from));
+         files = tok.tok;
+         ai = argv_iter_init_argv (files);
+
+       }
+      else
+       {
+         files = NULL;
+         ai = argv_iter_init_stream (stream);
+       }
+    }
+  else
+    {
+      static char *stdin_only[] = { NULL };
+      files = (optind < argc ? argv + optind : stdin_only);
+      ai = argv_iter_init_argv (files);
+    }
+
+  if (!ai)
+    xalloc_die ();
+
+  /* prepare_rename will not find cycles on its own, so ask fts_read
+     to check for them accurately.  */
+  bit_flags |= FTS_TIGHT_CYCLE_CHECK;
+
+  ok = true;
+  for (i = 0; /* empty */ ; i++)
+    {
+      bool skip_file = false;
+      enum argv_iter_err ai_err;
+      char *input_path = argv_iter (ai, &ai_err);
+      if (!input_path)
+       {
+         switch (ai_err)
+           {
+           case AI_ERR_EOF:
+             goto argv_iter_done;
+           case AI_ERR_READ:
+             error (0, errno, _("%s: read error"),
+                    quotearg_colon (files_from));
+             ok = false;
+             goto argv_iter_done;
+           case AI_ERR_MEM:
+             xalloc_die ();
+           case AI_ERR_OK:
+           default:
+             assert (!"unexpected error code from argv_iter");
+           }
+       }
+
+      if (!input_path[0])
+       {
+         /* Diagnose a zero-length file name.  When it's one
+            among many, knowing the record number may help.  */
+         if (files_from == NULL)
+           error (0, 0, "%s", _("invalid zero-length file name"));
+         else
+           {
+             /* Using the standard `filename:line-number:' prefix here is
+                not totally appropriate, since NUL is the separator, not NL,
+                but it might be better than nothing.  */
+             unsigned long int file_number = argv_iter_n_args (ai);
+             error (0, 0, "%s:%lu: %s", quotearg_colon (files_from),
+                    file_number, _("invalid zero-length file name"));
+           }
+         skip_file = true;
+       }
+
+      if (skip_file)
+       ok = false;
+      else
+       {
+         static char *temp_argv[] = { NULL, NULL };
+         temp_argv[0] = input_path;
+         ok &= prepare_rename (temp_argv, bit_flags, &x);
+       }
+    }
+argv_iter_done:
+
+  /* No command line arguments.  Read from stdin.  However, no
+     arguments with --files0-from is reason to skip this. */
+  if (ok && !files_from && argv_iter_n_args (ai) == 0)
+    ok &= prepare_rename (NULL, bit_flags, &x);
+
+  if (read_tokens)
+    readtokens0_free (&tok);
+
+  argv_iter_free (ai);
+
+  if (have_read_stdin && close (STDIN_FILENO) != 0)
+    error (EXIT_FAILURE, errno, "-");
+
+  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d8bc930..d620dc8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -226,6 +226,7 @@ TESTS =                                             \
   misc/readlink-fp-loop                                \
   misc/readlink-root                           \
   misc/realpath                                        \
+  misc/rename-foo2bar                          \
   misc/runcon-no-reorder                       \
   misc/sha1sum                                 \
   misc/sha1sum-vec                             \
diff --git a/tests/misc/help-version b/tests/misc/help-version
index e19f5b1..40ee81c 100755
--- a/tests/misc/help-version
+++ b/tests/misc/help-version
@@ -178,6 +178,7 @@ ginstall_setup () { args="$tmp_in $tmp_in2"; }
 mv_setup () { args="$tmp_in $tmp_in2"; }
 mkdir_setup () { args=$tmp_dir/subdir; }
 realpath_setup () { args=$tmp_in; }
+rename_setup () { args=--version; }
 rmdir_setup () { args=$tmp_dir; }
 rm_setup () { args=$tmp_in; }
 shred_setup () { args=$tmp_in; }
diff --git a/tests/misc/invalid-opt b/tests/misc/invalid-opt
index 8f7024c..6e6f059 100755
--- a/tests/misc/invalid-opt
+++ b/tests/misc/invalid-opt
@@ -42,6 +42,7 @@ my %exit_status =
     tty => 2,
     printf => 0,
     printenv => 2,
+    rename => 1,
   );

 my %expected_out =
diff --git a/tests/misc/rename-foo2bar b/tests/misc/rename-foo2bar
new file mode 100755
index 0000000..0760f6f
--- /dev/null
+++ b/tests/misc/rename-foo2bar
@@ -0,0 +1,30 @@
+#!/bin/sh
+# test rename basic functionality
+
+# Copyright (C) 2012 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

-- 
   Sami Kerola
   http://www.iki.fi/kerolasa/



reply via email to

[Prev in Thread] Current Thread [Next in Thread]