Remove support for the ? wildcard

Fixes #4520
This commit is contained in:
ridiculousfish 2018-03-31 16:48:57 -07:00
parent 4b079e16e5
commit 6e56637cf0
15 changed files with 74 additions and 117 deletions

View file

@ -51,6 +51,7 @@ This section is for changes merged to the `major` branch that are not also merge
- The machine hostname, where available, is now exposed as `$hostname` which is now a reserved variable. This drops the dependency on the `hostname` executable (#4422).
- `functions --handlers` can be used to show event handlers (#4694).
- Variables set in `if` and `while` conditions are available outside the block (#4820).
- The `?` wildcard has been removed (#4520).
## Other significant changes
- Command substitution output is now limited to 10 MB by default (#3822).

View file

@ -92,7 +92,6 @@ Some characters can not be written directly on the command line. For these chara
- '<code>\\$</code>' escapes the dollar character
- '<code>\\\\</code>' escapes the backslash character
- '<code>\\*</code>' escapes the star character
- '<code>\\?</code>' escapes the question mark character
- '<code>\\~</code>' escapes the tilde character
- '<code>\\#</code>' escapes the hash character
- '<code>\\(</code>' escapes the left parenthesis character
@ -330,7 +329,7 @@ These are the general purpose tab completions that `fish` provides:
- Completion of usernames for tilde expansion.
- Completion of filenames, even on strings with wildcards such as '`*`', '`**`' and '`?`'.
- Completion of filenames, even on strings with wildcards such as '`*`' and '`**`'.
`fish` provides a large number of program specific completions. Most of these completions are simple options like the `-l` option for `ls`, but some are more advanced. The latter include:
@ -418,9 +417,7 @@ When an argument for a program is given on the commandline, it undergoes the pro
\subsection expand-wildcard Wildcards
If a star (`*`) or a question mark (`?`) is present in the parameter, `fish` attempts to match the given parameter to any files in such a way that:
- `?` can match any single character except '/'.
If a star (`*`) is present in the parameter, `fish` attempts to match the given parameter to any files in such a way that:
- `*` can match any string of characters not containing '/'. This includes matching an empty string.
@ -446,8 +443,6 @@ Examples:
- `a*` matches any files beginning with an 'a' in the current directory.
- `???` matches any file in the current directory whose name is exactly three characters long.
- `**` matches any files and directories in the current directory and all of its subdirectories.
Note that for most commands, if any wildcard fails to expand, the command is not executed, <a href='#variables-status'>`$status`</a> is set to nonzero, and a warning is printed. This behavior is consistent with setting `shopt -s failglob` in bash. There are exactly 3 exceptions, namely <a href="commands.html#set">`set`</a>, <a href="commands.html#count">`count`</a> and <a href="commands.html#for">`for`</a>. Their globs are permitted to expand to zero arguments, as with `shopt -s nullglob` in bash.

View file

@ -6,25 +6,18 @@ function __fish_git_commits
# This allows filtering by subject with the new pager!
# Because even subject lines can be quite long,
# trim them (abbrev'd hash+tab+subject) to 73 characters
command git log --pretty=tformat:"%h"\t"%s" --all --max-count=1000 ^/dev/null \
| string replace -r '(.{73}).+' '$1…'
command git log --pretty=tformat:"%h"\t"%s" --all --max-count=1000 ^/dev/null | string replace -r '(.{73}).+' '$1…'
end
function __fish_git_recent_commits
# Like __fish_git_commits, but not on all branches and limited to
# the last 50 commits. Used for fixup, where only the current branch
# and the latest commits make sense.
command git log --pretty=tformat:"%h"\t"%s" --max-count=50 ^/dev/null \
| string replace -r '(.{73}).+' '$1…'
command git log --pretty=tformat:"%h"\t"%s" --max-count=50 ^/dev/null | string replace -r '(.{73}).+' '$1…'
end
function __fish_git_branches
command git branch --no-color -a $argv ^/dev/null \
# Filter out detached heads and such ("(HEAD detached at SOMESHA)", localized).
| string match -v '\* (*)' | string match -r -v ' -> ' | string trim -c "* " \
# We assume anything that's not remote is a local branch.
| string replace -r '^(?!remotes/)(.*)' '$1\tLocal Branch' \
| string replace -r "^remotes/(.*)" '$1\tRemote Branch'
command git branch --no-color -a $argv ^/dev/null # Filter out detached heads and such ("(HEAD detached at SOMESHA)", localized). | string match -v '\* (*)' | string match -r -v ' -> ' | string trim -c "* " # We assume anything that's not remote is a local branch. | string replace -r '^(?!remotes/)(.*)' '$1\tLocal Branch' | string replace -r "^remotes/(.*)" '$1\tRemote Branch'
end
function __fish_git_tags
@ -96,8 +89,7 @@ function __fish_git_files
# E.g. `git reset $submodule` won't do anything (not even print an error).
# --ignore-submodules=all was added in git 1.7.2, released July 2010.
set -l use_next
command git status --porcelain -z --ignore-submodules=all \
| while read -lz -d '' line
command git status --porcelain -z --ignore-submodules=all | while read -lz -d '' line
# The entire line is the "from" from a rename.
if set -q use_next[1]
if contains -- $use_next $argv
@ -144,28 +136,31 @@ function __fish_git_files
case 'A ' AM AD
# Additions are only shown here if they are staged.
# Otherwise it's an untracked file.
contains -- added $argv; or contains -- all-staged $argv
contains -- added $argv
or contains -- all-staged $argv
and printf '%s\t%s\n' "$file" $added_desc
case '?M'
case '*M'
# Modified
contains -- modified $argv
and printf '%s\t%s\n' "$file" $modified_desc
case 'M?'
case 'M*'
# If the character is first ("M "), then that means it's "our" change,
# which means it is staged.
# This is useless for many commands - e.g. `checkout` won't do anything with this.
# So it needs to be requested explicitly.
contains -- modified-staged $argv; or contains -- all-staged $argv
contains -- modified-staged $argv
or contains -- all-staged $argv
and printf '%s\t%s\n' "$file" $staged_modified_desc
case '?D'
case '*D'
contains -- deleted $argv
and printf '%s\t%s\n' "$file" $deleted_desc
case 'D?'
case 'D*'
# TODO: The docs are unclear on this.
# There is both X unmodified and Y either M or D ("not updated")
# and Y is D and X is unmodified or [MARC] ("deleted in work tree").
# For our purposes, we assume this is a staged deletion.
contains -- deleted-staged $argv; or contains -- all-staged $argv
contains -- deleted-staged $argv
or contains -- all-staged $argv
and printf '%s\t%s\n' "$file" $staged_deleted_desc
case '\?\?'
# Untracked
@ -201,7 +196,8 @@ end
function __fish_git_needs_command
set cmd (commandline -opc)
set -l skip_next 1
set -q cmd[2]; or return 0
set -q cmd[2]
or return 0
# Skip first word because it's "git" or a wrapper
for c in $cmd[2..-1]
test $skip_next -eq 0
@ -555,7 +551,7 @@ complete -f -c git -n '__fish_git_needs_command' -a init -d 'Create an empty git
# TODO options
### log
complete -c git -n '__fish_git_needs_command' -a shortlog -d 'Show commit shortlog'
complete -c git -n '__fish_git_needs_command' -a shortlog -d 'Show commit shortlog'
complete -c git -n '__fish_git_needs_command' -a log -d 'Show commit logs'
complete -c git -n '__fish_git_using_command log; and not contains -- -- (commandline -op)' -a '(__fish_git_refs) (__fish_git_ranges)'
@ -721,8 +717,8 @@ complete -c git -n '__fish_git_using_command log' -l irreversible-delete -s D
complete -f -c git -n '__fish_git_using_command log' -s l
function __fish__git_append_letters_nosep
set -l token (commandline -tc)
printf "%s\n" $token$argv
set -l token (commandline -tc)
printf "%s\n" $token$argv
end
complete -x -c git -n '__fish_git_using_command log' -l diff-filter -a '(__fish__git_append_letters_nosep a\tExclude\ added c\tExclude\ copied d\tExclude\ deleted m\tExclude\ modified r\tExclude\ renamed t\tExclude\ type\ changed u\tExclude\ unmerged x\tExclude\ unknown b\tExclude\ broken A\tAdded C\tCopied D\tDeleted M\tModified R\tRenamed T\tType\ Changed U\tUnmerged X\tUnknown B\tBroken)'

View file

@ -995,12 +995,6 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
out += *in;
break;
}
case ANY_CHAR: {
// Experimental fix for #1614. The hope is that any time these appear in a
// string, they came from wildcard expansion.
out += L'?';
break;
}
case ANY_STRING: {
out += L'*';
break;
@ -1022,7 +1016,6 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
case L']':
case L'{':
case L'}':
case L'?':
case L'*':
case L'|':
case L';':
@ -1356,12 +1349,6 @@ static bool unescape_string_internal(const wchar_t *const input, const size_t in
}
break;
}
case L'?': {
if (unescape_special) {
to_append_or_none = ANY_CHAR;
}
break;
}
case L'$': {
if (unescape_special) {
to_append_or_none = VARIABLE_EXPAND;

View file

@ -823,15 +823,11 @@ static void remove_internal_separator(wcstring *str, bool conv) {
// Remove all instances of INTERNAL_SEPARATOR.
str->erase(std::remove(str->begin(), str->end(), (wchar_t)INTERNAL_SEPARATOR), str->end());
// If conv is true, replace all instances of ANY_CHAR with '?', ANY_STRING with '*',
// If conv is true, replace all instances of ANY_STRING with '*',
// ANY_STRING_RECURSIVE with '*'.
if (conv) {
for (size_t idx = 0; idx < str->size(); idx++) {
switch (str->at(idx)) {
case ANY_CHAR: {
str->at(idx) = L'?';
break;
}
case ANY_STRING:
case ANY_STRING_RECURSIVE: {
str->at(idx) = L'*';
@ -918,7 +914,7 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<
wcstring path_to_expand = input;
remove_internal_separator(&path_to_expand, flags & EXPAND_SKIP_WILDCARDS);
const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_CHAR */);
const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_STRING */);
if (has_wildcard && (flags & EXECUTABLES_ONLY)) {
; // don't do wildcard expansion for executables, see issue #785

View file

@ -4120,15 +4120,16 @@ static void test_string() {
{{L"string", L"match", 0}, STATUS_INVALID_ARGS, L""},
{{L"string", L"match", L"", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"", L"", 0}, STATUS_CMD_OK, L"\n"},
{{L"string", L"match", L"?", L"a", 0}, STATUS_CMD_OK, L"a\n"},
{{L"string", L"match", L"?", L"a", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"*", L"", 0}, STATUS_CMD_OK, L"\n"},
{{L"string", L"match", L"**", L"", 0}, STATUS_CMD_OK, L"\n"},
{{L"string", L"match", L"*", L"xyzzy", 0}, STATUS_CMD_OK, L"xyzzy\n"},
{{L"string", L"match", L"**", L"plugh", 0}, STATUS_CMD_OK, L"plugh\n"},
{{L"string", L"match", L"a*b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
{{L"string", L"match", L"a??b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
{{L"string", L"match", L"-i", L"a??B", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
{{L"string", L"match", L"-i", L"a??b", L"Axxb", 0}, STATUS_CMD_OK, L"Axxb\n"},
{{L"string", L"match", L"a??b", L"axxb", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"a??b", L"a??b", 0}, STATUS_CMD_OK, L"a??b\n"},
{{L"string", L"match", L"-i", L"a??B", L"axxb", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"-i", L"a??b", L"Axxb", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"a*", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
{{L"string", L"match", L"*a", L"xxa", 0}, STATUS_CMD_OK, L"xxa\n"},
{{L"string", L"match", L"*a*", L"axa", 0}, STATUS_CMD_OK, L"axa\n"},
@ -4137,14 +4138,14 @@ static void test_string() {
{{L"string", L"match", L"*a", L"a", 0}, STATUS_CMD_OK, L"a\n"},
{{L"string", L"match", L"a*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
{{L"string", L"match", L"a*b*c", L"axxbyyc", 0}, STATUS_CMD_OK, L"axxbyyc\n"},
{{L"string", L"match", L"a*b?c", L"axxbyc", 0}, STATUS_CMD_OK, L"axxbyc\n"},
{{L"string", L"match", L"*?", L"a", 0}, STATUS_CMD_OK, L"a\n"},
{{L"string", L"match", L"*?", L"ab", 0}, STATUS_CMD_OK, L"ab\n"},
{{L"string", L"match", L"?*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
{{L"string", L"match", L"?*", L"ab", 0}, STATUS_CMD_OK, L"ab\n"},
{{L"string", L"match", L"a*b?c", L"axxb?c", 0}, STATUS_CMD_OK, L"axxb?c\n"},
{{L"string", L"match", L"*?", L"a", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"*?", L"ab", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"?*", L"a", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"?*", L"ab", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"\\*", L"*", 0}, STATUS_CMD_OK, L"*\n"},
{{L"string", L"match", L"a*\\", L"abc\\", 0}, STATUS_CMD_OK, L"abc\\\n"},
{{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_OK, L"abc?\n"},
{{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"?", L"", 0}, STATUS_CMD_ERROR, L""},
{{L"string", L"match", L"?", L"ab", 0}, STATUS_CMD_ERROR, L""},
@ -4428,24 +4429,27 @@ static void test_illegal_command_exit_code() {
};
const command_result_tuple_t tests[] = {
{L"echo -n", STATUS_CMD_OK}, {L"pwd", STATUS_CMD_OK},
// a `)` without a matching `(` is now a tokenizer error, and cannot be executed even as an illegal command
{L"echo -n", STATUS_CMD_OK},
{L"pwd", STATUS_CMD_OK},
// a `)` without a matching `(` is now a tokenizer error, and cannot be executed even as an
// illegal command
// {L")", STATUS_ILLEGAL_CMD}, {L") ", STATUS_ILLEGAL_CMD}, {L") ", STATUS_ILLEGAL_CMD}
{L"*", STATUS_ILLEGAL_CMD}, {L"**", STATUS_ILLEGAL_CMD},
{L"?", STATUS_ILLEGAL_CMD}, {L"abc?def", STATUS_ILLEGAL_CMD},
{L"*", STATUS_ILLEGAL_CMD},
{L"**", STATUS_ILLEGAL_CMD},
{L"?", STATUS_CMD_UNKNOWN},
{L"abc?def", STATUS_CMD_UNKNOWN},
};
int res = 0;
const io_chain_t empty_ios;
parser_t &parser = parser_t::principal_parser();
size_t i = 0;
for (i = 0; i < sizeof tests / sizeof *tests; i++) {
res = parser.eval(tests[i].txt, empty_ios, TOP);
for (const auto &test : tests) {
res = parser.eval(test.txt, empty_ios, TOP);
int exit_status = res ? STATUS_CMD_UNKNOWN : proc_get_last_status();
if (exit_status != tests[i].result) {
err(L"command '%ls': expected exit code %d , got %d", tests[i].txt, tests[i].result,
if (exit_status != test.result) {
err(L"command '%ls': expected exit code %d , got %d", test.txt, test.result,
exit_status);
}
}

View file

@ -125,7 +125,6 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l
case BRACE_BEGIN:
case BRACE_END:
case BRACE_SEP:
case ANY_CHAR:
case ANY_STRING:
case ANY_STRING_RECURSIVE: {
has_magic = 1;
@ -549,7 +548,6 @@ static void color_argument_internal(const wcstring &buffstr,
break;
}
case L'*':
case L'?':
case L'(':
case L')': {
colors[in_pos] = highlight_spec_operator;

View file

@ -423,9 +423,7 @@ wcstring parse_util_unescape_wildcards(const wcstring &str) {
for (size_t i = 0; cs[i] != L'\0'; i++) {
if (cs[i] == L'*') {
result.push_back(ANY_STRING);
} else if (cs[i] == L'?') {
result.push_back(ANY_CHAR);
} else if (cs[i] == L'\\' && (cs[i + 1] == L'*' || cs[i + 1] == L'?')) {
} else if (cs[i] == L'\\' && cs[i + 1] == L'*') {
result.push_back(cs[i + 1]);
i += 1;
} else if (cs[i] == L'\\' && cs[i + 1] == L'\\') {
@ -892,9 +890,7 @@ void parse_util_expand_variable_error(const wcstring &token, size_t global_token
default: {
wchar_t token_stop_char = char_after_dollar;
// Unescape (see issue #50).
if (token_stop_char == ANY_CHAR)
token_stop_char = L'?';
else if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE)
if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE)
token_stop_char = L'*';
// Determine which error message to use. The format string may not consume all the

View file

@ -51,7 +51,7 @@
/// Finds an internal (ANY_STRING, etc.) style wildcard, or wcstring::npos.
static size_t wildcard_find(const wchar_t *wc) {
for (size_t i = 0; wc[i] != L'\0'; i++) {
if (wc[i] == ANY_CHAR || wc[i] == ANY_STRING || wc[i] == ANY_STRING_RECURSIVE) {
if (wc[i] == ANY_STRING || wc[i] == ANY_STRING_RECURSIVE) {
return i;
}
}
@ -64,13 +64,12 @@ static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal) {
const wchar_t *end = str + len;
if (internal) {
for (; str < end; str++) {
if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE))
return true;
if (*str == ANY_STRING || *str == ANY_STRING_RECURSIVE) return true;
}
} else {
wchar_t prev = 0;
for (; str < end; str++) {
if (((*str == L'*') || (*str == L'?')) && (prev != L'\\')) return true;
if (*str == L'*' && prev != L'\\') return true;
prev = *str;
}
}
@ -128,13 +127,6 @@ static enum fuzzy_match_type_t wildcard_match_internal(const wchar_t *str, const
restart_is_out_of_str = (*str_x == 0);
wc_x++;
continue;
} else if (*wc_x == ANY_CHAR && *str_x != 0) {
if (is_first && *str_x == L'.') {
return fuzzy_match_none;
}
wc_x++;
str_x++;
continue;
} else if (*str_x != 0 && *str_x == *wc_x) { // ordinary character
wc_x++;
str_x++;
@ -212,7 +204,7 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc,
return false;
}
// Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING.
// Locate the next wildcard character position, e.g. ANY_STRING.
const size_t next_wc_char_pos = wildcard_find(wc);
// Maybe we have no more wildcards at all. This includes the empty string.
@ -265,12 +257,6 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc,
// Our first character is a wildcard.
assert(next_wc_char_pos == 0);
switch (wc[0]) {
case ANY_CHAR: {
if (str[0] == L'\0') {
return false;
}
return wildcard_complete_internal(str + 1, wc + 1, params, flags, out);
}
case ANY_STRING: {
// Hackish. If this is the last character of the wildcard, then just complete with
// the empty string. This fixes cases like "f*<tab>" -> "f*o".
@ -789,7 +775,7 @@ void wildcard_expander_t::expand_last_segment(const wcstring &base_dir, DIR *bas
///
/// Args:
/// base_dir: the "working directory" against which the wildcard is to be resolved
/// wc: the wildcard string itself, e.g. foo*bar/baz (where * is acutally ANY_CHAR)
/// wc: the wildcard string itself, e.g. foo*bar/baz (where * is acutally ANY_STRING)
/// prefix: the string that should be prepended for completions that replace their token.
// This is usually the same thing as the original wildcard, but for fuzzy matching, we
// expand intermediate segments. effective_prefix is always either empty, or ends with a slash
@ -810,7 +796,7 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc,
const size_t wc_segment_len = next_slash ? next_slash - wc : wc_len;
const wcstring wc_segment = wcstring(wc, wc_segment_len);
const bool segment_has_wildcards =
wildcard_has(wc_segment, true /* internal, i.e. look for ANY_CHAR instead of ? */);
wildcard_has(wc_segment, true /* internal, i.e. look for ANY_STRING instead of * */);
const wchar_t *const wc_remainder = next_slash ? next_slash + 1 : NULL;
if (wc_segment.empty()) {

View file

@ -11,10 +11,8 @@
// Enumeration of all wildcard types.
enum {
/// Character representing any character except '/' (slash).
ANY_CHAR = WILDCARD_RESERVED_BASE,
/// Character representing any character string not containing '/' (slash).
ANY_STRING,
ANY_STRING = WILDCARD_RESERVED_BASE,
/// Character representing any character string.
ANY_STRING_RECURSIVE,
/// This is a special psuedo-char that is not used other than to mark the

View file

@ -117,16 +117,16 @@
# string unescape --style=var (string escape --style=var -- -)
####################
# string match "?" a
# string match "*" a
####################
# string match "a*b" axxb
####################
# string match -i "a??B" Axxb
# string match -i "a**B" Axxb
####################
# echo "ok?" | string match "*\?"
# echo "ok?" | string match "*?"
####################
# string match -r "cat|dog|fish" "nice dog"
@ -190,7 +190,7 @@ string invalidarg; and echo "unexpected exit 0"
# string match -r -v "[dcantg].*" dog can cat diz
####################
# string match -v "???" dog can cat diz
# string match -v "*" dog can cat diz
####################
# string match -rvn a bbb

View file

@ -124,17 +124,17 @@ string unescape --style=var -- (string escape --style=var -- -)
# The following tests verify that we can correctly match strings.
logmsg 'string match "?" a'
string match "?" a
logmsg 'string match "*" a'
string match "*" a
logmsg 'string match "a*b" axxb'
string match "a*b" axxb
logmsg 'string match -i "a??B" Axxb'
string match -i "a??B" Axxb
logmsg 'string match -i "a**B" Axxb'
string match -i "a**B" Axxb
logmsg 'echo "ok?" | string match "*\?"'
echo "ok?" | string match "*\?"
logmsg 'echo "ok?" | string match "*?"'
echo "ok?" | string match "*?"
logmsg 'string match -r "cat|dog|fish" "nice dog"'
string match -r "cat|dog|fish" "nice dog"
@ -199,8 +199,8 @@ string length; or echo "missing argument returns 1"
logmsg 'string match -r -v "[dcantg].*" dog can cat diz'
string match -r -v "[dcantg].*" dog can cat diz; or echo "no regexp invert match"
logmsg 'string match -v "???" dog can cat diz'
string match -v "???" dog can cat diz; or echo "no glob invert match"
logmsg 'string match -v "*" dog can cat diz'
string match -v "*" dog can cat diz; or echo "no glob invert match"
logmsg 'string match -rvn a bbb'
string match -rvn a bbb; or echo "exit 1"

View file

@ -170,7 +170,7 @@ _a_b_c_
-
####################
# string match "?" a
# string match "*" a
a
####################
@ -178,11 +178,11 @@ a
axxb
####################
# string match -i "a??B" Axxb
# string match -i "a**B" Axxb
Axxb
####################
# echo "ok?" | string match "*\?"
# echo "ok?" | string match "*?"
ok?
####################
@ -272,7 +272,7 @@ missing argument returns 1
no regexp invert match
####################
# string match -v "???" dog can cat diz
# string match -v "*" dog can cat diz
no glob invert match
####################

View file

@ -65,7 +65,7 @@ for i in Test for continue break and switch builtins problems;
switch $i
case Test
printf "%s " $i
case "f??"
case "for"
printf "%s " 3b
case "c*"
echo pass

View file

@ -20,7 +20,7 @@ end
switch $smurf
case cyan magenta yellow
echo Test 3 fail
case "?????"
case "*"
echo Test 3 pass
end