How to use declare -x in bash - bash

Can some one give an example where declare -x would be useful ?

declare -x FOO is the same as export FOO. It "exports" the FOO variable as an environment variable, so that programs you run from that shell session would see it.

Declare -x can be used instead of eval to allow variables to be set as arguments to the shell. For example, you can replace the extremely insecure:
# THIS IS NOT SAFE
while test $# -gt 0; do
eval export $1
shift
done
with the safer:
while test $# -gt 0; do
declare -x $1
shift
done
As an aside, this construct allows the user to invoke the script as:
$ ./test-script foo=bar
rather than the more idiomatic (but confusing to some):
$ foo=bar ./test-script

Use declare -x when you want to pass a variable to a different program, but don't want the variable to be used in global scope of the parent shell (i.e. when declaring inside a function).
From the bash help:
When used in a function, declare makes NAMEs local, as with the local
command. The -g option suppresses this behavior.
-x to make NAMEs export
Using + instead of - turns off the given attribute.
So declare -gx NAME=X will effectively behave the same as export NAME=X, but declare -x does not when the declare statements are inside functions.

The accepted solution is not accurate, as commented by #sampablokuper.
However both export variable a to subshells (below within ()).
test.sh:
#/bin/bash
foo() {
declare -x a="OK"
([ "$a" = "OK" ]) && echo "foo exported a locally"
}
bar() {
export a="OK"
([ "$a" = "OK" ]) && echo "bar exported a locally"
}
foo
echo "Global value of a by foo: ${a}"
([ "$a" = "OK" ]) && echo "foo exported a globally" \
|| echo 'foo did not export a globally'
bar
echo "Global value of a by bar: ${a}"
([ "$a" = "OK" ]) && echo "bar exported a globally" \
|| echo 'bar did not export a globally'
Runs as follows:
$ ./test.sh
foo exported a locally
Global value of a by foo:
foo did not export a globally
bar exported a locally
Global value of a by bar: OK
bar exported a globally

Related

Makefile: check if multiple variables are set

In my Makefile I want to check if multiple environments variables are set.
But I don't want to write multiple ifndef for each one. I just want an array of variables to make it reusable.
check-variables:
for var in var1 var2 var3 ; do \
if [ -z "$$var" ] ; then \
echo "$$var is not set"; exit 1; \
fi \
done
But it's not working...
This isn't a make issue, it's a shell issue. If you ran that script at your shell prompt (changing $$ back to $ as make will do, of course) it wouldn't work either. Until you can get the command to work at your shell prompt, you can't get it to work in a makefile.
The shell command would be:
for var in var1 var2 var3 ; do \
if [ -z "$var" ] ; then \
echo "$var is not set"; exit 1; \
fi \
done
You can see why this doesn't do what you want: you're checking the shell variable var every time. What you're trying to do is check the value of the variable which is named by the value of $var. To do this you need eval (the shell's eval, not make's eval function):
for var in var1 var2 var3 ; do \
eval test -n \"\$$var\" \
|| { echo "$var is not set"; exit 1; }; \
done
You should discover that the above will work in the shell, then you need to put it back into the makefile (and double all the $).

Bash variable variables passed through functions

I've looked over a couple other variable variable posts here but still seem stuck with what I'm trying to attempt.
I have an existing script that has a series of similar block like
set_name="something"
# Required values
var1=$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "key1_${set_name}")
if [[ -z "$var1" ]]; then
echo "Cannot find required var1"
exit 1
fi
var2=$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "key2_${set_name}")
if [[ -z "$var2" ]]; then
echo "Cannot find required var2"
exit 1
fi
# Optional, okay to be empty
var3=$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "key3_${set_name}")
var4=$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "key4_${set_name}")
I was trying to factor some of the boilerplate checks out to keep this set of lookups assignment easier to read (in my opinion anyway). The last iteration I had attempted (clearly not working) looks like
ZTest () {
var=$1
if [[ -z "${!var}" ]]; then
echo $2
exit 1
fi
}
VarRequire () {
var=$1
key=$2
errmsg=$3
VarLookup ${!var} $key
ZTest ${!var} $errmsg
}
VarLookup () {
var=$1
key=$2
${!var}=$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "$key")
}
# Required
VarRequire "var1" "key1_${set_name}" "Cannot find required var1"
VarRequire "var2" "key2_${set_name}" "Cannot find required var2"
# optional
VarLookup "var3" "key3_${set_name}"
VarLookup "var4" "key4_${set_name}"
The end result is I would be able to reference $var1, $var2, $var3, $var4 down the line in the script just the same as the original.
Is what I'm attempting possible in bash?
ZTest is already available as a parameter expansion operator:
: ${var1:?Cannot find required var1}
You are close with VarLookup, though; you need to use the declare command to create the variable. ${!var} is only for accessing the value once the variable exists. (Note that declare requires the -g option to avoid creating a local variable, and that option was only introduced in version 4.2.)
VarLookup () {
local var=$1
local key=$2
declare -g "${var}=$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "$key")"
}
Prior to version 4.2, you can use printf in place of declare:
printf -v "$var" '%s' "$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "$key")"
I would resist the urge to refactor too much in shell, as indirection can be fragile. I'd suggest something more direct like
var_lookup () {
REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "${1}_$set_name"
}
var1=$(var_lookup key1); : ${var1:?Cannot find required var1}
var2=$(var_lookup key2); : ${var2:?Cannot find required var2}
var3=$(var_lookup key3)
var4=$(var_lookup key4)
Too much indirection in the Var* functions
VarRequire () {
local var=$1
local key=$2
local errmsg=$3
VarLookup "$var" "$key"
ZTest "$var" "$errmsg"
}
VarLookup () {
local var=$1
local key=$2
declare -g "$var"="$(REQUIRED_ENV_VAR=/path/to/somewhere mybinary -p "$key")"
}
The declare command allows you to use a variable's value as the variable name. I use the -g option so the variable is global.
The ZTest function does require the indirection.
From the BASH manpage
local [option] [name[=value] ...]
For each argument, a local variable named name is created,
and assigned value. The option can be any of the options
accepted by declare. When local is used within a function, it
causes the variable name to have a visible scope restricted to
that function and its children. With no operands, local writes a
list of local variables to the standard output. It is an
error to use local when not within a function. The return status
is 0 unless local is used outside a function, an invalid name is
supplied, or name is a readonly variable.
So the answer is yes. If you declare a variable a local within a function, and it calls another function, that function has access to the variable as well.
Can you set REQUIRED_ENV_VAR=/path/to/somewhere once at the start?
And have your mybinary return 0 when a var is found?
You might like something like
function mylog {
errortype=$1
shift
echo "(maybe show ${errortype}) $*"
if [ "$errortype" = "error" ]; then
exit 1
fi
}
var1=$(mybinary -p "key1_${set_name}") || mylog error "Cannot find required var1"
var2=$(mybinary -p "key2_${set_name}") || mylog error "Cannot find required var2"
var3=$(mybinary -p "key3_${set_name}")
var4=$(mybinary -p "key4_${set_name}") || mylog debug "Just want to say something"

Strange Bash function export for the Shellshock bug

Why does the code
date
bash -c "date"
declare -x date='() { echo today; }' #aka export date='() { echo today; }'
date
bash -c "date"
print
Wed Sep 24 22:01:50 CEST 2014
Wed Sep 24 22:01:50 CEST 2014
Wed Sep 24 22:01:50 CEST 2014
today
?
Where (and why) does the evaluation
date$date
happen and getting
date() {echo today; }
Ad: #Etan Reisner
I exporting a variable - not a function. Bash makes a function from it. The
export date='someting'
is still a variable regardless of its content. So, why is
export date='() { echo something; }' #Note, it is a variable, not function.
converted to an function?
The mentioned security advisory talks about the execution of the command following the variable, for example,
x='() { echo I do nothing; }; echo vulnerable' bash -c ':'
^^^^^^^^^^^^^^^
This is executed - this vunerability is CLOSED in version 4.3.25(1).
The command after the env-definition isn't executed in the latest Bash.
But the question remains - Why does Bash convert the exported variable to a function?
It is a bug ;) Full demo, based on #chepner's answer:
#Define three variables
foo='() { echo variable foo; }' # ()crafted
qux='() { echo variable qux; }' # ()crafted
bar='variable bar' # Normal
export foo qux bar # Export
#Define the same name functions (but not qux!)
foo() { echo "function foo"; }
bar() { echo "function bar"; }
declare -fx foo bar #Export
#printouts
echo "current shell foo variable:=$foo="
echo "current shell foo function:=$(foo)="
echo "current shell bar variable:=$bar="
echo "current shell bar function:=$(bar)="
echo "current shell qux variable:=$qux="
echo "current shell qux function:=$(qux)="
#subshell
bash -c 'echo subshell foo variable:=$foo='
bash -c 'echo subshell foo command :=$(foo)='
bash -c 'echo subshell bar variable:=$bar='
bash -c 'echo subshell bar command :=$(bar)='
bash -c 'echo subshell qux variable:=$qux='
bash -c 'echo subshell qux command :=$(qux)='
prints
current shell foo variable:=() { echo variable foo; }=
current shell foo function:=function foo=
current shell bar variable:=variable bar=
current shell bar function:=function bar=
current shell qux variable:=() { echo variable qux; }=
tt: line 20: qux: command not found
current shell qux function:==
subshell foo variable:== #<-- LOST the exported foo variable
subshell foo command :=function foo=
subshell bar variable:=variable bar=
subshell bar command :=function bar=
subshell qux variable:== #<-- And the variable qux got converted to
subshell qux command :=variable qux= #<-- function qux in the subshell (!!!).
Avoiding the long comments, here is code from the Bash sources:
if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
^^^^^^^^ THE PROBLEM
{
string_length = strlen (string);
temp_string = (char *)xmalloc (3 + string_length + char_index);
strcpy (temp_string, name);
temp_string[char_index] = ' ';
strcpy (temp_string + char_index + 1, string);
if (posixly_correct == 0 || legal_identifier (name))
parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
/* Ancient backwards compatibility. Old versions of bash exported
functions like name()=() {...} */
The "ancient" (seems) was better... :)
if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
name[char_index - 2] = '\0';
The key point to remember is that
foo='() { echo 5; }'
only defines a string parameter with a string that looks a lot like a function. It's still a regular string:
$ echo $foo
() { echo 5; }
And not a function:
$ foo
bash: foo: command not found
Once foo is marked for export,
$ export foo
any child Bash will see the following string in its environment:
foo=() { echo 5; }
Normally, such strings become shell variables, using the part preceding the = as the name and the part following the value. However, Bash treats such strings specially by defining a function instead:
$ echo $foo
$ foo
5
You can see that the environment itself is not changed by examining it with something other than Bash:
$ perl -e 'print $ENV{foo}\n"'
() { echo 5
}
(The parent Bash replaces the semicolon with a newline when creating the child's environment, apparently). It's only the child Bash that creates a function instead of a shell variable from such a string.
The fact that foo could be both a parameter and a function within the same shell;
$ foo=5
$ foo () { echo 9; }
$ echo $foo
5
$ foo
9
explains why -f is needed with export. export foo would cause the string foo=5 to be added to the environment of a child; export -f foo is used to add the string foo=() { echo 9; }.
You are essentially manually exporting a function with the name date. (Since that is the format that bash uses internally to export functions. Which is suggested by Barmar in his answer. This mechanism is mentioned here at the very least.)
Then when you run bash it sees that exported function and uses it when you tell it to run date.
Is the question then where is that mechanism specified? My guess is it isn't since it is an internal detail.
This should show the merging of the behaviours if that helps anything.
$ bar() { echo automatic; }; export -f bar
$ declare -x foo='() { echo manual; }'
$ declare -p foo bar
declare -x foo="() { echo manual; }"
-bash: declare: bar: not found
$ type foo bar
-bash: type: foo: not found
bar is a function
bar ()
{
echo automatic
}
$ bash -c 'type foo bar'
foo is a function
foo ()
{
echo manual
}
bar is a function
bar ()
{
echo automatic
}
The answer to your question comes directly from man bash:
The export and declare -x commands allow parameters and functions
to be added to and deleted from the environment. If the value of a
parameter in the environment is modified, the new value becomes part
of the environment, replacing the old.
Thus
declare -x date='() { echo today; }'
replaces date in the environment. The next immediate call to date gives date as it exists in the script (which is unchanged). The call to bash -c "date" creates a new shell and executes date as defined by declare -x.

How to export an associative array (hash) in bash?

Related, but not a duplicate of: How to define hash tables in Bash?
I can define and use a bash hash, but I am unable to export it, even with the -x flag. For example, the following works to export (and test exportation of) a normal string variable:
aschirma#graphics9-lnx:/$ export animal_cow="moo"
aschirma#graphics9-lnx:/$ bash -c "echo \$animal_cow"
moo
aschirma#graphics9-lnx:/$
However, if I try to export a hash:
aschirma#graphics9-lnx:/$ declare -A -x animals
aschirma#graphics9-lnx:/$ animals[duck]="quack"
aschirma#graphics9-lnx:/$ echo ${animals[duck]}
quack
aschirma#graphics9-lnx:/$ bash -c "echo \${animals[duck]}"
aschirma#graphics9-lnx:/$
It seems the nested bash shell does not have the hash in its scope. I did verify this also by manually entering the nested bash shell and attempting to use the hash interactively.
There isn't really a good way to encode an array variable into the environment. See
http://www.mail-archive.com/bug-bash#gnu.org/msg01774.html (Chet Ramey is the maintainer of bash)
As a workaround for this harsh Bash limitation I'm using "serialize to temporary file" method. You can export plain variables, so you can pass an array (associative) through filepath. Of course, this has limitations, but sometimes works and is good enough.
declare -A MAP # export associative array
MAP[bar]="baz"
declare -x serialized_array=$(mktemp) # create temporary variable
# declare -p can be used to dump the definition
# of a variable as shell code ready to be interpreted
declare -p MAP > "${serialized_array}" # serialize an array in temporary file
# perform cleanup after finishing script
cleanup() {
rm "${serialized_array}"
}
trap cleanup EXIT
# ... in place where you need this variable ...
source "${serialized_array}" # deserialize an array
echo "map: ${MAP[#]}"
This is a bit old but I answer anyway, you could use temp files. If you do it right you can wrapper it to use them like arrays. For example with this function:
var() { # set var or add comtent
case $1 in
*=|*=*)
local __var_part1=$( echo "$1" | sed -e 's/=.*//' -e 's/[+,-]//' ) # cut +=/=
local __var_part2=$( echo "$1" | sed -e 's/.*.=//' )
local __var12=$tmp_dir/$__var_part1
mkdir -p ${__var12%/*} #create all subdirs if its an array
case $1 in
*+=*)
# if its an array try to add new item
if [ -d $tmp_dir/$__var_part1 ] ; then
printf -- $__var_part2 > $tmp_dir/$__var_part1/\ $((
$( echo $tmp_dir/$__var_part2/* \
| tail | basename )\ + 1 ))
else
printf -- "$__var_part2" >> $tmp_dir/$__var_part1
fi
;;
*-=*) false ;;
# else just add content
*) printf -- "$__var_part2" > $tmp_dir/$__var_part1 ;;
esac
;;
*) # just print var
if [ -d $tmp_dir/$1 ] ; then
ls $tmp_dir/$1
elif [ -e $tmp_dir/$1 ] ; then
cat $tmp_dir/$1
else
return 1
fi
;;
esac
}
# you can use mostly like you set vars in bash/shell
var test='Hello Welt!'
# if you need arrays set it like this:
var fruits/0='Apple'
var fruits/1='Banana'
# or if you need a dict:
var contacts/1/name="Max"
var contacts/1/surname="Musterman"
This not the fastest way, but its very flexible, simple and works in nearly all shells.
short answer --> export animals after declaring it
full -->
Try this way as a script:
#!/usr/bin/env bash
declare -A -x animals
export animals
animals[duck]="quack"
echo ${animals[duck]}
bash -c "echo ${animals[duck]}"
Output on my side using Bash version: 5.1.16(1)
quack
quack
or in terminal:
$ declare -A -x animals
$ export animals
$ animals[duck]="quack"
$ echo ${animals[duck]}
quack
$ bash -c "echo ${animals[duck]}"
quack
$

Test if a variable is read-only

To test if a variable is read-only, there are the following ugly hacks:
# True if readonly
readonly -p | egrep "declare -[:lower:]+ ${var}="
# False if readonly
temp="$var"; eval $var=x 2>/dev/null && eval $var=\$temp
Is there a more elegant solution?
Using a subshell seems to work. Both with local and exported variables.
$ foo=123
$ bar=456
$ readonly foo
$ echo $foo $bar
123 456
$ (unset foo 2> /dev/null) || echo "Read only"
Read only
$ (unset bar 2> /dev/null) || echo "Read only"
$
$ echo $foo $bar
123 456 # Still intact :-)
The important thing is that even is that the subshell salvages your RW ($bar in this case) from being unset in your current shell.
Tested with bash and ksh.
You can also add an empty string to the variable, which still leaves its value alone, but is faster than using a subshell, e.g.:
foo+= 2>/dev/null || echo "Read only"
Captured as a function, it'd be:
is-writable() { eval "$1+=" >2/dev/null; }

Resources