functions can fully unset local vars in other scopes

2022-07-28 Thread Emanuele Torre
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -march=x86-64 -mtune=generic -O2 -pipe -fno-plt
-DDEFAULT_PATH_VALUE='/usr/local/sbin:/usr/local/bin:/usr/bin'
-DSTANDARD_UTILS_PATH='/usr/bin' -DSYS_BASHRC='/etc/bash.bashrc'
-DSYS_BASH_LOGOUT='/etc/bash.bash_logout'
-DNON_INTERACTIVE_LOGIN_SHELLS
uname output: Linux t420 5.15.55-2-lts #1 SMP Fri, 22 Jul 2022
23:29:06 + x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 5.1
Patch Level: 16
Release Status: release

Description:
  `bash' does not let `unset' fully undeclare local variables. (so that
  they can be used later as `local' variables without needing to
  redeclare them I assume.)

  bash-5.1$ x=abc; declare -p x; unset -v x; declare -p x
  declare -- x="abc"
  bash: declare: x: not found

  bash-5.1$ a () { local x=abc; local -p x; unset -v x; local -p x ;}
  bash-5.1$ a
  declare -- x="abc"
  declare -- x

  However, other functions are allowed to delete those variables:

  bash-5.1$ a () { local x=abc; b; local -p x ;}
  bash-5.1$ b () { unset -v x ;}
  bash-5.1$ a
  bash: local: x: not found

  This enables defininng a "really_unset" function like so:

  really_unset () { unset "$@" ;}

  Which may be useful I guess.

  But I think allowing functions to unset local variables from other
  functions defeats the whole purpose of having that `unset'
  behaviour. This enables `local' variable to unexpectedly become global
  after a function is called.

Fix:
  I think calling `unset -v x' (where `x' is a local variable not in the
  current scope) should behave as if it was called in the scope of `x',
  so `x' should remain declared in that scope with no attributes and no
  value.

  It may be nice to also add a "force" option for `unset' that makes it
  actually unset the variable if it is `local'. Since this could be
  useful in some cases and it won't be possible after the behaviour is
  changed.



Re: functions can fully unset local vars in other scopes

2022-07-28 Thread Emanuele Torre
>  It may be nice to also add a "force" option for `unset' that makes it
>  actually unset the variable if it is `local'. Since this could be
>  useful in some cases and it won't be possible after the behaviour is
>  changed.

Clarification
  I meant that it should fully undeclare local variables in the current
  scope, not that it should be able to fully undeclare variables from
  other scopes.

  Now that I think about it, it does not really make sense to implement
  it like a "force" option.

  I think adding a "-l" option that behaves similarly to "-v", but only
  works on local varnames of the current scope and fully undeclares them
  would make more sense.



Re: functions can fully unset local vars in other scopes

2022-07-28 Thread Greg Wooledge
On Fri, Jul 29, 2022 at 03:59:07AM +0200, Emanuele Torre wrote:
>   This enables defininng a "really_unset" function like so:
> 
>   really_unset () { unset "$@" ;}
> 
>   Which may be useful I guess.

https://lists.gnu.org/archive/html/bug-bash/2010-05/msg00045.html

https://fvue.nl/wiki/Bash:_passing_variables_by_reference



Re: functions can fully unset local vars in other scopes

2022-07-28 Thread Koichi Murase
2022年7月29日(金) 10:59 Emanuele Torre :
> Description:
>   `bash' does not let `unset' fully undeclare local variables. (so that
>   they can be used later as `local' variables without needing to
>   redeclare them I assume.)
>
>   [...]
>
>   However, other functions are allowed to delete those variables:

This is a documented behavior:

>From Bash Reference Manul - 3.3 Shell Functions
https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Functions
> The unset builtin also acts using the same dynamic scope: if a
> variable is local to the current scope, unset will unset it;
> otherwise the unset will refer to the variable found in any calling
> scope as described above. If a variable at the current local scope
> is unset, it will remain so until it is reset in that scope or until
> the function returns. Once the function returns, any instance of the
> variable at a previous scope will become visible. If the unset acts
> on a variable at a previous scope, any instance of a variable with
> that name that had been shadowed will become visible.

There has been a long discussion before.

https://lists.gnu.org/archive/html/bug-bash/2018-02/threads.html#00065
https://lists.gnu.org/archive/html/bug-bash/2018-03/threads.html#0

Also, I would like to repeat myself in that thread.  The dynamic
unsetting has existing applications [see the following my reply]:

https://lists.gnu.org/archive/html/bug-bash/2018-03/msg00020.html



> This enables defininng a "really_unset" function like so:
>
> really_unset () { unset "$@" ;}
>
> Which may be useful I guess.

This is a well-known idiom and is already used in some places as
`unlocal'.

https://github.com/scop/bash-completion/blob/36ceb272ddf7ef70b7fa79c5c3686080b1510054/bash_completion#L248-L263
https://github.com/akinomyoga/ble.sh/blob/0b95d5d900b79a63e7f0834da5aa7276b8332a44/src/util.sh#L388-L404

This is also used for Freddy Vulto's upvars trick as Greg has cited.



> But I think allowing functions to unset local variables from other
> functions defeats the whole purpose of having that `unset'
> behaviour. This enables `local' variable to unexpectedly become
> global after a function is called.

I think these two cases will never be mixed.  Because when a user does
not intend to remove the variable placeholder of the previous scopes,
the user unsets a variable that is declared within the same function.
When a user intends to remove that of the previous scopes, the
`local-variable name' can become global, but it is just what the user
expects.



> Fix:
>   I think calling `unset -v x' (where `x' is a local variable not in the
>   current scope) should behave as if it was called in the scope of `x',
>   so `x' should remain declared in that scope with no attributes and no
>   value.

This is what `shopt -s localvar_unset' does.

> It may be nice to also add a "force" option for `unset' that makes it
> actually unset the variable if it is `local'. Since this could be
> useful in some cases and it won't be possible after the behaviour is
> changed.

For this purpose, you can always use `unlocal' referenced above.

--
Koichi



Re: functions can fully unset local vars in other scopes

2022-07-28 Thread Emanuele Torre
Thank you, Koichi and Greg.

I didn't notice that this was intended and documented. Also it is good
to know that `localvar_unset' exists.

Cheers.