Using Declare Inside a Function in Bash - bash

I am want to change a global variable (or at least append to it) using a function.
input="Hello"
example=input
func() {
declare -x $example="${input} World"
}
func
echo $input
The output of this would be "Hello" The original value. I would like it if the function were to able to change the original value. Is there alternative to accomplishing this. Please note I need to set example=input and then perform on the operation on example (the variable).
BTW, if I used eval instead the function will complain about World being a function or something.

Did you try using export?
export $example="${input} World"

you should use
declare -x -g $example="${input} World"
in your func
The -g option forces variables to be created or modified at the global scope, even when declare is executed in a shell function. It is ignored in all other cases.
see
http://www.gnu.org/software/bash/manual/bashref.html
Also note in MinGW, it seems that declare does not support the -g option.

You could use eval by redefining func() as the following:
func() {
eval $example=\"${input} World\"
}
This allows the double-quotes to "survive" the first parsing (which expands the variables into their values, as needs to be done) so that eval starts parsing again with the string 'input="Hello World".
As for the use of export to do the job, if the variable input does not actually need to be exported, include its '-n' option:
export -n $example=...
, and the variable remains a shell variable and does not get exported as an environment variable.

Related

Does declare -u in bash also make variables locally scoped?

I'm mystified by the output of this example script. It seems as if some_other_var is being implicitly declared as local when I declare it as uppercase (declare -u):
#!/bin/bash
function works() {
some_var="${1}"
printf "in the function, some_var is ${some_var}\n"
}
function doesnt_work() {
declare -u some_other_var="${1}"
printf "in the function, some_other_var is ${some_other_var}\n"
}
works "apple"
printf "got back this value of some_var from the function: ${some_var}\n"
doesnt_work "banana"
printf "got back this value of some_other_var from the function: ${some_other_var}\n"
Output:
in the function, some_var is apple
got back this value of some_var from the function: apple
in the function, some_other_var is BANANA
got back this value of some_other_var from the function:
declare -u is supposed to make the variable uppercase-only, which it does. But is it supposed to also make a variable locally scoped? I was under the impression that one had to use the 'local' directive to explicitly mark a variable as local, otherwise it was global, but declare -u seems to behave differently.
This is:
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
That's a general property of declare: when used in a function, it implies local scope. To use global scope, the -g flag (Bash 4.2+) has to be used.
See the manual:
When used in a function, declare makes each name local, as with the local command, unless the -g option is used.

How do I pass a command parameter in a variable holding the command?

I want to produce the same output as this:
bash utilities.bash "is_net_connected"
But I don't know how to pass "is_net_connected" if command and file is stored in a variable like this:
T=$(bash utilities.bash)
I've tried these but it doesn't seem to work. It's not picking up ${1} in utilities.bash.
$(T) "is_net_connected"
$(T "is_net_connected")
Not the best way to inport but I'm trying to avoid cluttering my main script with function blocks.
T=$(bash utilities.bash) doesn't save the command; it runs the command and saves its output. You want to define a function instead.
T () {
bash utilities.bash "$#"
}
# Or on one line,
# T () { bash utilities.bash "$#"; }
Now
T "is_net_connected"
will run bash utilities.bash with whatever arguments were passed to T. In a case like this, an alias would work the same: alias T='bash utilities.bash'. However, any changes to what T should do will probably require switching from an alias to a function anyway, so you may as well use the function to start. (Plus, you would have to explicitly enable alias expansion in your script.)
You might be tempted to use
T="bash utilities.bash"
$T is_net_connected
Don't be. Unquoted parameter expansions are bad practice that only work in select situations, and you will get bitten eventually if you try to use them with more complicated commands. Use a function; that's why the language supports them.

Bash functions returning values meanwhile altering global variables

I'm just struggling with bash functions, and trying to return string values meanwhile some global variable is modified inside the function. An example:
MyGlobal="some value"
function ReturnAndAlter () {
MyGlobal="changed global value"
echo "Returned string"
}
str=$(ReturnAndAlter)
echo $str # prints out 'Returned value' as expected
echo $MyGlobal # prints out the initial value, not changed
This is because $(...) (and also `...` if used instead) cause the function to have its own environment, so the global variable is never affected.
I found a very dirty workaround by returning the value into another global variable and calling the function only using its name, but think that there should be a cleaner way to do it.
My dirty solution:
MyGlobal="some value"
ret_val=""
function ReturnAndAlter () {
ret_val="Returned string"
MyGlobal="changed value"
}
ReturnAndAlter # call the bare function
str=$ret_val # and assign using the auxiliary global ret_val
echo $str
echo $MyGlobal # Here both global variables are updated.
Any new ideas? Some way of calling functions that I'm missing?
Setting global variables is the only way a function has of communicating directly with the shell that calls it. The practice of "returning" a value by capturing the standard output is a bit of a hack necessitated by the shell's semantics, which are geared towards making it easy to call other programs, not making it easy to do things in the shell itself.
So, don't worry; no, you aren't missing any cool tricks. You're doing what the shell allows you to do.
The $(…) (command expansion) is run in a sub-shell.
All changes inside the sub-shell are lost when the sub-shell close.
It is usually a bad idea to use both printing a result and changing a variable inside a function. Either make all variables or just use one printed string.
There is no other solution.

Why doesn't this custom sourcing function make my declared variable globally available?

I'm facing a very weird issue. I know I'm missing something basic but for the life of me I can't quite figure out what.
Consider these declarations in a file tmp.sh:
declare -A aa
aa[1]=hello
aa[2]=world
myfunc() {
echo exists
}
myvar=exists
I source the script as source tmp.sh and run:
myfunc
echo $myvar
echo ${aa[#]}
The output is:
exists
exists
hello world
Now I do the same thing but put the source statement in a function:
mysource() {
filename="$1"
source "$filename"
}
This time the output is:
exists
exists
What's going on here?
Add the -g option to declare. [1]
From the manual
-g create global variables when used in a shell function; otherwise ignored (by default, declare declares local scope variables when used in shell functions)
Also useful to mention from chepner's comment below
source works by executing the contents of the file exactly as if you replaced the source command with contents of the file. Even though the declare statements are not in a function in your file, they are part of the function that calls source.
[1] The -g option requires Bash 4.2 or above.
To complement 123's helpful answer:
By default, declare creates a local variable when used in a function (to put it differently: inside a function, declare by default behaves the same as local).
To create a global variable from inside a function:
Bash 4.2+:
use declare -g (e.g., declare -g foo='bar')
Older Bash versions, including 3.x:
simply assign a value to the variable (e.g., foo='bar'), do not use declare.
As an aside:
Your sample code uses declare -A to declare an associative array, which requires Bash 4.0.
Associative arrays are the only types of (non-environment) variables that strictly need a declare statement for their creation - you cannot create an associative array without declare -A, whereas you can create (non-integer-typed) scalars and arrays implicitly by simple assignment.
Thus, given that declare -g requires Bash 4.2, there is no solution to your problem if you happen to be stuck on 4.0 or 4.1.
3.x versions of Bash don't face this problem, because they don't support declare -A altogether.

Bash typeset (declare) as integer without variable

Is it possible to typeset -i (synonymous with declare -i, see a manpage or a reference) in bash without assigning to a variable?
Consider the following example:
typeset -i a=42;
foo $a;
Is it possible to achieve the same functionality without using a helper variable?
Assume foo is not editable (for example, a binary) with reasonable ease.
Put the declaration of type inside the function's body. You can use either declare or (to be more explicit) local for this:
foo() {
local -i arg=$1
....
}
No other solution is possible without modifying the function's body (or adding a wrapper which performs typechecking before passing the arguments as untyped strings), as arguments to functions (and to external commands) are passed as strings, regardless of any type declarations which may have been made beforehand.

Resources