On Tue, Jun 28, 2011 at 23:37, James Youngman <[email protected]> wrote:
> If we're going to implement a "rename" command, can we make one that
> copes with internal newlines (IIRC "rename" does not) and doesn't
> force the user to assume path names are text[1]?
>
> [1] UNIX-like kernels mostly think path names are byte sequences
> terminated by '\0', possibly including one or more instances of '/'.
> In particular, filesystems routinely contain files with a multiplicity
> of character encodings.

Sounds reasonable. After hearing what you and others has said I made
couple modifications to the command.

o Support recursion.

o Support input from stdin, and have a switch for null terminated
names. That will allow find -print0 | rename combination to rock.

o Add upper & lower casing options, which needs to obey locale.

o Add --test option. If this option is really wanted I need assistance
how to make test work properly. The current copy.h flags does not
support test and I feel reluctant to add such. Secondly this is not
even that simple. With --sed one can do such wacky renames that
expressing how the situation will look like after a run might be very
difficult.

o Add --sed to do complicated name manipulations (only an idea).
Assuming this is desired should I simply copy all necessary code from
sed to be a coreutils/src/sed_functions.c? Perhaps sed style string
manipulations should be part of gnulib. Perhaps something other,
better and maintainer acceptable?

See bellow snapshot of a usage output after latest modifications. Same
can be found in rough and functionally broken patch attachment.

-- snip
$ ./rename --help
Usage: ./rename [OPTION]... EXPRESSION REPLACEMENT FILE...
  or:  ./rename [OPTION]... FILE...
  or:  ./rename [OPTION]...
Rename lots of files in directory.

      --files0-from=FILE  read rename entries from NUL-terminated file
  -r, -R, --recursive     rename in directories recursively
  -D, --dereference-args  dereference only symlinks that are listed on the
                            command line
  -L, --dereference       dereference all symbolic links
  -x, --one-file-system   skip directories on different file systems
      --backup[=CONTROL]  make a backup of each existing destination file
  -b                      like --backup but does not accept an argument
  -S, --suffix=SUFFIX     override the usual backup suffix
  -f, --force             do not prompt before overwriting
  -i, --interactive       prompt before overwrite
  -n, --no-clobber        do not overwrite an existing file
If you specify more than one of -f, -i, -n, only the final one takes effect.
      --sed=s/regexp/replacement/
                          use sed regular expression replace syntax FIXME
  -u, --uppercase         upper case the names FIXME
  -l, --lowercase         lower case the names FIXME
  -t, --test              show a dry-run print out FIXME
  -v, --verbose           explain what is being done
      --help     display this help and exit
      --version  output version information and exit
-- snip

p.s. I will away keyboard for next week so finalization of the command
needs to wait a bit. Good side is that a pause of a week will give
plenty of time to discuss about the utility.

-- 
   Sami Kerola
   http://www.iki.fi/kerolasa/
From 8fea1a509c4bafee53202ea598a99b4491283655 Mon Sep 17 00:00:00 2001
From: Sami Kerola <[email protected]>
Date: Sat, 2 Jul 2011 17:25:53 +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              |  563 +++++++++++++++++++++++++++++++++++++++++++++
 tests/Makefile.am         |    1 +
 tests/misc/rename-foo2bar |   30 +++
 13 files changed, 696 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..75bc196 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -62,6 +62,7 @@ printf: David MacKenzie
 ptx: François Pinard
 pwd: Jim Meyering
 readlink: Dmitry V. Levin
+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 d58df26..2623078 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 c59af2f..8e78e5e 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..78a41f2
--- /dev/null
+++ b/src/rename.c
@@ -0,0 +1,563 @@
+/* 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/>.  */
+
+/* This command supports the same syntax as util-linux rename, which was
+   written by Andries Brouwer Jan 1st, 2000.  The coreutils version has some
+   similarities to http://rename.sourceforge.net/ maintained by Andy Xuming
+   but be warned, some options and features differ.
+
+   Written 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 <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
+
+/* For long options that have no equivalent short option, use a
+   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
+enum
+{
+  FILES0_FROM_OPTION = CHAR_MAX + 1,
+  SED_OPTION
+};
+
+/* 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;
+
+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;
+}
+
+static bool
+do_rename (FTS * fts, FTSENT * ent, struct cp_options *x)
+{
+  bool copy_into_self;
+  bool rename_succeeded;
+  bool ok = true;
+  char *strdupped;
+  char *file;
+  char *dir;
+
+  /* FIXME: I have not completely though though this, but something like
+     following is needed.
+     o split ent->fts_path to dir & basename
+     o apply s/foo/bar/ to basename (and/or lower/uppercasing)
+     o use move source to destination, unless dry-run is queried(1)
+     o check results of the move  */
+  /* FIXME: (1) How to do dry-run? Either I am mistaking or the current copy
+     flags does not have support for doing this sort of check. Implementint
+     proper dry-run might end up to be ifficult task. Should it for instance
+     inform that a regular expressions will end up conflicts? For example:
+
+     $ rename --sed=s/^.*$/pebcak/ *
+
+     +1 dropping this feature, like this.  */
+
+  if (ok)
+    {
+      if (copy_into_self)
+       {
+         /* The likely case is that expression does not match, so
+            replacement does not have to happen.  */
+         /* FIXME: In some cases this can be detected earlier; if
+            s/expression/expression/ should return after sed comman
+            compilation and tell the 'change' is superficial.  */
+         ok = false;
+       }
+    }
+
+  return ok;
+}
+
+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 &= do_rename (fts, 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)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+            program_name);
+  else
+    {
+      printf (_("\
+Usage: %s [OPTION]... EXPRESSION REPLACEMENT FILE...\n\
+  or:  %s [OPTION]... FILE...\n\
+  or:  %s [OPTION]...\n\
+"), program_name, program_name, program_name);
+      fputs (_("\
+Rename lots of files in directory.\n\
+\n\
+"), stdout);
+      fputs (_("\
+      --files0-from=FILE  read rename entries from NUL-terminated file\n\
+  -r, -R, --recursive     rename in directories recursively\n\
+  -D, --dereference-args  dereference only symlinks that are listed on the\n\
+                            command line\n\
+  -L, --dereference       dereference all symbolic links\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\
+      --sed=s/regexp/replacement/\n\
+                          use sed regular expression replace syntax FIXME\n\
+  -u, --uppercase         upper case the names FIXME\n\
+  -l, --lowercase         lower case the names FIXME\n\
+  -t, --test              show a dry-run print out FIXME\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)
+{
+  int i, c;
+  bool make_backups = false;
+  char *backup_suffix_string;
+  char *version_control_string = NULL;
+  struct cp_options x;
+  char *cwd_only[2];
+  int nfiles;
+  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", required_argument, NULL, FILES0_FROM_OPTION},
+    {"backup", optional_argument, NULL, 'b'},
+    {"dereference-args", no_argument, NULL, 'D'},
+    {"force", no_argument, NULL, 'f'},
+    {"interactive", no_argument, NULL, 'i'},
+    {"lowercase", no_argument, NULL, 'l'},
+    {"dereference", no_argument, NULL, 'L'},
+    {"no-clobber", no_argument, NULL, 'n'},
+    {"recursive", no_argument, NULL, 'r'},
+    {"sed", required_argument, NULL, SED_OPTION},
+    {"suffix", required_argument, NULL, 'S'},
+    {"test", no_argument, NULL, 't'},
+    {"uppercase", no_argument, NULL, 'u'},
+    {"verbose", no_argument, NULL, 'v'},
+    {"one-file-system", no_argument, NULL, 'x'},
+    {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, "bDfilLnrRS:tuvx", longopts, NULL)) != -1)
+    {
+      switch (c)
+       {
+       case FILES0_FROM_OPTION:
+         if (files_from != NULL)
+           error (0, 0,
+                  _
+                  ("warning: input file %s will not be read"),
+                  quote (files_from));
+         files_from = optarg;
+         break;
+       case 'b':
+         make_backups = true;
+         if (optarg)
+           version_control_string = optarg;
+         break;
+       case 'D':
+         bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
+         break;
+       case 'f':
+         x.interactive = I_ALWAYS_YES;
+         break;
+       case 'i':
+         x.interactive = I_ASK_USER;
+         break;
+       case 'l':
+         /* FIXME: set flag and use towlower before rename.  */
+         break;
+       case 'L':
+         bit_flags = FTS_LOGICAL;
+         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 SED_OPTION:
+         /* FIXME: whole functionality missing.  */
+         break;
+       case 'u':
+         /* FIXME: set flag and use towupper before rename.  */
+         break;
+       case 't':
+         /* FIXME: this could be difficult, see comment in do_rename function. 
 */
+         break;
+       case 'x':
+         bit_flags |= FTS_XDEV;
+         break;
+
+         case_GETOPT_HELP_CHAR;
+         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+       default:
+         usage (EXIT_FAILURE);
+       }
+    }
+
+  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 ();
+
+  /* FIXME: This is the point to figure out what user is asking and make
+     reqular expression accordingly.  */
+  /* FIXME: Add sed s/regex/replacement/ functionality.  Should I simply
+     copy code from sed?  */
+  /* FIXME: If an user calls command with A. Brouwer util-linux style e.g.
+
+     $ rename expression replacement file-with-expression.txt
+
+     The first two arguments should converted sed format removed from file
+     list.  */
+
+  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;
+         nfiles = tok.n_tok;
+         ai = argv_iter_init_argv (files);
+       }
+      else
+       {
+         files = NULL;
+         nfiles = 0;
+         ai = argv_iter_init_stream (stream);
+       }
+    }
+  else
+    {
+      static char *stdin_only[] = { NULL };
+      files = (optind < argc ? argv + optind : stdin_only);
+      nfiles = (optind < argc ? argc - optind : 1);
+      ai = argv_iter_init_argv (files);
+    }
+
+  if (!ai)
+    xalloc_die ();
+
+  if (optind == argc)
+    {
+      error (0, 0, _("not enough arguments"));
+      usage (EXIT_FAILURE);
+    }
+
+  /* prepare_rename won't find cycles on its own, so ask fts_read
+     to check for them accurately. */
+  bit_flags |= FTS_TIGHT_CYCLE_CHECK;
+
+  ok = true;
+  static char *temp_argv[] = { NULL, NULL };
+  for (i = 0; /* */ ; 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 ();
+           default:
+             assert (!"unexpected error code from argv_iter");
+           }
+       }
+      if (files_from && STREQ (files_from, "-") && STREQ (input_path, "-"))
+       {
+         /* Give a better diagnostic in an unusual case:
+            printf - | wc --files0-from=- */
+         error (0, 0, _("when reading file names from stdin, "
+                        "no file name of %s allowed"), quote (input_path));
+         skip_file = true;
+       }
+
+      if (!input_path[0])
+       {
+         /* Diagnose a zero-length file name.  When it's one
+            among many, knowing the record number may help.
+            FIXME: currently print the record number only with
+            --files0-from=FILE.  Maybe do it for argv, too?  */
+         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
+       {
+         temp_argv[0] = input_path;
+         ok &= prepare_rename (temp_argv, bit_flags, &x);
+       }
+    }
+ argv_iter_done:
+
+  /* No arguments on the command line is fine.  That means read from stdin.
+     However, no arguments on the --files0-from input stream is an error
+     means don't read anything.  */
+  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 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.6

Reply via email to