i-bash/lib/glob/strmatch.c

846 lines
22 KiB
C
Raw Normal View History

2001-11-13 17:56:06 +00:00
/* strmatch.c -- ksh-like extended pattern matching for the shell and filename
1998-04-17 19:52:44 +00:00
globbing. */
/* Copyright (C) 1991, 1997 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
Bash 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 2, or (at your option) any later
version.
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
Bash 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.
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
You should have received a copy of the GNU General Public License along
with Bash; see the file COPYING. If not, write to the Free Software
2000-03-17 21:46:59 +00:00
Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
1998-04-17 19:52:44 +00:00
#include <config.h>
2000-03-17 21:46:59 +00:00
#include <stdio.h> /* for debugging */
2001-04-06 19:14:31 +00:00
2001-11-13 17:56:06 +00:00
#include "strmatch.h"
1998-04-17 19:52:44 +00:00
#include "collsyms.h"
2001-11-13 17:56:06 +00:00
#include <chartypes.h>
1996-08-26 18:22:31 +00:00
1999-02-19 17:11:39 +00:00
#if defined (HAVE_STRING_H)
# include <string.h>
#else
# include <strings.h>
#endif /* HAVE_STRING_H */
1998-04-17 19:52:44 +00:00
static int gmatch ();
static char *brackmatch ();
#ifdef EXTENDED_GLOB
static int extmatch ();
1999-02-19 17:11:39 +00:00
static char *patscan ();
1998-04-17 19:52:44 +00:00
#endif
2001-11-13 17:56:06 +00:00
#if !defined (isascii) && !defined (HAVE_ISASCII)
1998-04-17 19:52:44 +00:00
# define isascii(c) ((unsigned int)(c) <= 0177)
#endif
1996-08-26 18:22:31 +00:00
1998-07-23 14:37:54 +00:00
/* The result of FOLD is an `unsigned char' */
2001-11-13 17:56:06 +00:00
# define FOLD(c) ((flags & FNM_CASEFOLD) \
? TOLOWER ((unsigned char)c) \
1998-07-23 14:37:54 +00:00
: ((unsigned char)c))
1998-04-17 19:52:44 +00:00
#ifndef STREQ
#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)
#endif
2001-04-06 19:14:31 +00:00
/* We use strcoll(3) for range comparisons in bracket expressions,
even though it can have unwanted side effects in locales
2000-03-17 21:46:59 +00:00
other than POSIX or US. For instance, in the de locale, [A-Z] matches
2001-04-06 19:14:31 +00:00
all characters. */
2000-03-17 21:46:59 +00:00
1998-04-17 19:52:44 +00:00
#if defined (HAVE_STRCOLL)
2000-03-17 21:46:59 +00:00
/* Helper function for collating symbol equivalence. */
2001-04-06 19:14:31 +00:00
static int rangecmp (c1, c2)
1998-04-17 19:52:44 +00:00
int c1, c2;
{
static char s1[2] = { ' ', '\0' };
static char s2[2] = { ' ', '\0' };
int ret;
/* Eight bits only. Period. */
c1 &= 0xFF;
c2 &= 0xFF;
if (c1 == c2)
return (0);
s1[0] = c1;
s2[0] = c2;
if ((ret = strcoll (s1, s2)) != 0)
return ret;
return (c1 - c2);
}
#else /* !HAVE_STRCOLL */
2001-04-06 19:14:31 +00:00
# define rangecmp(c1, c2) ((int)(c1) - (int)(c2))
1998-04-17 19:52:44 +00:00
#endif /* !HAVE_STRCOLL */
#if defined (HAVE_STRCOLL)
static int collequiv (c1, c2)
int c1, c2;
{
2001-04-06 19:14:31 +00:00
return (rangecmp (c1, c2) == 0);
1998-04-17 19:52:44 +00:00
}
#else
# define collequiv(c1, c2) ((c1) == (c2))
#endif
static int
collsym (s, len)
char *s;
int len;
{
register struct _collsym *csp;
for (csp = posix_collsyms; csp->name; csp++)
{
if (STREQN(csp->name, s, len) && csp->name[len] == '\0')
2001-04-06 19:14:31 +00:00
return (csp->code);
1998-04-17 19:52:44 +00:00
}
if (len == 1)
return s[0];
return -1;
}
2001-11-13 17:56:06 +00:00
#ifdef HAVE_LIBC_FNM_EXTMATCH
int
strmatch (pattern, string, flags)
char *pattern;
char *string;
int flags;
{
char *se, *pe;
if (string == 0 || pattern == 0)
return FNM_NOMATCH;
return (fnmatch (pattern, string, flags));
}
#else /* !HAVE_LIBC_FNM_EXTMATCH */
1996-08-26 18:22:31 +00:00
int
2001-11-13 17:56:06 +00:00
strmatch (pattern, string, flags)
1996-08-26 18:22:31 +00:00
char *pattern;
char *string;
int flags;
{
1998-04-17 19:52:44 +00:00
char *se, *pe;
1996-08-26 18:22:31 +00:00
1998-04-17 19:52:44 +00:00
if (string == 0 || pattern == 0)
return FNM_NOMATCH;
se = string + strlen (string);
pe = pattern + strlen (pattern);
return (gmatch (string, se, pattern, pe, flags));
}
2001-11-13 17:56:06 +00:00
#endif /* !HAVE_LIBC_FNM_EXTMATCH */
1998-04-17 19:52:44 +00:00
/* Match STRING against the filename pattern PATTERN, returning zero if
it matches, FNM_NOMATCH if not. */
static int
gmatch (string, se, pattern, pe, flags)
char *string, *se;
char *pattern, *pe;
int flags;
{
register char *p, *n; /* pattern, string */
register char c; /* current pattern character */
register char sc; /* current string character */
1996-08-26 18:22:31 +00:00
1998-04-17 19:52:44 +00:00
p = pattern;
n = string;
if (string == 0 || pattern == 0)
return FNM_NOMATCH;
2001-04-06 19:14:31 +00:00
#if DEBUG_MATCHING
fprintf(stderr, "gmatch: string = %s; se = %s\n", string, se);
fprintf(stderr, "gmatch: pattern = %s; pe = %s\n", pattern, pe);
#endif
1998-04-17 19:52:44 +00:00
while (p < pe)
1996-08-26 18:22:31 +00:00
{
1998-04-17 19:52:44 +00:00
c = *p++;
c = FOLD (c);
sc = n < se ? *n : '\0';
#ifdef EXTENDED_GLOB
1999-02-19 17:11:39 +00:00
/* extmatch () will handle recursively calling gmatch, so we can
just return what extmatch() returns. */
1998-04-17 19:52:44 +00:00
if ((flags & FNM_EXTMATCH) && *p == '(' &&
(c == '+' || c == '*' || c == '?' || c == '@' || c == '!')) /* ) */
1999-02-19 17:11:39 +00:00
{
int lflags;
/* If we're not matching the start of the string, we're not
concerned about the special cases for matching `.' */
lflags = (n == string) ? flags : (flags & ~FNM_PERIOD);
return (extmatch (c, n, se, p, pe, lflags));
}
1998-04-17 19:52:44 +00:00
#endif
1996-08-26 18:22:31 +00:00
switch (c)
{
1998-04-17 19:52:44 +00:00
case '?': /* Match single character */
if (sc == '\0')
return FNM_NOMATCH;
else if ((flags & FNM_PATHNAME) && sc == '/')
1997-06-05 14:59:13 +00:00
/* If we are matching a pathname, `?' can never match a `/'. */
1998-04-17 19:52:44 +00:00
return FNM_NOMATCH;
else if ((flags & FNM_PERIOD) && sc == '.' &&
1996-08-26 18:22:31 +00:00
(n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
1997-06-05 14:59:13 +00:00
/* `?' cannot match a `.' if it is the first character of the
string or if it is the first character following a slash and
we are matching a pathname. */
1998-04-17 19:52:44 +00:00
return FNM_NOMATCH;
1996-08-26 18:22:31 +00:00
break;
1998-04-17 19:52:44 +00:00
case '\\': /* backslash escape removes special meaning */
if (p == pe)
return FNM_NOMATCH;
if ((flags & FNM_NOESCAPE) == 0)
1997-06-05 14:59:13 +00:00
{
c = *p++;
1998-04-17 19:52:44 +00:00
/* A trailing `\' cannot match. */
if (p > pe)
return FNM_NOMATCH;
c = FOLD (c);
1997-06-05 14:59:13 +00:00
}
1998-07-23 14:37:54 +00:00
if (FOLD (sc) != (unsigned char)c)
1998-04-17 19:52:44 +00:00
return FNM_NOMATCH;
1996-08-26 18:22:31 +00:00
break;
1998-04-17 19:52:44 +00:00
case '*': /* Match zero or more characters */
if (p == pe)
return 0;
if ((flags & FNM_PERIOD) && sc == '.' &&
1996-08-26 18:22:31 +00:00
(n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
1997-06-05 14:59:13 +00:00
/* `*' cannot match a `.' if it is the first character of the
string or if it is the first character following a slash and
we are matching a pathname. */
1998-04-17 19:52:44 +00:00
return FNM_NOMATCH;
1996-08-26 18:22:31 +00:00
2001-11-13 17:56:06 +00:00
/* Collapse multiple consecutive `*' and `?', but make sure that
1997-06-05 14:59:13 +00:00
one character of the string is consumed for each `?'. */
1998-04-17 19:52:44 +00:00
for (c = *p++; (c == '?' || c == '*'); c = *p++)
1996-12-23 17:02:34 +00:00
{
1998-04-17 19:52:44 +00:00
if ((flags & FNM_PATHNAME) && sc == '/')
1997-06-05 14:59:13 +00:00
/* A slash does not match a wildcard under FNM_PATHNAME. */
1998-04-17 19:52:44 +00:00
return FNM_NOMATCH;
1997-06-05 14:59:13 +00:00
else if (c == '?')
{
1998-04-17 19:52:44 +00:00
if (sc == '\0')
return FNM_NOMATCH;
1997-06-05 14:59:13 +00:00
/* One character of the string is consumed in matching
this ? wildcard, so *??? won't match if there are
fewer than three characters. */
n++;
1998-04-17 19:52:44 +00:00
sc = n < se ? *n : '\0';
1997-06-05 14:59:13 +00:00
}
1998-04-17 19:52:44 +00:00
#ifdef EXTENDED_GLOB
/* Handle ******(patlist) */
if ((flags & FNM_EXTMATCH) && c == '*' && *p == '(') /*)*/
1999-02-19 17:11:39 +00:00
{
char *newn;
/* We need to check whether or not the extended glob
pattern matches the remainder of the string.
If it does, we match the entire pattern. */
for (newn = n; newn < se; ++newn)
{
if (extmatch (c, newn, se, p, pe, flags) == 0)
return (0);
}
/* We didn't match the extended glob pattern, but
that's OK, since we can match 0 or more occurrences.
We need to skip the glob pattern and see if we
match the rest of the string. */
2000-03-17 21:46:59 +00:00
newn = patscan (p + 1, pe, 0);
2001-11-13 17:56:06 +00:00
/* If NEWN is 0, we have an ill-formed pattern. */
p = newn ? newn : pe;
1999-02-19 17:11:39 +00:00
}
1998-04-17 19:52:44 +00:00
#endif
if (p == pe)
2001-04-06 19:14:31 +00:00
break;
1996-12-23 17:02:34 +00:00
}
1996-08-26 18:22:31 +00:00
1998-04-17 19:52:44 +00:00
/* If we've hit the end of the pattern and the last character of
the pattern was handled by the loop above, we've succeeded.
Otherwise, we need to match that last character. */
if (p == pe && (c == '?' || c == '*'))
1996-08-26 18:22:31 +00:00
return (0);
1997-06-05 14:59:13 +00:00
/* General case, use recursion. */
1996-08-26 18:22:31 +00:00
{
1998-07-23 14:37:54 +00:00
unsigned char c1;
1998-04-17 19:52:44 +00:00
1998-07-23 14:37:54 +00:00
c1 = (unsigned char)((flags & FNM_NOESCAPE) == 0 && c == '\\') ? *p : c;
1998-04-17 19:52:44 +00:00
c1 = FOLD (c1);
for (--p; n < se; ++n)
1999-02-19 17:11:39 +00:00
{
2001-11-13 17:56:06 +00:00
/* Only call strmatch if the first character indicates a
1999-02-19 17:11:39 +00:00
possible match. We can check the first character if
we're not doing an extended glob match. */
2001-04-06 19:14:31 +00:00
if ((flags & FNM_EXTMATCH) == 0 && c != '[' && FOLD (*n) != c1) /*]*/
1999-02-19 17:11:39 +00:00
continue;
/* If we're doing an extended glob match and the pattern is not
one of the extended glob patterns, we can check the first
character. */
if ((flags & FNM_EXTMATCH) && p[1] != '(' && /*)*/
2001-04-06 19:14:31 +00:00
strchr ("?*+@!", *p) == 0 && c != '[' && FOLD (*n) != c1) /*]*/
1999-02-19 17:11:39 +00:00
continue;
/* Otherwise, we just recurse. */
if (gmatch (n, se, p, pe, flags & ~FNM_PERIOD) == 0)
return (0);
}
1998-04-17 19:52:44 +00:00
return FNM_NOMATCH;
1996-08-26 18:22:31 +00:00
}
case '[':
{
1998-04-17 19:52:44 +00:00
if (sc == '\0' || n == se)
return FNM_NOMATCH;
1996-08-26 18:22:31 +00:00
1997-06-05 14:59:13 +00:00
/* A character class cannot match a `.' if it is the first
character of the string or if it is the first character
following a slash and we are matching a pathname. */
1998-04-17 19:52:44 +00:00
if ((flags & FNM_PERIOD) && sc == '.' &&
1996-08-26 18:22:31 +00:00
(n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
return (FNM_NOMATCH);
1998-04-17 19:52:44 +00:00
p = brackmatch (p, sc, flags);
if (p == 0)
return FNM_NOMATCH;
1996-08-26 18:22:31 +00:00
}
break;
default:
1998-07-23 14:37:54 +00:00
if ((unsigned char)c != FOLD (sc))
1996-08-26 18:22:31 +00:00
return (FNM_NOMATCH);
}
++n;
}
1998-04-17 19:52:44 +00:00
if (n == se)
1996-08-26 18:22:31 +00:00
return (0);
1998-04-17 19:52:44 +00:00
if ((flags & FNM_LEADING_DIR) && *n == '/')
/* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */
return 0;
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
return (FNM_NOMATCH);
}
/* Parse a bracket expression collating symbol ([.sym.]) starting at P, find
the value of the symbol, and move P past the collating symbol expression.
The value is returned in *VP, if VP is not null. */
static char *
parse_collsym (p, vp)
char *p;
int *vp;
{
register int pc;
int val;
p++; /* move past the `.' */
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
for (pc = 0; p[pc]; pc++)
if (p[pc] == '.' && p[pc+1] == ']')
break;
val = collsym (p, pc);
if (vp)
*vp = val;
return (p + pc + 2);
}
static char *
brackmatch (p, test, flags)
char *p;
unsigned char test;
int flags;
{
register char cstart, cend, c;
register int not; /* Nonzero if the sense of the character class is inverted. */
int pc, brcnt;
char *savep;
test = FOLD (test);
savep = p;
/* POSIX.2 3.13.1 says that an exclamation mark (`!') shall replace the
circumflex (`^') in its role in a `nonmatching list'. A bracket
2001-04-06 19:14:31 +00:00
expression starting with an unquoted circumflex character produces
1998-04-17 19:52:44 +00:00
unspecified results. This implementation treats the two identically. */
if (not = (*p == '!' || *p == '^'))
++p;
c = *p++;
for (;;)
{
/* Initialize cstart and cend in case `-' is the last
character of the pattern. */
cstart = cend = c;
/* POSIX.2 equivalence class: [=c=]. See POSIX.2 2.8.3.2. Find
the end of the equivalence class, move the pattern pointer past
it, and check for equivalence. XXX - this handles only
single-character equivalence classes, which is wrong, or at
least incomplete. */
if (c == '[' && *p == '=' && p[2] == '=' && p[3] == ']')
{
pc = FOLD (p[1]);
p += 4;
if (collequiv (test, pc))
2001-04-06 19:14:31 +00:00
{
/*[*/ /* Move past the closing `]', since the first thing we do at
the `matched:' label is back p up one. */
p++;
goto matched;
}
1998-04-17 19:52:44 +00:00
else
{
c = *p++;
if (c == '\0')
2001-04-06 19:14:31 +00:00
return ((test == '[') ? savep : (char *)0); /*]*/
1998-04-17 19:52:44 +00:00
c = FOLD (c);
continue;
}
2001-04-06 19:14:31 +00:00
}
1998-04-17 19:52:44 +00:00
/* POSIX.2 character class expression. See POSIX.2 2.8.3.2. */
2001-04-06 19:14:31 +00:00
if (c == '[' && *p == ':') /*]*/
1998-04-17 19:52:44 +00:00
{
pc = 0; /* make sure invalid char classes don't match. */
if (STREQN (p+1, "alnum:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISALNUM (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "alpha:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISALPHA (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "blank:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISBLANK (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "cntrl:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISCNTRL (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "digit:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISDIGIT (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "graph:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISGRAPH (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "lower:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISLOWER (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "print:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISPRINT (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "punct:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISPUNCT (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "space:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISSPACE (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "upper:]", 7))
2001-11-13 17:56:06 +00:00
{ pc = ISUPPER (test); p += 8; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "xdigit:]", 8))
2001-11-13 17:56:06 +00:00
{ pc = ISXDIGIT (test); p += 9; }
1998-04-17 19:52:44 +00:00
else if (STREQN (p+1, "ascii:]", 7))
{ pc = isascii (test); p += 8; }
if (pc)
2001-04-06 19:14:31 +00:00
{
/*[*/ /* Move past the closing `]', since the first thing we do at
the `matched:' label is back p up one. */
p++;
1998-04-17 19:52:44 +00:00
goto matched;
2001-04-06 19:14:31 +00:00
}
1998-04-17 19:52:44 +00:00
else
{
/* continue the loop here, since this expression can't be
the first part of a range expression. */
c = *p++;
if (c == '\0')
return ((test == '[') ? savep : (char *)0);
else if (c == ']')
2001-04-06 19:14:31 +00:00
break;
1998-04-17 19:52:44 +00:00
c = FOLD (c);
continue;
}
}
/* POSIX.2 collating symbols. See POSIX.2 2.8.3.2. Find the end of
the symbol name, make sure it is terminated by `.]', translate
the name to a character using the external table, and do the
comparison. */
if (c == '[' && *p == '.')
{
p = parse_collsym (p, &pc);
/* An invalid collating symbol cannot be the first point of a
range. If it is, we set cstart to one greater than `test',
so any comparisons later will fail. */
cstart = (pc == -1) ? test + 1 : pc;
}
if (!(flags & FNM_NOESCAPE) && c == '\\')
{
if (*p == '\0')
return (char *)0;
cstart = cend = *p++;
}
cstart = cend = FOLD (cstart);
/* POSIX.2 2.8.3.1.2 says: `An expression containing a `[' that
is not preceded by a backslash and is not part of a bracket
expression produces undefined results.' This implementation
treats the `[' as just a character to be matched if there is
not a closing `]'. */
if (c == '\0')
return ((test == '[') ? savep : (char *)0);
c = *p++;
c = FOLD (c);
if ((flags & FNM_PATHNAME) && c == '/')
/* [/] can never match when matching a pathname. */
return (char *)0;
/* This introduces a range, unless the `-' is the last
character of the class. Find the end of the range
and move past it. */
if (c == '-' && *p != ']')
{
cend = *p++;
if (!(flags & FNM_NOESCAPE) && cend == '\\')
cend = *p++;
if (cend == '\0')
return (char *)0;
if (cend == '[' && *p == '.')
{
p = parse_collsym (p, &pc);
/* An invalid collating symbol cannot be the second part of a
range expression. If we get one, we set cend to one fewer
than the test character to make sure the range test fails. */
cend = (pc == -1) ? test - 1 : pc;
}
cend = FOLD (cend);
c = *p++;
/* POSIX.2 2.8.3.2: ``The ending range point shall collate
equal to or higher than the starting range point; otherwise
the expression shall be treated as invalid.'' Note that this
applies to only the range expression; the rest of the bracket
expression is still checked for matches. */
if (rangecmp (cstart, cend) > 0)
{
if (c == ']')
2001-04-06 19:14:31 +00:00
break;
1998-04-17 19:52:44 +00:00
c = FOLD (c);
continue;
}
}
if (rangecmp (test, cstart) >= 0 && rangecmp (test, cend) <= 0)
goto matched;
if (c == ']')
break;
}
/* No match. */
return (!not ? (char *)0 : p);
matched:
/* Skip the rest of the [...] that already matched. */
2001-04-06 19:14:31 +00:00
#if 0
1998-04-17 19:52:44 +00:00
brcnt = (c != ']') + (c == '[' && (*p == '=' || *p == ':' || *p == '.'));
2001-04-06 19:14:31 +00:00
#else
c = *--p;
brcnt = 1;
#endif
1998-04-17 19:52:44 +00:00
while (brcnt > 0)
{
/* A `[' without a matching `]' is just another character to match. */
if (c == '\0')
return ((test == '[') ? savep : (char *)0);
c = *p++;
if (c == '[' && (*p == '=' || *p == ':' || *p == '.'))
2001-04-06 19:14:31 +00:00
brcnt++;
1998-04-17 19:52:44 +00:00
else if (c == ']')
2001-04-06 19:14:31 +00:00
brcnt--;
1998-04-17 19:52:44 +00:00
else if (!(flags & FNM_NOESCAPE) && c == '\\')
{
if (*p == '\0')
return (char *)0;
/* XXX 1003.2d11 is unclear if this is right. */
++p;
}
}
return (not ? (char *)0 : p);
}
#if defined (EXTENDED_GLOB)
/* ksh-like extended pattern matching:
[?*+@!](pat-list)
where pat-list is a list of one or patterns separated by `|'. Operation
is as follows:
?(patlist) match zero or one of the given patterns
*(patlist) match zero or more of the given patterns
+(patlist) match one or more of the given patterns
@(patlist) match exactly one of the given patterns
!(patlist) match anything except one of the given patterns
*/
/* Scan a pattern starting at STRING and ending at END, keeping track of
embedded () and []. If DELIM is 0, we scan until a matching `)'
because we're scanning a `patlist'. Otherwise, we scan until we see
DELIM. In all cases, we never scan past END. The return value is the
first character after the matching DELIM. */
static char *
patscan (string, end, delim)
char *string, *end;
int delim;
{
2001-04-06 19:14:31 +00:00
int pnest, bnest, cchar;
char *s, c, *bfirst;
1998-04-17 19:52:44 +00:00
2001-04-06 19:14:31 +00:00
pnest = bnest = cchar = 0;
bfirst = 0;
1998-04-17 19:52:44 +00:00
for (s = string; c = *s; s++)
{
2000-03-17 21:46:59 +00:00
if (s >= end)
2001-04-06 19:14:31 +00:00
return (s);
1998-04-17 19:52:44 +00:00
switch (c)
{
case '\0':
return ((char *)0);
2001-04-06 19:14:31 +00:00
/* `[' is not special inside a bracket expression, but it may
introduce one of the special POSIX bracket expressions
([.SYM.], [=c=], [: ... :]) that needs special handling. */
1998-04-17 19:52:44 +00:00
case '[':
2001-04-06 19:14:31 +00:00
if (bnest == 0)
{
bfirst = s + 1;
if (*bfirst == '!' || *bfirst == '^')
bfirst++;
bnest++;
}
else if (s[1] == ':' || s[1] == '.' || s[1] == '=')
cchar = s[1];
1998-04-17 19:52:44 +00:00
break;
2001-04-06 19:14:31 +00:00
/* `]' is not special if it's the first char (after a leading `!'
or `^') in a bracket expression or if it's part of one of the
special POSIX bracket expressions ([.SYM.], [=c=], [: ... :]) */
1998-04-17 19:52:44 +00:00
case ']':
if (bnest)
2001-04-06 19:14:31 +00:00
{
if (cchar && s[-1] == cchar)
cchar = 0;
else if (s != bfirst)
{
bnest--;
bfirst = 0;
}
}
1998-04-17 19:52:44 +00:00
break;
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
case '(':
if (bnest == 0)
pnest++;
break;
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
case ')':
2000-03-17 21:46:59 +00:00
#if 0
1998-04-17 19:52:44 +00:00
if (bnest == 0)
pnest--;
if (pnest <= 0)
return ++s;
2000-03-17 21:46:59 +00:00
#else
if (bnest == 0 && pnest-- <= 0)
return ++s;
#endif
1998-04-17 19:52:44 +00:00
break;
2001-04-06 19:14:31 +00:00
1998-04-17 19:52:44 +00:00
case '|':
if (bnest == 0 && pnest == 0 && delim == '|')
return ++s;
break;
}
}
2000-03-17 21:46:59 +00:00
1998-04-17 19:52:44 +00:00
return (char *)0;
}
/* Return 0 if dequoted pattern matches S in the current locale. */
static int
strcompare (p, pe, s, se)
char *p, *pe, *s, *se;
{
int ret;
char c1, c2;
c1 = *pe;
c2 = *se;
*pe = *se = '\0';
#if defined (HAVE_STRCOLL)
ret = strcoll (p, s);
#else
ret = strcmp (p, s);
#endif
*pe = c1;
*se = c2;
return (ret == 0 ? ret : FNM_NOMATCH);
}
/* Match a ksh extended pattern specifier. Return FNM_NOMATCH on failure or
0 on success. This is handed the entire rest of the pattern and string
the first time an extended pattern specifier is encountered, so it calls
gmatch recursively. */
static int
extmatch (xc, s, se, p, pe, flags)
int xc; /* select which operation */
char *s, *se;
char *p, *pe;
int flags;
{
char *prest; /* pointer to rest of pattern */
char *psub; /* pointer to sub-pattern */
char *pnext; /* pointer to next sub-pattern */
char *srest; /* pointer to rest of string */
int m1, m2;
2001-04-06 19:14:31 +00:00
#if DEBUG_MATCHING
2000-03-17 21:46:59 +00:00
fprintf(stderr, "extmatch: xc = %c\n", xc);
fprintf(stderr, "extmatch: s = %s; se = %s\n", s, se);
fprintf(stderr, "extmatch: p = %s; pe = %s\n", p, pe);
#endif
prest = patscan (p + (*p == '('), pe, 0); /* ) */
if (prest == 0)
/* If PREST is 0, we failed to scan a valid pattern. In this
case, we just want to compare the two as strings. */
return (strcompare (p - 1, pe, s, se));
1998-04-17 19:52:44 +00:00
switch (xc)
{
case '+': /* match one or more occurrences */
case '*': /* match zero or more occurrences */
/* If we can get away with no matches, don't even bother. Just
call gmatch on the rest of the pattern and return success if
it succeeds. */
if (xc == '*' && (gmatch (s, se, prest, pe, flags) == 0))
return 0;
/* OK, we have to do this the hard way. First, we make sure one of
2001-04-06 19:14:31 +00:00
the subpatterns matches, then we try to match the rest of the
string. */
1998-04-17 19:52:44 +00:00
for (psub = p + 1; ; psub = pnext)
{
pnext = patscan (psub, pe, '|');
for (srest = s; srest <= se; srest++)
{
/* Match this substring (S -> SREST) against this
subpattern (psub -> pnext - 1) */
m1 = gmatch (s, srest, psub, pnext - 1, flags) == 0;
/* OK, we matched a subpattern, so make sure the rest of the
string matches the rest of the pattern. Also handle
multiple matches of the pattern. */
if (m1)
m2 = (gmatch (srest, se, prest, pe, flags) == 0) ||
(s != srest && gmatch (srest, se, p - 1, pe, flags) == 0);
if (m1 && m2)
2001-04-06 19:14:31 +00:00
return (0);
1998-04-17 19:52:44 +00:00
}
if (pnext == prest)
break;
}
return (FNM_NOMATCH);
case '?': /* match zero or one of the patterns */
case '@': /* match exactly one of the patterns */
/* If we can get away with no matches, don't even bother. Just
call gmatch on the rest of the pattern and return success if
it succeeds. */
if (xc == '?' && (gmatch (s, se, prest, pe, flags) == 0))
return 0;
/* OK, we have to do this the hard way. First, we see if one of
the subpatterns matches, then, if it does, we try to match the
rest of the string. */
for (psub = p + 1; ; psub = pnext)
{
pnext = patscan (psub, pe, '|');
srest = (prest == pe) ? se : s;
for ( ; srest <= se; srest++)
{
if (gmatch (s, srest, psub, pnext - 1, flags) == 0 &&
gmatch (srest, se, prest, pe, flags) == 0)
return (0);
}
if (pnext == prest)
break;
}
return (FNM_NOMATCH);
case '!': /* match anything *except* one of the patterns */
for (srest = s; srest <= se; srest++)
{
m1 = 0;
for (psub = p + 1; ; psub = pnext)
{
pnext = patscan (psub, pe, '|');
/* If one of the patterns matches, just bail immediately. */
if (m1 = (gmatch (s, srest, psub, pnext - 1, flags) == 0))
break;
if (pnext == prest)
break;
}
if (m1 == 0 && gmatch (srest, se, prest, pe, flags) == 0)
2001-04-06 19:14:31 +00:00
return (0);
1998-04-17 19:52:44 +00:00
}
return (FNM_NOMATCH);
}
1996-08-26 18:22:31 +00:00
return (FNM_NOMATCH);
}
1998-04-17 19:52:44 +00:00
#endif /* EXTENDED_GLOB */
#ifdef TEST
main (c, v)
int c;
char **v;
{
char *string, *pat;
string = v[1];
pat = v[2];
2001-11-13 17:56:06 +00:00
if (strmatch (pat, string, 0) == 0)
1998-04-17 19:52:44 +00:00
{
printf ("%s matches %s\n", string, pat);
exit (0);
}
else
{
printf ("%s does not match %s\n", string, pat);
exit (1);
}
}
#endif