From 186129835ea0b42bb894366aeb27a251ae0787e5 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Tue, 4 Jan 2022 10:11:48 -0500 Subject: [PATCH] new shell option to force globbing never to return `.' and `..' as matches --- CWRU/CWRU.chlog | 26 ++++++++++++++++++++ MANIFEST | 2 ++ arrayfunc.c | 9 +++++-- builtins/shopt.def | 2 ++ doc/bash.1 | 24 ++++++++++++++++--- doc/bashref.texi | 10 ++++++++ doc/version.texi | 4 ++-- execute_cmd.c | 2 +- lib/glob/glob.c | 2 +- subst.c | 8 ++++++- tests/assoc.right | 22 ++++++++++++----- tests/assoc.tests | 3 +++ tests/assoc11.sub | 2 +- tests/assoc14.sub | 14 +++++++++++ tests/assoc17.sub | 58 +++++++++++++++++++++++++++++++++++++++++++++ tests/extglob.right | 28 +++++++++++----------- tests/extglob7.sub | 1 + tests/glob.right | 6 ++++- tests/glob.tests | 1 + tests/glob10.sub | 32 +++++++++++++++++++++++++ tests/shopt.right | 6 +++++ 21 files changed, 230 insertions(+), 32 deletions(-) create mode 100644 tests/assoc17.sub create mode 100644 tests/glob10.sub diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index fe24b6417..158376f5d 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -2788,4 +2788,30 @@ builtins/common.c - INTERNAL_DEBUG: use instead of calls to itrace protected by #ifdef DEBUG + 12/26 + ----- +lib/glob/glob.c + - glob_always_skip_dot_and_dotdot: initialize to 1 (enabled) + +builtins/shopt.def + - globskipdots: new shell option, exposes glob_always_skip_dot_and_dotdot +doc/{bash.1,bashref.texi} + - globskipdots: document new shell option + +execute_cmd.c + - fix_arrayref_words: call valid_array_reference with 0 for third arg + because the words have not undergone any word expansions yet and + the quotes are still present. This makes things like + A=[\[]=set + unset A[\[] + work + +subst.c + - word_list_split: if a word undergoes word splitting but is not + changed, preserve any W_ARRAYREF flag into the new word. This makes + things like + rkey=']' + unset A[$rkey] + work because the unset builtin sees the W_ARRAYREF flag on its + argument diff --git a/MANIFEST b/MANIFEST index 211170717..1e326eefd 100644 --- a/MANIFEST +++ b/MANIFEST @@ -954,6 +954,7 @@ tests/assoc13.sub f tests/assoc14.sub f tests/assoc15.sub f tests/assoc16.sub f +tests/assoc17.sub f tests/attr.tests f tests/attr.right f tests/attr1.sub f @@ -1144,6 +1145,7 @@ tests/glob6.sub f tests/glob7.sub f tests/glob8.sub f tests/glob9.sub f +tests/glob10.sub f tests/glob.right f tests/globstar.tests f tests/globstar.right f diff --git a/arrayfunc.c b/arrayfunc.c index 11b03ca96..1f9b84fdf 100644 --- a/arrayfunc.c +++ b/arrayfunc.c @@ -1255,7 +1255,7 @@ tokenize_array_reference (name, flags, subp) char **subp; { char *t; - int r, len, isassoc; + int r, len, isassoc, ssflags; SHELL_VAR *entry; t = mbschr (name, '['); /* ] */ @@ -1270,10 +1270,15 @@ tokenize_array_reference (name, flags, subp) if (r == 0) return 0; + ssflags = 0; if (isassoc && ((flags & (VA_NOEXPAND|VA_ONEWORD)) == (VA_NOEXPAND|VA_ONEWORD))) len = strlen (t) - 1; else if (isassoc) - len = skipsubscript (t, 0, flags&VA_NOEXPAND); /* VA_NOEXPAND must be 1 */ + { + if (flags & VA_NOEXPAND) + ssflags |= 1; + len = skipsubscript (t, 0, ssflags); + } else /* Check for a properly-terminated non-null subscript. */ len = skipsubscript (t, 0, 0); /* arithmetic expression */ diff --git a/builtins/shopt.def b/builtins/shopt.def index 1c7826011..52204c7bf 100644 --- a/builtins/shopt.def +++ b/builtins/shopt.def @@ -89,6 +89,7 @@ extern int check_jobs_at_exit; extern int autocd; extern int glob_star; extern int glob_asciirange; +extern int glob_always_skip_dot_and_dotdot; extern int lastpipe_opt; extern int inherit_errexit; extern int localvar_inherit; @@ -210,6 +211,7 @@ static struct { { "force_fignore", &force_fignore, (shopt_set_func_t *)NULL }, #endif { "globasciiranges", &glob_asciirange, (shopt_set_func_t *)NULL }, + { "globskipdots", &glob_always_skip_dot_and_dotdot, (shopt_set_func_t *)NULL }, { "globstar", &glob_star, (shopt_set_func_t *)NULL }, { "gnu_errfmt", &gnu_error_format, (shopt_set_func_t *)NULL }, #if defined (HISTORY) diff --git a/doc/bash.1 b/doc/bash.1 index 34a01f468..d35468da6 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -5,12 +5,12 @@ .\" Case Western Reserve University .\" chet.ramey@case.edu .\" -.\" Last Change: Mon Nov 22 09:58:49 EST 2021 +.\" Last Change: Sun Dec 26 16:02:07 EST 2021 .\" .\" bash_builtins, strip all but Built-Ins section .if \n(zZ=1 .ig zZ .if \n(zY=1 .ig zY -.TH BASH 1 "2021 November 22" "GNU Bash 5.2" +.TH BASH 1 "2021 December 26" "GNU Bash 5.2" .\" .\" There's some problem with having a `@' .\" in a tagged paragraph with the BSD man macros. @@ -714,7 +714,7 @@ Expressions are composed of the primaries described below under .SM .BR "CONDITIONAL EXPRESSIONS" . The words between the \fB[[\fP and \fB]]\fP do not undergo word splitting -and filename expansion. +and pathname expansion. The shell performs tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal on those words @@ -3742,6 +3742,14 @@ the pattern must begin with ``.'' (for example, ``.?''), even if .B dotglob is set. +If the +.B globskipdots +shell option is enabled, the filenames +.B ``.'' +and +.BR ``..'' +are never matched, even if the pattern begins with a +.BR ``.'' . When not matching pathnames, the .B ``.'' character is not treated specially. @@ -3759,6 +3767,7 @@ below under for a description of the .BR nocaseglob , .BR nullglob , +.BR globskipdots , .BR failglob , and .B dotglob @@ -10361,6 +10370,15 @@ and .BR B , and upper-case and lower-case ASCII characters will collate together. .TP 8 +.B globskipdots +If set, pathname expansion will never match the filenames +.B ``.'' +and +.BR ``..'' , +even if the pattern begins with a +.BR ``.'' . +This option is enabled by default. +.TP 8 .B globstar If set, the pattern \fB**\fP used in a pathname expansion context will match all files and zero or more directories and subdirectories. diff --git a/doc/bashref.texi b/doc/bashref.texi index df64c3027..fdfc50811 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -2757,6 +2757,9 @@ must be matched explicitly, unless the shell option @code{dotglob} is set. In order to match the filenames @samp{.} and @samp{..}, the pattern must begin with @samp{.} (for example, @samp{.?}), even if @code{dotglob} is set. +If the @code{globskipdots} shell option is enabled, the filenames +@samp{.} and @samp{..} are never matched, even if the pattern begins +with a @samp{.}. When not matching filenames, the @samp{.} character is not treated specially. When matching a filename, the slash character must always be @@ -2766,6 +2769,7 @@ below (@pxref{Pattern Matching}). See the description of @code{shopt} in @ref{The Shopt Builtin}, for a description of the @code{nocaseglob}, @code{nullglob}, +@code{globskipdots}, @code{failglob}, and @code{dotglob} options. The @env{GLOBIGNORE} @@ -5692,6 +5696,12 @@ is not taken into account, so @samp{b} will not collate between @samp{A} and @samp{B}, and upper-case and lower-case ASCII characters will collate together. +@item globskipdots +If set, filename expansion will never match the filenames +@samp{.} and @samp{..}, +even if the pattern begins with a @samp{.}. +This option is enabled by default. + @item globstar If set, the pattern @samp{**} used in a filename expansion context will match all files and zero or more directories and subdirectories. diff --git a/doc/version.texi b/doc/version.texi index 248ea17d0..7fca4730a 100644 --- a/doc/version.texi +++ b/doc/version.texi @@ -2,10 +2,10 @@ Copyright (C) 1988-2021 Free Software Foundation, Inc. @end ignore -@set LASTCHANGE Thu Dec 2 15:07:19 EST 2021 +@set LASTCHANGE Sun Dec 26 16:02:48 EST 2021 @set EDITION 5.2 @set VERSION 5.2 -@set UPDATED 2 December 2021 +@set UPDATED 26 December 2021 @set UPDATED-MONTH December 2021 diff --git a/execute_cmd.c b/execute_cmd.c index b4f71b195..f86f396ad 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -4268,7 +4268,7 @@ fix_arrayref_words (words) for (w = wcmd->next; w; w = w->next) { - if (w->word && w->word->word && valid_array_reference (w->word->word, VA_NOEXPAND)) + if (w->word && w->word->word && valid_array_reference (w->word->word, 0)) w->word->flags |= W_ARRAYREF; } } diff --git a/lib/glob/glob.c b/lib/glob/glob.c index 5aa34794b..1a6515409 100644 --- a/lib/glob/glob.c +++ b/lib/glob/glob.c @@ -103,7 +103,7 @@ int glob_ignore_case = 0; /* Global variable controlling whether globbing ever returns . or .. regardless of the pattern. If set to 1, no glob pattern will ever match `.' or `..'. Disabled by default. */ -int glob_always_skip_dot_and_dotdot = 0; +int glob_always_skip_dot_and_dotdot = 1; /* Global variable to return to signify an error in globbing. */ char *glob_error_return; diff --git a/subst.c b/subst.c index bf2a8d391..72c61dc2b 100644 --- a/subst.c +++ b/subst.c @@ -1819,7 +1819,7 @@ skip_matched_pair (string, start, open, close, flags) : skip_double_quoted (ss, slen, ++i, 0); /* no increment, the skip functions increment past the closing quote. */ } - else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) + else if ((flags & 1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) { si = i + 2; if (string[si] == '\0') @@ -11608,6 +11608,12 @@ word_list_split (list) w->word[0] = '\0'; tresult = make_word_list (w, (WORD_LIST *)NULL); } +#if defined (ARRAY_VARS) + /* pass W_ARRAYREF through for words that are not split and are + identical to the original word. */ + if (tresult && tresult->next == 0 && t->next == 0 && (t->word->flags & W_ARRAYREF) && STREQ (t->word->word, tresult->word->word)) + tresult->word->flags |= W_ARRAYREF; +#endif if (result == 0) result = e = tresult; else diff --git a/tests/assoc.right b/tests/assoc.right index 1d558228a..3516a6e55 100644 --- a/tests/assoc.right +++ b/tests/assoc.right @@ -253,12 +253,12 @@ declare -A dict=(["?"]="quest" ["*"]="star" ["'"]="squote" ["\$"]="dol" ["\""]=" dict=( "?" "quest" "*" "star" "'" "squote" "\$" "dol" "\"" "dquote" "\\" "bslash" "@" "at" "}" "rbrace" "{" "lbrace" "\`" "bquote" ) declare -A foo=([two]="" [one]="1" ) foo=( two "" one "1" ) -rparen dquote rbrace bs -declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbrace" ["\\"]="bs" ) -")" "rparen" "\"" "dquote" "]" "rbrace" "\\" "bs" -declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbrace" ["\\"]="bs" ) -declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbrace" ["\\"]="bs" ) -declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbrace" ["\\"]="bs" ) +rparen dquote rbracket bs +declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbracket" ["\\"]="bs" ) +")" "rparen" "\"" "dquote" "]" "rbracket" "\\" "bs" +declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbracket" ["\\"]="bs" ) +declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbracket" ["\\"]="bs" ) +declare -A a=([")"]="rparen" ["\""]="dquote" ["]"]="rbracket" ["\\"]="bs" ) declare -Arx foo=([two]="2" [three]="3" [one]="1" ) ./assoc11.sub: line 90: foo: readonly variable declare -A v1=(["1 2"]="3" ) @@ -383,3 +383,13 @@ set stderr 42 42 +declare -A A=(["]"]="rbracket" ["["]="lbracket" ) +declare -A A=() +declare -A A=(["]"]="rbracket" ["["]="lbracket" ) +declare -A A=() +declare -A A=(["]"]="rbracket" ["["]="lbracket" ) +declare -A A=() +declare -A A=(["]"]="rbracket" ["["]="lbracket" ) +declare -A A=() +declare -A A=(["]"]="rbracket" ["["]="lbracket" ) +declare -A A=() diff --git a/tests/assoc.tests b/tests/assoc.tests index 5dd90ce52..5fc2b2cff 100644 --- a/tests/assoc.tests +++ b/tests/assoc.tests @@ -259,3 +259,6 @@ ${THIS_SH} ./assoc15.sub # tests with subscripts being expanded more than one in ${xxx} word expansions ${THIS_SH} ./assoc16.sub + +# tests with `[' and `]' subscripts and `unset' +${THIS_SH} ./assoc17.sub diff --git a/tests/assoc11.sub b/tests/assoc11.sub index 13111a52b..9d9afae95 100644 --- a/tests/assoc11.sub +++ b/tests/assoc11.sub @@ -69,7 +69,7 @@ foo=(one 1 two) declare -p foo echo foo=\( ${foo[@]@K} \) -typeset -A a=( [\\]=bs [\"]=dquote [\)]=rparen [\]]=rbrace ) +typeset -A a=( [\\]=bs [\"]=dquote [\)]=rparen [\]]=rbracket ) echo ${a[@]} declare -p a diff --git a/tests/assoc14.sub b/tests/assoc14.sub index 854878c02..95df04925 100644 --- a/tests/assoc14.sub +++ b/tests/assoc14.sub @@ -1,3 +1,17 @@ +# 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 . +# + declare -A assoc=(hello world "key with spaces" "value with spaces" one 1 foo bar) declare -p assoc diff --git a/tests/assoc17.sub b/tests/assoc17.sub new file mode 100644 index 000000000..a98aaa158 --- /dev/null +++ b/tests/assoc17.sub @@ -0,0 +1,58 @@ +# 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 . +# +# test behavior with `unset' and `[' and ']' subscripts + +declare -A A +rkey=']' +lkey='[' + +A[$rkey]=rbracket +A[$lkey]=lbracket +declare -p A + +unset A[$rkey] +unset A[$lkey] +declare -p A + +A["$rkey"]=rbracket +A["$lkey"]=lbracket +declare -p A + +unset A["$rkey"] +unset A["$lkey"] +declare -p A + +A[\]]=rbracket +A[\[]=lbracket +declare -p A + +unset A[\]] +unset A[\[] +declare -p A + +A[']']=rbracket +A['[']=lbracket +declare -p A + +unset A[']'] +unset A['['] +declare -p A + +A["]"]=rbracket +A["["]=lbracket +declare -p A + +unset A["]"] +unset A["["] +declare -p A diff --git a/tests/extglob.right b/tests/extglob.right index e8f954ac9..2974cecaf 100644 --- a/tests/extglob.right +++ b/tests/extglob.right @@ -92,16 +92,16 @@ a ab a ab a ab a -. .. -. .. a.log +*(.) +a.log *(foo) *(foo|bar) a.log ?(foo) a.log a.log -. .. -. .. +*(foo).* +*(foo|bar).* a.log a.log .x .y .z @@ -114,16 +114,16 @@ a b c .x .y .z a b c .x .y .z a b c * -.. .b a -.. .b a -a .. .b -. .. .b -. .. .b -.. .b a -.. .b a -a .. .b -. .. .b -. .. .b +.b a +.b a +a .b +.b +.b +.b a +.b a +a .b +.b +.b dotglob: .a .foo bar @(.foo) .foo diff --git a/tests/extglob7.sub b/tests/extglob7.sub index 07a6071b7..5fab9cdab 100644 --- a/tests/extglob7.sub +++ b/tests/extglob7.sub @@ -12,6 +12,7 @@ cd $TESTDIR || { LC_CTYPE=C LC_COLLATE=C shopt -s extglob dotglob +shopt -u globskipdots # XXX - backwards compatibility touch .foo bar .a echo dotglob: .a .foo bar diff --git a/tests/glob.right b/tests/glob.right index 949669050..723ee7b46 100644 --- a/tests/glob.right +++ b/tests/glob.right @@ -121,6 +121,10 @@ a\*b a\*b* é/* é/* +a aa b bb +.a .aa .b .bb a aa b bb +.a .aa .b .bb +. .. .a .aa .b .bb argv[1] = argv[2] = argv[3] = @@ -135,7 +139,7 @@ argv[2] = argv[3] = argv[4] = tmp/l1 tmp/l2 tmp/*4 tmp/l3 -./glob.tests: line 65: no match: tmp/*4 +./glob.tests: line 66: no match: tmp/*4 argv[1] = argv[1] = <*> argv[1] = diff --git a/tests/glob.tests b/tests/glob.tests index b35d7336f..02d530261 100644 --- a/tests/glob.tests +++ b/tests/glob.tests @@ -30,6 +30,7 @@ ${THIS_SH} ./glob6.sub ${THIS_SH} ./glob7.sub ${THIS_SH} ./glob8.sub ${THIS_SH} ./glob9.sub +${THIS_SH} ./glob10.sub MYDIR=$PWD # save where we are diff --git a/tests/glob10.sub b/tests/glob10.sub new file mode 100644 index 000000000..7c14c0d24 --- /dev/null +++ b/tests/glob10.sub @@ -0,0 +1,32 @@ +# 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 . +# + +# test basic behavior of globskipdots +TDIR=/tmp/dotglob-$$ + +{ mkdir $TDIR && cd $TDIR; } || exit 1 + +touch a b aa bb .a .b .aa .bb + +echo * +shopt -s dotglob +echo * + +shopt -s globskipdots +echo .* +shopt -u globskipdots +echo .* + +cd $OLDPWD +rm -rf $TDIR diff --git a/tests/shopt.right b/tests/shopt.right index 177ea2f09..d617c1d72 100644 --- a/tests/shopt.right +++ b/tests/shopt.right @@ -28,6 +28,7 @@ shopt -s extquote shopt -u failglob shopt -s force_fignore shopt -s globasciiranges +shopt -s globskipdots shopt -u globstar shopt -u gnu_errfmt shopt -u histappend @@ -69,6 +70,7 @@ shopt -s expand_aliases shopt -s extquote shopt -s force_fignore shopt -s globasciiranges +shopt -s globskipdots shopt -s hostcomplete shopt -s interactive_comments shopt -s patsub_replacement @@ -302,5 +304,9 @@ xtrace off -- ./shopt.tests: line 106: shopt: xyz1: invalid shell option name ./shopt.tests: line 107: shopt: xyz1: invalid option name +28c28 +< globskipdots off +--- +> globskipdots on expand_aliases on expand_aliases on -- 2.47.2