diff --git a/NEWS b/NEWS index 7bc685bf..3ad3a39a 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ History of Yash ---------------------------------------------------------------------- Yash 2.49 + + '--forlocal' option. * Expansion of ""$*, ""$@, $*"", and $@"" now correctly yields an empty string rather than nothing when there are no positional parameters. diff --git a/doc/_set.txt b/doc/_set.txt index bb7876fe..dceb1198 100644 --- a/doc/_set.txt +++ b/doc/_set.txt @@ -126,6 +126,15 @@ enabled. This option enables link:expand.html#extendedglob[extension in pathname expansion]. +[[so-forlocal]]forlocal:: +(Enabled by default) +If a link:syntax.html#for[for loop] is executed within a +link:exec.html#function[function], this option causes the iteration variable +to be created as a link:exec.html#localvar[local variable], even if the +variable already exists globally. +This option has no effect if the link:posix.html[POSIXly-correct mode] is +active. + [[so-glob]]glob (`+f`):: (Enabled by default) This option enables link:expand.html#glob[pathname expansion]. diff --git a/doc/exec.txt b/doc/exec.txt index df18d98b..30ff0369 100644 --- a/doc/exec.txt +++ b/doc/exec.txt @@ -209,7 +209,8 @@ finishes. dfn:[Local variables] are temporary variables that are defined in a function and exist during the function execution only. -They can be defined by the link:_typeset.html[typeset built-in]. +They can be defined by the link:_typeset.html[typeset built-in] +or implicitly created by a link:syntax.html#for[for loop]. They are removed when the function execution finishes. Local variables may _hide_ variables that have already been defined before diff --git a/doc/posix.txt b/doc/posix.txt index 7219b833..2c8349b4 100644 --- a/doc/posix.txt +++ b/doc/posix.txt @@ -35,9 +35,9 @@ When the POSIXly-correct mode is enabled: - Global link:syntax.html#aliases[aliases] are not substituted. - Nested commands in a link:syntax.html#compound[compound command] must not be empty. -- Words expanded in a link:syntax.html#for[for loop] are assigned as a global - variable rather than a local. The variable must have a portable (ASCII-only) - name. +- The link:syntax.html#for[for loop] iteration variable is created as global, + regardless of the link:_set.html#so-forlocal[forlocal] shell option. + The variable must have a portable (ASCII-only) name. - The first pattern in a link:syntax.html#case[case command] cannot be +esac+. - The +!+ keyword cannot be followed by +(+ without any whitespaces in-between. diff --git a/doc/syntax.txt b/doc/syntax.txt index f9ab35f5..678a7049 100644 --- a/doc/syntax.txt +++ b/doc/syntax.txt @@ -340,10 +340,14 @@ order the words were expanded): . Assign the word to the variable whose name is {{varname}}. . Execute the {{command}}s. -Each word is assigned as a link:exec.html#localvar[local variable] except in -the POSIXly-correct mode. -If the expansion of the {{word}}s yielded no words as a result, the -{{command}}s are not executed at all. +By default, if a for loop is executed within a function, {{varname}} is +created as a link:exec.html#localvar[local variable], even if it already +exists globally. Turning off the link:_set.html#so-forlocal[forlocal] shell +option or enabling the link:posix.html[POSIXly-correct mode] mode will +disable this behavior. + +If the expansion of the {{word}}s yields no words, no variable is created +and the {{command}}s are not executed at all. The exit status of a for loop is that of the last executed {{command}}. The exit status is zero if the {{command}}s are not empty and not executed at all. diff --git a/exec.c b/exec.c index 4cfc446a..9fbd5feb 100644 --- a/exec.c +++ b/exec.c @@ -447,7 +447,9 @@ void exec_for(const command_T *c, bool finally_exit) int i; for (i = 0; i < count; i++) { if (!set_variable(c->c_forname, words[i], - posixly_correct ? SCOPE_GLOBAL : SCOPE_LOCAL, false)) { + shopt_forlocal && !posixly_correct ? + SCOPE_LOCAL : SCOPE_GLOBAL, + false)) { laststatus = Exit_ASSGNERR; goto done; } diff --git a/option.c b/option.c index 8aab9d1d..eb68d218 100644 --- a/option.c +++ b/option.c @@ -89,6 +89,8 @@ bool shopt_allexport = false; /* If set, when a function is defined, all the commands in the function * are hashed. Corresponds to the -h/--hashondef option. */ bool shopt_hashondef = false; +/* If set, the 'for' loop iteration variable will be made local. */ +bool shopt_forlocal = true; /* If set, when a command returns a non-zero status, the shell exits. * Corresponds to the -e/--errexit option. */ @@ -223,6 +225,7 @@ static const struct option_T shell_options[] = { { 0, 0, L"errreturn", &shopt_errreturn, true, }, { 0, L'n', L"exec", &shopt_exec, true, }, { 0, 0, L"extendedglob", &shopt_extendedglob, true, }, + { 0, 0, L"forlocal", &shopt_forlocal, true, }, { 0, L'f', L"glob", &shopt_glob, true, }, { L'h', 0, L"hashondef", &shopt_hashondef, true, }, #if YASH_ENABLE_HISTORY diff --git a/option.h b/option.h index 81cf18c8..308b9f14 100644 --- a/option.h +++ b/option.h @@ -41,7 +41,7 @@ extern _Bool is_interactive, is_interactive_now; extern _Bool shopt_cmdline, shopt_stdin; extern _Bool do_job_control, shopt_notify, shopt_notifyle, shopt_curasync, shopt_curbg, shopt_curstop; -extern _Bool shopt_allexport, shopt_hashondef; +extern _Bool shopt_allexport, shopt_hashondef, shopt_forlocal; extern _Bool shopt_errexit, shopt_errreturn, shopt_pipefail, shopt_unset, shopt_exec, shopt_ignoreeof, shopt_verbose, shopt_xtrace; extern _Bool shopt_traceall; diff --git a/tests/for-p.tst b/tests/for-p.tst index 90a72bce..fd76f4c0 100644 --- a/tests/for-p.tst +++ b/tests/for-p.tst @@ -163,4 +163,13 @@ b 2 c 3 __OUT__ +test_o 'iteration variable is global' +unset -v i +fn() { for i in a b c; do : ; done; } +fn +echo "${i-UNSET}" +__IN__ +c +__OUT__ + # vim: set ft=sh ts=8 sts=4 sw=4 noet: diff --git a/tests/for-y.tst b/tests/for-y.tst index 4a94205a..a62652a4 100644 --- a/tests/for-y.tst +++ b/tests/for-y.tst @@ -336,4 +336,23 @@ for v in 1; do done __IN__ +test_oE 'iteration variable is local' +unset -v i +fn() { for i in a b c; do : ; done; } +fn +echo "${i-UNSET}" +__IN__ +UNSET +__OUT__ + +test_oE 'noforlocal: iteration variable is global' +set -o noforlocal +unset -v i +fn() { for i in a b c; do : ; done; } +fn +echo "${i-UNSET}" +__IN__ +c +__OUT__ + # vim: set ft=sh ts=8 sts=4 sw=4 noet: diff --git a/tests/help-y.tst b/tests/help-y.tst index 1398d80f..3fd66563 100644 --- a/tests/help-y.tst +++ b/tests/help-y.tst @@ -771,6 +771,7 @@ Options: -o errreturn +n -o exec -o extendedglob + -o forlocal +f -o glob -h -o hashondef -o histspace diff --git a/tests/set-y.tst b/tests/set-y.tst index 9a33760d..0012ad28 100644 --- a/tests/set-y.tst +++ b/tests/set-y.tst @@ -411,6 +411,7 @@ errexit off errreturn off exec on extendedglob off +forlocal on glob on hashondef off ignoreeof off @@ -465,6 +466,7 @@ set +o errexit set +o errreturn set -o exec set +o extendedglob +set -o forlocal set -o glob set +o hashondef set +o ignoreeof diff --git a/tests/startup-y.tst b/tests/startup-y.tst index 0b394250..726a9c0c 100644 --- a/tests/startup-y.tst +++ b/tests/startup-y.tst @@ -553,6 +553,7 @@ Options: -o errreturn +n -o exec -o extendedglob + -o forlocal +f -o glob -h -o hashondef -o histspace @@ -610,6 +611,7 @@ Options: -o errreturn +n -o exec -o extendedglob + -o forlocal +f -o glob -h -o hashondef -o histspace