about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2021-11-26 07:00:11 +0000
committerLaurent Bercot <ska@appnovation.com>2021-11-26 07:00:11 +0000
commit45ca80e0e1509c613f05cdb5fe8ec1157a4a7a48 (patch)
tree08da8661a666889a0e84201af36f3d8834aeac10
parent901efc65acfee5cad65a6ed84fc540c5ba6ebdf0 (diff)
downloadexecline-45ca80e0e1509c613f05cdb5fe8ec1157a4a7a48.tar.gz
execline-45ca80e0e1509c613f05cdb5fe8ec1157a4a7a48.tar.xz
execline-45ca80e0e1509c613f05cdb5fe8ec1157a4a7a48.zip
Add shell matching option to case
Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--doc/case.html44
-rw-r--r--src/execline/case.c68
2 files changed, 86 insertions, 26 deletions
diff --git a/doc/case.html b/doc/case.html
index a82eec8..8ae817b 100644
--- a/doc/case.html
+++ b/doc/case.html
@@ -31,7 +31,7 @@ matches.
 </p>
 
 <pre>
-     case [ -E | -e ] [ -i ] [ -n | -N ] <em>value</em>
+     case [ -S | -s ] [ -E | -e ] [ -i ] [ -n | -N ] <em>value</em>
      {
        [ regex { <em>prog...</em> } ]
        [ regex { <em>prog...</em> } ]
@@ -57,6 +57,13 @@ is empty. </li>
 <h2> Options </h2>
 
 <ul>
+ <li> <tt>-s</tt>&nbsp;: Shell matching. The <em>regex</em> words will not be
+interpreted as regular expressions, but as shell expressions to be interpreted
+via <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html">fnmatch()</a>.
+The other options also change meanings, see the <em>Shell matching</em> section below. </li>
+ <li> <tt>-S</tt>&nbsp;: Regular expression matching. This is the default. This
+section, and all of the sections below except the <em>Shell matching</em> one,
+assumes that it is the case. </li>
  <li> <tt>-e</tt>&nbsp;: Interpret the <em>regex</em> words as
 <a href="https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03">basic
 regular expressions</a>. </li>
@@ -116,6 +123,41 @@ to the output of the <tt>/usr/bin/env</tt> command:
 2=baz
 </pre>
 
+<h2> Shell matching </h2>
+
+<p>
+ If the <tt>-s</tt> option has been given to <tt>case</tt>, then the <em>regex</em>
+words are not interpreted as regular expressions, but as shell patterns, as is
+performed by the shell's
+<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_04_05">case</a>
+conditional construct. This has the following consequences:
+</p>
+
+<ul>
+ <li> Subexpression matching is always disabled. </li>
+ <li> <em>prog...</em> is always executed with an unmodified environment. </li>
+ <li> The options to the <tt>case</tt> command change meanings: instead of
+controlling how the <em>regex</em> regular expressions are interpreted by the
+<a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/regcomp.html">regcomp()</a>
+primitive, they now control how <em>value</em> is matched against the <em>regex</em> patterns
+(which are not regular expressions!) via the
+<a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html">fnmatch()</a>
+primitive. Namely:
+ <ul>
+  <li> <tt>-e</tt>&nbsp;: Treat a backslash as an ordinary character; do not allow
+character escaping in patterns. (This sets the <tt>FNM_NOESCAPE</tt> flag.) </li>
+  <li> <tt>-E</tt>&nbsp;: Allow backslash escaping in patterns. This is the default.
+(This clears the <tt>FNM_NOESCAPE</tt> flag.) </li>
+  <li> <tt>-i</tt>&nbsp;: Treat a period (<tt>.</tt>) as a special character for
+matching (set <tt>FNM_PERIOD</tt>). By default, the period is not a special character
+(<tt>FNM_PERIOD</tt> is cleared). </li>
+  <li> <tt>-N</tt>&nbsp;: Treat patterns as pathnames: make slashes character special.
+(This sets the <tt>FNM_PATHNAME</tt> flag.) </li>
+  <li> <tt>-n</tt>&nbsp;: Do not treat patterns as pathnames (clear the
+<tt>FNM_PATHNAME</tt> flag). This is the default. </li>
+ </ul> </li>
+</ul>
+
 <h2> Notes </h2>
 
 <ul>
diff --git a/src/execline/case.c b/src/execline/case.c
index 77f905e..b026256 100644
--- a/src/execline/case.c
+++ b/src/execline/case.c
@@ -2,6 +2,7 @@
 
 #include <string.h>
 #include <regex.h>
+#include <fnmatch.h>
 
 #include <skalibs/gccattributes.h>
 #include <skalibs/types.h>
@@ -11,7 +12,7 @@
 
 #include <execline/execline.h>
 
-#define USAGE "case [ -e | -E ] [ -n | -N ] [ -i ] value { re1 { prog1... } re2 { prog2... } ... } progdefault... "
+#define USAGE "case [ -s | -S ] [ -e | -E ] [ -n | -N ] [ -i ] value { re1 { prog1... } re2 { prog2... } ... } progdefault... "
 #define dieusage() strerr_dieusage(100, USAGE)
 
 static void execit (char const *const *argv, char const *expr, char const *s, regmatch_t const *pmatch, size_t n) gccattr_noreturn ;
@@ -47,6 +48,7 @@ static void execit (char const *const *argv, char const *expr, char const *s, re
 
 int main (int argc, char const **argv, char const *const *envp)
 {
+  int flagshell = 0 ;
   int flagextended = 1 ;
   int flagnosub = 1 ;
   int flagicase = 0 ;
@@ -58,10 +60,12 @@ int main (int argc, char const **argv, char const *const *envp)
     subgetopt l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      int opt = subgetopt_r(argc, argv, "eEnNi", &l) ;
+      int opt = subgetopt_r(argc, argv, "sSeEnNi", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
+        case 's' : flagshell = 1 ; break ;
+        case 'S' : flagshell = 0 ; break ;
         case 'e' : flagextended = 0 ; break ;
         case 'E' : flagextended = 1 ; break ;
         case 'N' : flagnosub = 0 ; break ;
@@ -82,42 +86,56 @@ int main (int argc, char const **argv, char const *const *envp)
   {
     char const *expr = argv[i++] ;
     int argc2 ;
-    regex_t re ;
     if (i == argc1) strerr_dief1x(100, "malformed case block") ;
     argc2 = el_semicolon(argv + i) ;
     if (i + argc2 >= argc1) strerr_dief1x(100, "unterminated regex block") ;
+    if (flagshell)
     {
-      int r ;
-      size_t len = strlen(expr) ;
-      char tmp[len+3] ;
-      tmp[0] = '^' ;
-      memcpy(tmp + 1, expr, len) ;
-      tmp[1+len] = '$' ;
-      tmp[2+len] = 0 ;
-      r = regcomp(&re, tmp, (flagextended ? REG_EXTENDED : 0) | (flagicase ? REG_ICASE : 0) | (flagnosub ? REG_NOSUB : 0) | REG_NEWLINE) ;
-      if (r)
+      int r = fnmatch(expr, s, (flagextended ? 0 : FNM_NOESCAPE) | (flagicase ? FNM_PERIOD : 0) | (flagnosub ? 0 : FNM_PATHNAME)) ;
+      if (!r)
       {
-        char buf[256] ;
-        regerror(r, &re, buf, 256) ;
-        strerr_diefu4x(r == REG_ESPACE ? 111 : 100, "regcomp \"^", argv[i], "$\": ", buf) ;
+        argv[i + argc2] = 0 ;
+        xexec0(argv + i) ;
       }
+      else if (r != FNM_NOMATCH)
+        strerr_warnw2x("invalid fnmatch pattern: ", expr) ;
     }
+    else
     {
-      regmatch_t pmatch[re.re_nsub && !flagnosub ? re.re_nsub + 1 : 1] ;
-      int r = regexec(&re, s, re.re_nsub + 1, pmatch, 0) ;
-      if (!r)
+      regex_t re ;
       {
-        argv[i + argc2] = 0 ;
-        execit(argv + i, expr, s, pmatch, flagnosub ? 0 : 1 + re.re_nsub) ;
+        int r ;
+        size_t len = strlen(expr) ;
+        char tmp[len+3] ;
+        tmp[0] = '^' ;
+        memcpy(tmp + 1, expr, len) ;
+        tmp[1+len] = '$' ;
+        tmp[2+len] = 0 ;
+        r = regcomp(&re, tmp, (flagextended ? REG_EXTENDED : 0) | (flagicase ? REG_ICASE : 0) | (flagnosub ? REG_NOSUB : 0) | REG_NEWLINE) ;
+        if (r)
+        {
+          char buf[256] ;
+          regerror(r, &re, buf, 256) ;
+          strerr_diefu4x(r == REG_ESPACE ? 111 : 100, "regcomp \"^", argv[i], "$\": ", buf) ;
+        }
       }
-      if (r != REG_NOMATCH)
       {
-        char buf[256] ;
-        regerror(r, &re, buf, 256) ;
-        strerr_diefu6x(111, "match string \"", s, "\" against regex \"", expr, "\": ", buf) ;
+        regmatch_t pmatch[re.re_nsub && !flagnosub ? re.re_nsub + 1 : 1] ;
+        int r = regexec(&re, s, re.re_nsub + 1, pmatch, 0) ;
+        if (!r)
+        {
+          argv[i + argc2] = 0 ;
+          execit(argv + i, expr, s, pmatch, flagnosub ? 0 : 1 + re.re_nsub) ;
+        }
+        if (r != REG_NOMATCH)
+        {
+          char buf[256] ;
+          regerror(r, &re, buf, 256) ;
+          strerr_diefu6x(111, "match string \"", s, "\" against regex \"", expr, "\": ", buf) ;
+        }
       }
+      regfree(&re) ;
     }
-    regfree(&re) ;
     i += argc2 + 1 ;
   }
   xexec0(argv + argc1 + 1) ;