[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Is there a good way to tell the difference between a variable declar
From: |
Koichi Murase |
Subject: |
Re: Is there a good way to tell the difference between a variable declared in a function then a variable declared outside a function? |
Date: |
Sun, 30 Aug 2020 17:16:20 +0900 |
2020-08-30 13:38 Peng Yu <pengyu.ut@gmail.com>:
> I don't a way like `declare -p` which return 0 (when the vairable is
> defined in a function) and return non-zero (when the variable is not
> defined in a function). I don't think `local` can do so.
First, I naively think you should already know whether the variable
name is declared in the current function because the function is
written by you, and all the current-function variables are declared by
you, so there is no need to dynamically test if the variable exists or
not. If you declare a variable under certain conditions, you can
create another variable holding the state whether the variable exists
or not:
local is_varname_set=no
if some-conditoin; then
local varname
is_varname_set=yes
fi
# You can check the value of another variable
[[ $is_varname_set == yes ]]
----------
I recommend you to write in the above way if there is no particular
reason, but if you really need to dynamically test a variable, in Bash
5.1, you can use `local -p':
local -p varname &>/dev/null
In other versions of Bash, you can use `local' as Marco wrote.
Although the command `local' doesn't set the exit status that you
expect, you can always read and process the output of `local' for your
purpose. I think
local | grep '^varname='
would be probably the most straightforward way. If you want to reduce
the number of fork/exec, you may test the output using the conditional
command `[[ ... ]]'. For example,
[[ $'\n'$(local) == *$'\n'varname=* ]]
Another way would be to use the fact that the local variable is
initialized to be unset when it is newly created:
(varname=1; local varname; [[ ${varname+set} ]])
The above command first makes `varname' a `set' state by assigning a
value `1'. Next, it tries to create a new local variable by `local
varname'. If there is already a local variable in the current
function, this doesn't have any effect, and the variable `varname'
remains in a `set' state. If there is no local variable in the
current function, a variable is newly created in the current function
and initialized to be the `unset' state. Finally one can check if the
variable has a `set' or `unset' state. These processes break the
state of the variable, so need to be performed in a subshell. For
this technique to work, the shell option `localvar_inherit' (available
with Bash 5.0+) needs to be turned off, i.e., `shopt -u
localvar_inherit'.
Note that the above methods cannot be encapsulated in a function
because the builtin `local' needs to be performed in the function
scope that you want to test with. Instead, maybe you can store the
code in a variable and evaluated it by `eval'. For example,
# Prepare a global variable at the beginning of the script
is_current_function_local_variable='(VAR=1; local VAR; [[ ${VAR+set} ]])'
# Use the variable in a function scope
eval "${is_current_function_local_variable//VAR/varname}"
----------
If you don't need to distinguish the current-function local variable
and the previous-function local variable and just want to test if the
variable is a global variable or a function-scope variable, there is
another way. You can use the fact that a readonly global variable
prevents the creation of function-scope variables with the same name
while a readonly function-scope variable does not:
# Prepare a function
is-global() (readonly "$1"; ! local "$1") &>/dev/null
# Use a function to test if a variable is function-scope variable.
! is-global varname
This function `is-global' first make the variable `readonly' and then
try to create a new variable with the same name. Because this changes
the `readonly' attribute of the variable, it should be processed in a
subshell.
--
Koichi