bug-coreutils
[Top][All Lists]
Advanced

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

Patch for new feature of 'rm' command


From: David Bell
Subject: Patch for new feature of 'rm' command
Date: Thu, 8 Jul 2004 17:24:54 +1000 (EST)

Hello:

Here is a patch to the 'remove' functionality of coreutils-5.2.1 which
adds the --confirm (-c) option to the 'rm' command.  This basically prompts
once for the list of files to be removed instead of for each file as the -i
option does.  I find this new feature extremely convenient when I am
deleting lots of files but also want to be a bit safe.  The -c and -i
options can both be used to do the full file list prompt followed by the
individual file prompts.

The terminal width for the file list prompting is hard coded which isn't
too much of a drawback I think.  But if you want to be fancy, you could ask
the terminal's width and use that.

As part of the patch the REMOVE_CONFIRM_NOT_REQUIRED_LIST environment
variable is added which lets users define "safe" files which don't need
confirmation.  That fact that this exists, and how it exactly works,
can be debated by you.  I wasn't too sure about it myself, but I tried it
out to see how useful it might be.  Change its behavior or just remove it
totally if you want.

I hope you accept this patch for coreutils, or at least implement a
feature similar to this.

Thanks,
David I. Bell
address@hidden


----------------------- BEGIN PATCH ----------------------------------------
*** coreutils-5.2.1/src/mv.c    2004-02-08 02:41:02.000000000 +1100
--- coreutils-5.2.1-dbell/src/mv.c      2004-07-06 17:41:20.000000000 +1000
***************
*** 106,111 ****
--- 106,112 ----
    x->stdin_tty = 0;
  
    x->verbose = 0;
+   x->confirm = 0;
  }
  
  static void
*** coreutils-5.2.1/src/remove.c        2003-11-09 18:31:51.000000000 +1100
--- coreutils-5.2.1-dbell/src/remove.c  2004-07-08 15:32:40.411514600 +1000
***************
*** 37,42 ****
--- 37,44 ----
  #include "remove.h"
  #include "root-dev-ino.h"
  
+ #define       CONFIRM_WIDTH 75
+ 
  /* Avoid shadowing warnings because these are functions declared
     in dirname.h as well as locals used below.  */
  #define dir_name rm_dir_name
***************
*** 1124,1129 ****
--- 1126,1360 ----
    return remove_dir (ds, filename, cwd_state, x);
  }
  
+ /* Determine whether or not the specified file name is matched by any of
+    the items in the specified space-separated list.  Leading directory
+    components are handled specially, and asterisks are allowed at the
+    beginning and end of the last component of an item for a simple
+    wildcarding capability.  */
+ static bool
+ filename_matches (char const *full_item_list, char const *filename)
+ {
+   char const *file_component;
+   char const *item_begin;
+   char const *item_end;
+   char const *item_component;
+   int file_len;
+   int file_path_len;
+   int file_component_len;
+   int item_len;
+   int item_path_len;
+   int item_component_len;
+ 
+   /* Find the last component of the file name and associated lengths.  */
+   file_component = strrchr (filename, '/');
+   if (file_component != NULL)
+     file_component++;
+   else
+     file_component = filename;
+ 
+   file_len = strlen (filename);
+   file_component_len = strlen (file_component);
+   file_path_len = file_len - file_component_len;
+ 
+   /* Iterate over all of the items in the list looking for a match to
+      the file name.  If a match is found for any item then return true.  */
+   for (item_begin = full_item_list ; ; item_begin = item_end)
+     {
+       /* Find the beginning and end of the next item which is delimited
+          by spaces.  If there are no more items then leave the loop.  */
+       while (*item_begin == ' ')
+       item_begin++;
+       if (*item_begin == '\0')
+       break;
+       item_end = item_begin;
+       while (*item_end && (*item_end != ' '))
+       item_end++;
+       item_len = item_end - item_begin;
+ 
+       /* Find the last path component of the item and some lengths. */
+       item_component = memrchr (item_begin, '/', item_len);
+       if (item_component != NULL)
+       item_component++;
+       else item_component = item_begin;
+ 
+       item_component_len = item_end - item_component;
+       item_path_len = item_len - item_component_len;
+ 
+       /* If the item has any directory components then the file name must
+        also have the same directory components in order to match.  */
+       if (item_path_len > 0)
+       {
+         if ((file_path_len != item_path_len) ||
+             (memcmp (filename, item_begin, item_path_len) != 0))
+           {
+             continue;
+           }
+       }
+ 
+       /* Now we only need to compare the last components of the filename
+        and item for a match.  Do the comparison according to whether the
+        last component of the item has beginning or ending asterisks.  */
+       if ((*item_component == '*') && (item_end[-1] == '*'))
+       {
+         /* There are asterisks both at the beginning and end of the item's
+            last component (even perhaps the same asterisk!).  Remove them
+            from the item's last component and see if there is anything left.
+            If not, then all file names match.  */
+         item_component++;
+         item_component_len -= 2;
+         if (item_component_len <= 0)
+           return true;
+ 
+         /* The file name is matched only if it contains the item's last
+            component value somewhere within it.  */
+         if (memmem (file_component, file_component_len,
+                     item_component, item_component_len) != NULL)
+           {
+             return true;
+           }
+       }
+       else if (*item_component == '*')
+       {
+         /* There is an asterisk at the beginning of the item's last
+            component.  Remove it, and then the file name is matched only
+            if it ends with the item's value.  */
+         item_component++;
+         item_component_len--;
+ 
+         if ((file_component_len >= item_component_len) &&
+           (memcmp (file_component + (file_component_len - item_component_len),
+                    item_component, item_component_len) == 0))
+           {
+             return true;
+           }
+       }
+       else if (item_end[-1] == '*')
+       {
+         /* There is an asterisk at the end of the item's last component.
+            Remove it, and then the file name is matched only if it begins
+            with the item's value.  */
+         item_component_len--;
+ 
+         if ((file_component_len >= item_component_len) &&
+           (memcmp (file_component, item_component, item_component_len) == 0))
+           {
+             return true;
+           }
+       }
+       else
+       {
+         /* There are no asterisks at all in the item's last component.
+            The file name matches if it is the same as the item's value.  */
+         if ((file_component_len == item_component_len) &&
+           (memcmp (file_component, item_component, item_component_len) == 0))
+           {
+             return true;
+           }
+       }
+     }
+ 
+   return false;
+ }
+ 
+ /* Return whether or not confirmation of the list of file names is required
+    based on their values and a possible confirmation-not-required value.  */
+ static bool
+ confirm_needed (size_t n_files, char const *const *file)
+ {
+   char const *confirm_not_required_list;
+   size_t i;
+ 
+   /* Get the list of space-separated items which don't need confirming.
+      If this isn't defined then confirmation is always needed.  */
+   confirm_not_required_list = getenv ("REMOVE_CONFIRM_NOT_REQUIRED_LIST");
+ 
+   if (confirm_not_required_list == NULL)
+     return true;
+ 
+   /* If any of the filenames isn't matched then confirmation is needed.  */
+   for (i = 0; i < n_files; i++)
+     {
+       if (!filename_matches (confirm_not_required_list, file[i]))
+       return true;
+     }
+ 
+   return false;
+ }
+ 
+ /* Confirm the deletion of the complete file list with one question.
+    Returns RM_OK if confirmation was not required, the user agrees, or
+    if all of the file names in the list are known to not need confirming.
+    Note: Individual questioning for each file can still happen later.  */
+ static enum RM_status
+ confirm_list (Dirstack_state *ds, size_t n_files, char const *const *file,
+               struct rm_options const *x)
+ {
+   char const *prefix;
+   size_t i;
+   int column;
+ 
+   /* If no confirmation is required then return success.  */
+   if (!x->confirm)
+     return RM_OK;
+ 
+   /* See if user confirmation is really needed for these file names.  */
+   if (!confirm_needed (n_files, file))
+     return RM_OK;
+ 
+   /* Confirmation is required.  Print the whole file list (on multiple
+      lines if necessary) with a possible indication that the removal will
+      be interactive.  */
+   fprintf (stderr, _("%s: "), program_name);
+   column = strlen (program_name) + 2;
+ 
+   if (x->interactive)
+     {
+       prefix = ((n_files == 1) ?
+       _("interactively remove file in list:") :
+       _("interactively remove files in list:"));
+     }
+   else
+     {
+       prefix = ((n_files == 1) ?
+       _("remove file in list:") :
+       _("remove files in list:"));
+     }
+ 
+   fputs (prefix, stderr);
+   column += strlen (prefix);
+ 
+   for (i = 0; i < n_files; i++)
+     {
+       char const *quoted_name = quote (full_filename (file[i]));
+       int len = strlen (quoted_name) + 1;
+ 
+       if (i != 0)
+       {
+         fputc (',', stderr);
+         column++;
+       }
+ 
+       if (column + len > CONFIRM_WIDTH)
+       {
+         fputs ("\n ", stderr);
+         column = 1;
+       }
+ 
+       fputc (' ', stderr);
+       fputs (quoted_name, stderr);
+       column += len;
+     }
+ 
+     fputs ( _("? "), stderr);
+ 
+     /* Now see if the user agrees to this file list.  */
+     if (!yesno ())
+       return RM_USER_DECLINED;
+ 
+     /* Confirmation is successful.  */
+     return RM_OK;
+ }
+ 
  /* Remove all files and/or directories specified by N_FILES and FILE.
     Apply the options in X.  */
  enum RM_status
***************
*** 1139,1144 ****
--- 1370,1387 ----
  
    ds = ds_init ();
  
+   /* Perform the file list confirmation check.  If confirmation fails
+      then return without removing any of the files.  */
+   status = confirm_list (ds, n_files, file, x);
+ 
+   if (status != RM_OK)
+     {
+       ds_free (ds);
+ 
+       return status;
+     }
+ 
+   /* Now walk through the file list deleting each one.  */
    for (i = 0; i < n_files; i++)
      {
        enum RM_status s;
*** coreutils-5.2.1/src/remove.h        2003-11-07 19:08:32.000000000 +1100
--- coreutils-5.2.1-dbell/src/remove.h  2004-07-06 16:25:44.000000000 +1000
***************
*** 28,33 ****
--- 28,36 ----
  
    /* If nonzero, display the name of each file removed.  */
    int verbose;
+ 
+   /* If nonzero, ask user to confirm full file list before any removals.  */
+   int confirm;
  };
  
  enum RM_status
*** coreutils-5.2.1/src/rm.c    2004-01-22 09:27:02.000000000 +1100
--- coreutils-5.2.1-dbell/src/rm.c      2004-07-08 15:31:27.121656352 +1000
***************
*** 79,84 ****
--- 79,85 ----
    {"directory", no_argument, NULL, 'd'},
    {"force", no_argument, NULL, 'f'},
    {"interactive", no_argument, NULL, 'i'},
+   {"confirm", no_argument, NULL, 'c'},
  
    {"no-preserve-root", no_argument, 0, NO_PRESERVE_ROOT},
    {"preserve-root", no_argument, 0, PRESERVE_ROOT},
***************
*** 109,119 ****
        fputs (_("\
  Remove (unlink) the FILE(s).\n\
  \n\
    -d, --directory       unlink FILE, even if it is a non-empty directory\n\
                            (super-user only; this works only if your system\n\
                             supports `unlink' for nonempty directories)\n\
    -f, --force           ignore nonexistent files, never prompt\n\
!   -i, --interactive     prompt before any removal\n\
  "), stdout);
        fputs (_("\
        --no-preserve-root do not treat `/' specially (the default)\n\
--- 110,121 ----
        fputs (_("\
  Remove (unlink) the FILE(s).\n\
  \n\
+   -c, --confirm         confirm full file list before any removal\n\
    -d, --directory       unlink FILE, even if it is a non-empty directory\n\
                            (super-user only; this works only if your system\n\
                             supports `unlink' for nonempty directories)\n\
    -f, --force           ignore nonexistent files, never prompt\n\
!   -i, --interactive     prompt for each file before removal\n\
  "), stdout);
        fputs (_("\
        --no-preserve-root do not treat `/' specially (the default)\n\
***************
*** 153,158 ****
--- 155,161 ----
    x->root_dev_ino = NULL;
    x->stdin_tty = isatty (STDIN_FILENO);
    x->verbose = 0;
+   x->confirm = 0;
  }
  
  int
***************
*** 173,191 ****
  
    rm_option_init (&x);
  
!   while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
      {
        switch (c)
        {
        case 0:         /* Long option.  */
          break;
  
        case 'd':
          x.unlink_dirs = 1;
          break;
  
        case 'f':
          x.interactive = 0;
          x.ignore_missing_files = 1;
          break;
  
--- 176,199 ----
  
    rm_option_init (&x);
  
!   while ((c = getopt_long (argc, argv, "cdfirvR", long_opts, NULL)) != -1)
      {
        switch (c)
        {
        case 0:         /* Long option.  */
          break;
  
+       case 'c':
+         x.confirm = 1;
+         break;
+ 
        case 'd':
          x.unlink_dirs = 1;
          break;
  
        case 'f':
          x.interactive = 0;
+         x.confirm = 0;
          x.ignore_missing_files = 1;
          break;
  
*** coreutils-5.2.1/doc/coreutils.texi  2004-03-11 04:50:52.000000000 +1100
--- coreutils-5.2.1-dbell/doc/coreutils.texi    2004-07-08 16:16:34.128128816 
+1000
***************
*** 6829,6834 ****
--- 6829,6862 ----
  
  @table @samp
  
+ @item -c
+ @itemx --confirm
+ @opindex -c
+ @opindex --confirm
+ Ask once for confirmation of the full file list before any removals.
+ Ignore any previous @option{--force} (@option{-f}) option.
+ If the response does not begin with @samp{y} or @samp{Y}, no files are 
removed.
+ The @option{--interactive} (@option{-i}) option can be used along with this
+ option to perform individual prompting for each file after the full file
+ list has been confirmed.
+ 
+ If the @env{REMOVE_CONFIRM_NOT_REQUIRED_LIST} environment variable is defined,
+ then it specifies a list of space-separated items defining file names which
+ never require confirmation.
+ If all of the file names in the full file list are matched by one or
+ more of the items, then confirmation is not performed.
+ However, if one or more of the file names is not matched, then confirmation
+ of the full file list is performed as if the environment variable was
+ not defined.
+ If an item contains a directory component (e.g., contains a slash) then
+ it can only match file names which have that same directory component.
+ In addition, the last component of each item is compared against the last
+ component of the file name to see if the file name is matched.
+ A very simple form of wildcarding is available within each item.
+ An asterisk can be used at the beginning or end (or both) of the last
+ component of an item to match filename components which end with or
+ begin with (or contain) the specified string.
+ 
  @item -d
  @itemx --directory
  @opindex -d
***************
*** 6849,6855 ****
  @opindex -f
  @opindex --force
  Ignore nonexistent files and never prompt the user.
! Ignore any previous @option{--interactive} (@option{-i}) option.
  
  @item -i
  @itemx --interactive
--- 6877,6884 ----
  @opindex -f
  @opindex --force
  Ignore nonexistent files and never prompt the user.
! Ignore any previous @option{--interactive} (@option{-i}) and
! @option{--confirm} (@option{-c}) options.
  
  @item -i
  @itemx --interactive
------------------------ END PATCH -------------------------------------------





reply via email to

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