From 8fea1a509c4bafee53202ea598a99b4491283655 Mon Sep 17 00:00:00 2001 From: Sami Kerola 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 --- 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 . */ + +/* 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 + +#include // for assert +#include // for errno +#include // for no_argument, optind, optarg, etc +#include // for bindtextdomain, textdomain +#include // for CHAR_MAX +#include // for setlocale, LC_ALL +#include // for is_selinux_enabled +#include // for false, true, bool +#include // for NULL, stdout, fprintf, etc +#include // for EXIT_FAILURE, exit, etc +#include // for MIN +#include // for stat, fstat, S_ISREG +#include // 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 . + +. "${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