coreutils
[Top][All Lists]
Advanced

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

[PATCH] rename: move command from util-linux to coreutils


From: Sami Kerola
Subject: [PATCH] rename: move command from util-linux to coreutils
Date: Tue, 24 Dec 2013 22:28:24 +0000

The implementation is completely rewrote without chaning existing
command line syntax.  This implementation adds --exec option, which
will allow use of a string manipulation command, such as 'tr' or 'sed'
to determine target names or rename operations.

* AUTHORS: Add my name.
* NEWS: Mention the new program.
* README: Reference the new program.
* build-aux/gen-lists-of-programs.sh: Update.
* doc/coreutils.texi: Document the new command.
* man/.gitignore: Ignore the new man page.
* man/local.mk: Reference the new man page.
* man/rename.x: A new template.
* po/POTFILES.in: Add new c file.
* scripts/git-hooks/commit-msg: Allow rename: commit prefix.
* src/.gitignore: Ignore the new binary.
* src/local.mk: Reference the new command.
* src/rename.c: New file.
* tests/local.mk: Reference the new tests.
* tests/misc/rename-exec: Test new command exec option.
* tests/misc/rename-foobar: Basic test for the new command.
---
 AUTHORS                            |   1 +
 NEWS                               |   5 +
 README                             |   8 +-
 build-aux/gen-lists-of-programs.sh |   1 +
 doc/coreutils.texi                 | 117 ++++++-
 man/.gitignore                     |   1 +
 man/local.mk                       |   1 +
 man/rename.x                       |  10 +
 po/POTFILES.in                     |   1 +
 scripts/git-hooks/commit-msg       |  13 +-
 src/.gitignore                     |   1 +
 src/local.mk                       |   3 +
 src/rename.c                       | 659 +++++++++++++++++++++++++++++++++++++
 tests/local.mk                     |   2 +
 tests/misc/rename-exec.sh          |  30 ++
 tests/misc/rename-foo2bar.sh       |  30 ++
 16 files changed, 872 insertions(+), 11 deletions(-)
 create mode 100644 man/rename.x
 create mode 100644 src/rename.c
 create mode 100755 tests/misc/rename-exec.sh
 create mode 100755 tests/misc/rename-foo2bar.sh

diff --git a/AUTHORS b/AUTHORS
index df21e90..e2ae037 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -64,6 +64,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 cbac480..212b697 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,11 @@ GNU coreutils NEWS                                    -*- 
outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New programs
+
+  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.22 (2013-12-13) [stable]
 
diff --git a/README b/README
index b81897e..4f65819 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 numfmt 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
+  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
 
 See the file NEWS for a list of major changes in the current release.
 
diff --git a/build-aux/gen-lists-of-programs.sh 
b/build-aux/gen-lists-of-programs.sh
index bf63ee3..2962a82 100755
--- a/build-aux/gen-lists-of-programs.sh
+++ b/build-aux/gen-lists-of-programs.sh
@@ -96,6 +96,7 @@ normal_progs='
     pwd
     readlink
     realpath
+    rename
     rm
     rmdir
     runcon
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 9c2b79c..27a3602 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -96,6 +96,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.
@@ -195,7 +196,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
@@ -382,6 +383,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
 
@@ -13116,6 +13118,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
@@ -13340,6 +13343,118 @@ Exit status:
 1 otherwise.
 @end display
 
+
address@hidden rename invocation
address@hidden @command{rename}: Rename multiple files using a pattern
+
address@hidden rename
address@hidden rename multiple files
+
address@hidden moves multiple files using a pattern. Synopsis:
+
address@hidden
+rename address@hidden @var{expression} @var{replacement} @var{file...}
+rename address@hidden --files0-from=FILE  @var{--exec} @var{command}
+find . -print0 \
+  | rename address@hidden @var{expression} @var{replacement}
+find . -print0 \
+  | rename address@hidden @var{--exec} @var{command}
address@hidden example
+
+The @command{rename} command does multiple @var{file} renames same way as
address@hidden 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}.
+
address@hidden @samp
+
address@hidden
+
+
address@hidden -e @var{command -o --option}
address@hidden --exec @var{command -o --option}
address@hidden -e
address@hidden --exec
address@hidden external execution
+Use @command{command -o --option} to manipulate file name string.
address@hidden
+# Show how ba would be removed from bash
+find /bin/ba* -print0 | rename -t -e sed 's/bash/sh/'
address@hidden smallexample
+
+
address@hidden -R
address@hidden -r
address@hidden --recursive
address@hidden -R
address@hidden -r
address@hidden --recursive
address@hidden recursive renaming
+Rename files recursively.
+
address@hidden -x
address@hidden --one-file-system
address@hidden -x
address@hidden --one-file-system
address@hidden file systems, omitting renaming to different
+Skip subdirectories that are on different file systems from the one that
+the rename started on.
+
address@hidden
+
address@hidden
+
address@hidden -f
address@hidden --force
address@hidden -f
address@hidden --force
address@hidden prompts, omitting
+Do not prompt the user before removing a destination file.
address@hidden
+
address@hidden -i
address@hidden --interactive
address@hidden -i
address@hidden --interactive
address@hidden prompts, forcing
+Prompt whether to overwrite each existing destination file, regardless
+of its permissions.
+If the response is not affirmative, the file is skipped.
address@hidden
+
address@hidden -n
address@hidden --no-clobber
address@hidden -n
address@hidden --no-clobber
address@hidden prompts, omitting
+Do not overwrite an existing file.
address@hidden
+This option is mutually exclusive with @option{-b} or @option{--backup} option.
+
address@hidden -t
address@hidden --test
address@hidden -t
address@hidden --test
+Show printout how @command{rename} would affect without performing any changes.
+
address@hidden -v
address@hidden --verbose
address@hidden -v
address@hidden --verbose
+Print the name of each file before moving it.
+
address@hidden table
+
address@hidden exit status of @command{rename}
+Exit status:
+
address@hidden
+0 all files where renamed succesfully
+1 otherwise.
address@hidden display
+
+
 @node mktemp invocation
 @section @command{mktemp}: Create temporary file or directory
 
diff --git a/man/.gitignore b/man/.gitignore
index aef4002..b2993c5 100644
--- a/man/.gitignore
+++ b/man/.gitignore
@@ -61,6 +61,7 @@ ptx.1
 pwd.1
 readlink.1
 realpath.1
+rename.1
 rm.1
 rmdir.1
 seq.1
diff --git a/man/local.mk b/man/local.mk
index 45dbcb9..c228c5a 100644
--- a/man/local.mk
+++ b/man/local.mk
@@ -127,6 +127,7 @@ man/ptx.1:       src/ptx
 man/pwd.1:       src/pwd
 man/readlink.1:  src/readlink
 man/realpath.1:  src/realpath
+man/rename.1:    src/rename
 man/rm.1:        src/rm
 man/rmdir.1:     src/rmdir
 man/runcon.1:    src/runcon
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 5c219d9..a009f50 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -106,6 +106,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 559a0ea..0bb5d1a 100755
--- a/scripts/git-hooks/commit-msg
+++ b/scripts/git-hooks/commit-msg
@@ -17,12 +17,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 numfmt 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 numfmt 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 25573df..43895af 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -71,6 +71,7 @@ ptx
 pwd
 readlink
 realpath
+rename
 rm
 rmdir
 runcon
diff --git a/src/local.mk b/src/local.mk
index fe80f41..4f92d61 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -136,6 +136,7 @@ src_link_LDADD = $(LDADD)
 src_ln_LDADD = $(LDADD)
 src_logname_LDADD = $(LDADD)
 src_ls_LDADD = $(LDADD)
+src_rename_LDADD = $(LDADD)
 
 # This must *not* depend on anything in lib/, since it is used to generate
 # src/primes.h.  If it depended on libcoreutils.a, that would pull all lib/*.c
@@ -213,6 +214,7 @@ src_vdir_LDADD = $(src_ls_LDADD)
 src_cp_LDADD += $(copy_ldadd)
 src_ginstall_LDADD += $(copy_ldadd)
 src_mv_LDADD += $(copy_ldadd)
+src_rename_LDADD += $(copy_ldadd)
 
 src_mv_LDADD += $(remove_ldadd)
 src_rm_LDADD += $(remove_ldadd)
@@ -356,6 +358,7 @@ src_realpath_SOURCES = src/realpath.c src/relpath.c 
src/relpath.h
 src_timeout_SOURCES = src/timeout.c src/operand2sig.c
 
 src_mv_SOURCES = src/mv.c src/remove.c $(copy_sources) $(selinux_sources)
+src_rename_SOURCES = src/rename.c $(copy_sources)
 src_rm_SOURCES = src/rm.c src/remove.c
 
 src_mkdir_SOURCES = src/mkdir.c src/prog-fprintf.c $(selinux_sources)
diff --git a/src/rename.c b/src/rename.c
new file mode 100644
index 0000000..e800f43
--- /dev/null
+++ b/src/rename.c
@@ -0,0 +1,659 @@
+/* rename -- rename multiple files using a pattern
+   Copyright (C) 2013- 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 <getopt.h>             /* for no_argument, optind, optarg, etc */
+#include <libintl.h>            /* for bindtextdomain, textdomain */
+#include <selinux/selinux.h>    /* for is_selinux_enabled */
+#include <stdio.h>              /* for NULL, stdout, fprintf, etc */
+#include <sys/param.h>          /* for MIN */
+#include <sys/wait.h>           /* for waitpid */
+
+#include "argv-iter.h"          /* for argv_iter_init_argv, etc */
+#include "backupfile.h"         /* for xget_version, etc */
+#include "full-write.h"         /* for full_write */
+#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 "quotearg.h"           /* for quotearg_colon */
+#include "quote.h"              /* for quote */
+#include "readtokens0.h"        /* for Tokens, readtokens0, etc */
+#include "system.h"             /* for _, STREQ, etc */
+#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 
*repl)
+{
+  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 (repl) + 1);
+
+  /* Construct new file name.  */
+  if (newstr == NULL)
+    return NULL;
+  memcpy (newstr, filename, tok - filename);
+  memcpy (newstr + (tok - filename), repl, strlen (repl));
+  memcpy (newstr + (tok - filename) + strlen (repl),
+          tok + strlen (substr),
+          strlen (filename) - strlen (substr) - (tok - filename));
+  memset (newstr + strlen (filename) - strlen (substr) + strlen (repl), 0, 1);
+  return newstr;
+}
+
+/* Child will perform filename altering.  */
+static 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);
+  errno = 0;
+  if (pipe (mother2child_pipefd) != 0)
+    error (EXIT_FAILURE, errno, _("failed to create pipe"));
+  if (pipe (child2mother_pipefd) != 0)
+    error (EXIT_FAILURE, errno, _("failed to create pipe"));
+
+  exec_pid = fork ();
+  if (0 < exec_pid)
+    {
+      /* Mother.  */
+      if (close (mother2child_pipefd[0]) != 0)
+        error (EXIT_FAILURE, errno, _("failed to close pipe"));
+      if (close (child2mother_pipefd[1]) != 0)
+        error (EXIT_FAILURE, errno, _("failed to close pipe"));
+      mother_file = fdopen (child2mother_pipefd[0], "r");
+    }
+  if (0 == exec_pid)
+    {
+      /* Child.  */
+      if (close (STDIN_FILENO) != 0)
+        error (EXIT_FAILURE, errno, _("read error"));
+      if (close (STDOUT_FILENO) != 0)
+        error (EXIT_FAILURE, errno, _("write error"));
+      if (close (mother2child_pipefd[1]) != 0)
+        error (EXIT_FAILURE, errno, _("failed to close pipe"));
+      if (close (child2mother_pipefd[0]) != 0)
+        error (EXIT_FAILURE, errno, _("failed to close pipe"));
+
+      /* Associate input and output with a pipe.  */
+      if (dup2 (mother2child_pipefd[0], STDIN_FILENO) == -1)
+        error (EXIT_FAILURE, errno, _("moving input pipe"));
+      if (dup2 (child2mother_pipefd[1], STDOUT_FILENO) == -1)
+        error (EXIT_FAILURE, errno, _("moving output pipe"));
+
+      if (execvp (cmd_to_run, cmd_args) != 0)
+        error (0, errno, _("executing file %s failed"), quote (cmd_to_run));
+      /* Never reached after successful execvp.  */
+      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)
+    {
+      if (full_write
+          (mother2child_pipefd[1], basename,
+           strlen (basename)) != (strlen (basename)) && errno != EPIPE)
+        {
+          error (EXIT_FAILURE, errno, _("write error"));
+        }
+    }
+  else
+    {
+      if (full_write
+          (mother2child_pipefd[1], filename,
+           strlen (filename)) != (strlen (filename)) && errno != EPIPE)
+        {
+          error (EXIT_FAILURE, errno, _("write error"));
+        }
+    }
+  if (close (mother2child_pipefd[1]) != 0)
+    error (EXIT_FAILURE, errno, _("failed to close pipe"));
+  /* Child working, prepare memory for new file name.  */
+  ret = xmalloc ((size_t) PATH_MAX);
+  memcpy (ret, filename, skip);
+  /* Read what child had to tell.  */
+  if (fgets (ret + skip, PATH_MAX - skip, mother_file) == NULL)
+    error (EXIT_FAILURE, errno, _("reading executable conversion failed"));
+  if (waitpid (exec_pid, &status, 0) == -1 && errno != ECHILD)
+    error (EXIT_FAILURE, errno, _("waiting for child process"));
+  /* Child is terminated.  */
+  if (mother_file != NULL)
+    if (fclose (mother_file) != 0)
+      error (EXIT_FAILURE, errno, _("read error"));
+  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\
+      --dry-run           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;
+  static char const *files_from = NULL;
+  bool ok;
+  struct Tokens tok;
+
+  /* Bit flags that control how fts works.  */
+  int bit_flags = FTS_NOSTAT;
+  bit_flags |= FTS_PHYSICAL;
+
+  bool read_tokens = false;
+  struct argv_iterator *ai;
+
+  /* for long options with no corresponding short option, use enum */
+  enum
+  {
+    DRY_RUN_OPTION = CHAR_MAX + 1
+  };
+
+  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'},
+    {"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'},
+    {"dry-run", no_argument, NULL, DRY_RUN_OPTION},
+    {"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:v", 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 DRY_RUN_OPTION:
+          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 pair
+     ++ or exec option
+     accoppanied with file list
+     ++ as command line argument(s)
+     ++ or read from --files0-from=parameter
+     ++ or read from 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);
+    }
+
+  hash_init ();
+  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/local.mk b/tests/local.mk
index 9a54e7b..184a3ee 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -301,6 +301,8 @@ all_tests =                                 \
   tests/misc/readlink-fp-loop.sh               \
   tests/misc/readlink-root.sh                  \
   tests/misc/realpath.sh                       \
+  tests/misc/rename-exec.sh                    \
+  tests/misc/rename-foo2bar.sh                 \
   tests/misc/runcon-no-reorder.sh              \
   tests/misc/sha1sum.pl                                \
   tests/misc/sha1sum-vec.pl                    \
diff --git a/tests/misc/rename-exec.sh b/tests/misc/rename-exec.sh
new file mode 100755
index 0000000..4e892d0
--- /dev/null
+++ b/tests/misc/rename-exec.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# test rename basic functionality
+
+# Copyright (C) 2013 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=.}/tests/init.sh"; path_prepend_ ../src
+print_ver_ rename
+
+for i in foo1 foo2 ; do
+  echo a > $i || framework_failure_
+done
+
+find foo? -print0 | rename --exec tr a-z A-Z 2>/dev/null || fail=1
+test -f FOO1 || fail=1
+test -f FOO2 || fail=1
+
+Exit $fail
diff --git a/tests/misc/rename-foo2bar.sh b/tests/misc/rename-foo2bar.sh
new file mode 100755
index 0000000..6463dfc
--- /dev/null
+++ b/tests/misc/rename-foo2bar.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# test rename basic functionality
+
+# Copyright (C) 2013 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=.}/tests/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.8.5.2




reply via email to

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