Assign bash function parameter new value - bash

Is it possible to first pass a parameter to the function and than change its value?
#!/bin/bash
name=old_name
echo $name #echoes "old_name"
alter () {
$1=new_name #throws error that says 'command not found'
}
alter name
echo $name #I would like to see "new_name" here

Yes, using a nameref:
alter () {
declare -n foo=$1
foo=new_name
}
See Bash FAQ 006 for more advice and warnings, as well as workarounds for versions of bash that predate nameref support (i.e., 4.2 or earlier).

You can't do it that way, unfortunately. Also, bash functions can't return values. The usual options are (1) set a global var in the function (yuck), or (2) echo the "return" value and use command substitution to call it. Something like this:
alter () {
echo "$1 but different"
}
name="Fred"
name=$(alter $name)
echo Name is now $name
returns: Name is now Fred but different

Related

How to define and declare a function name with dynamic variable in it in bash scripting

CODE with details
#!/usr/bin/bash -xv
FUNCTION_DYNAMIC
eval "function APP_$i_$j
{
`enter code here`
}"
DEFINING_FUNC_TO_CALCULATE_VALUE
APP_VAR_MKT()
{
for i in `cat ${SERVER}`
do
for j in `cat ${ZONE}`
do
shopt -s expand_aliases
alias name="APP_${i}_${j}"
declare -fp "APP_${i}_${j}"
done
done
}
MAIN
SERVER_NAME=/path/servers_file
ZONE=/path/zones_file
DECLARING FUNCTION with variable in it
APP_VAR_MKT
You don't; you pass that information as arguments:
app () {
server_name=$1
zone=$2
# ...
}
app "$SERVER_NAME" "$ZONE"
Disclaimer: Declaring functions dynamically is not the approach you should use. See chepner's answer, that is definitely the preferred way!
However, if you really want to create the name dynamically, here is another way to do it, that is a little less problematic than eval:
#!/usr/bin/env bash
SERVER_NAME=foo
ZONE=bar
shopt -s expand_aliases
alias name="APP_${SERVER_NAME}_$ZONE"
name() {
echo hello
}
declare -fp "APP_${SERVER_NAME}_${ZONE}"
The output of declare shows that APP_foo_bar has been declared:
APP_foo_bar ()
{
echo hello
}
Now, this works to some degree. You have to be very cautious if the input is not under your control. This can be potentially dangerous:
#!/usr/bin/env bash
SERVER_NAME='foo() { echo hi; }; echo ouch;'
ZONE=bar
shopt -s expand_aliases
alias name="APP_${SERVER_NAME}_$ZONE"
name() {
echo hello
}
declare -fp APP_foo
declare -fp _bar
When the right alias is used, this approach can be used to execute arbitrary code. The output of this script is:
ouch
APP_foo ()
{
echo hi
}
_bar ()
{
echo hello
}
Not only were the wrong functions declared, echo ouch got executed! Now imagine if I used rm -rf *. Using eval presents the exact same problem.
Conclusion: Don't do it :)
You should not do this, unless you have a good reason for it - functions are reusable encapsulations of code, and their names should not change normally. Also you should not use eval because it is very dangerous. So be warned.
What you can do if you absolutely must is use eval:
#!/bin/bash
eval "function APP_${SERVER_NAME}_${ZONE}
{
echo 'XXX'
}"
APP_${SERVER_NAME}_${ZONE}
The result:
XXX
As others have said, it is not a good idea to generate function (or variable) names dynamically, instead you can use an associative array in a structure sometimes called a despatch table.
The idea is that the keys of the associative array (sometimes called a 'hash', 'hash table', or dictionary) hold the names of functions. When you need a particular function you just call it. Here is a simple example:
# Statically declare each function
func1() {
echo "This is func1"
}
func2() {
echo "This is func2"
}
# Declare the array as associative
declare -A lookup
# Setup the association of dynamic name with function
lookup[APP_fred_CBD]='func1'
lookup[APP_jim_ABCD]='func2'
SERVER_NAME='fred'
ZONE='CBD'
${lookup[APP_${SERVER_NAME}_${ZONE}]}
SERVER_NAME='jim'
ZONE='ABCD'
${lookup[APP_${SERVER_NAME}_${ZONE}]}
Gives:
This is func1
This is func2
If you application does not require unique functions, you can use the same function for more than one key, and pass parameters.

Returning a Dictionary from a Bash Function

I want to have a function in bash, which create a Dictionary as a local variable. Fill the Dictionary with one element and then return this dictionary as output.
Is the following code correct?
function Dictionary_Builder ()
{
local The_Dictionary
unset The_Dictionary
declare -A The_Dictionary
The_Dictionary+=(["A_Key"]="A_Word")
return $The_Dictionary
}
How can I access to the output of the function above? Can I use the following command in bash?
The_Output_Dictionary=Dictionary_Builder()
To capture output of a command or function, use command substitution:
The_Output_Dictionary=$(Dictionary_Builder)
and output the value to return, i.e. replace return with echo. You can't easily return a structure, though, but you might try returning a string that declares it (see below).
There's no need to use local and unset in the function. declare creates a local variable inside a function unless told otherwise by -g. The newly created variable is always empty.
To add a single element to an empty variable, you can assign it directly, no + is needed:
The_Dictionary=([A_Key]=A_Word)
In other words
#!/bin/bash
Dictionary_Builder () {
declare -A The_Dictionary=([A_Key]=A_Word)
echo "([${!The_Dictionary[#]}]=${The_Dictionary[#]})"
}
declare -A The_Output_Dictionary="$(Dictionary_Builder)"
echo key: ${!The_Output_Dictionary[#]}
echo value: ${The_Output_Dictionary[#]}
For multiple keys and values, you need to loop over the dictionary:
Dictionary_Builder () {
declare -A The_Dictionary=([A_Key]=A_Word
[Second]=Third)
echo '('
for key in "${!The_Dictionary[#]}" ; do
echo "[$key]=${The_Dictionary[$key]}"
done
echo ')'
}
declare -A The_Output_Dictionary="$(Dictionary_Builder)"
for key in "${!The_Output_Dictionary[#]}" ; do
echo key: $key, value: ${The_Output_Dictionary[$key]}
done
The answer by #choroba is what I was looking for. However, my dictionary values also had white spaces in them and the above answer didn't work outright. What worked was a minor variation of the above answer.
#!/bin/bash
function Dictionary_Builder() {
declare -A dict=(['title']="Title of the song"
['artist']="Artist of the song"
['album']="Album of the song"
)
echo '('
for key in "${!dict[#]}" ; do
echo "['$key']='${dict[$key]}'"
done
echo ')'
}
declare -A Output_Dictionary="$(Dictionary_Builder)"
for key in "${!Output_Dictionary[#]}" ; do
echo "${key}: '"${Output_Dictionary[$key]}"'"
done
Note the extra single quotes on the 2nd echo line which made it possible to output values with whitespaces in them.

ksh: Defining a parameter name with another parameter's value

I have a ksh script that reads a profile script with a number of sessions defined. Each session defines its own parameters as such:
SESSION_ONE_USER=...
SESSION_ONE_PWD=...
SESSION_TWO_USER=...
...
The script gets the SESSION parameter from the command line, but I simply set it for the example.
I want to let the SESSION parameter value define part of another parameter name, that I need the value from, like:
SESSION="SESSION_ONE"
USER=${${SESSION}_USER}
PASS=${${SESSION}_PWD}
That gives me a compile error.
I also tried
GET_USER_PARAM(){
echo ${SESSION}_USER
}
echo $`GET_USER_PARAM`
But that returns $SESSION_ONE_USER
I want it to return the value of the parameter SESSION_ONE_USER instead.
Does anyone have any solutions?
This is what eval is for:
SESSION=SESSION_ONE
eval echo \$${SESSION}_USER
should display the value of $SESSION_ONE_USER.
Don't monkey with variable names, use associative arrays instead
typeset -A users
typeset -A pwd
session=SESSION_ONE
users[$session]=joe
pwd[$session]=secret
for key in "${!users[#]}"; do
echo "user for session $key is ${users[$key]}"
echo "pwd for session $key is ${pwd[$key]}"
done
Try this:
SESSION="SESSION_ONE"
SESSION_ONE_USER="foo"
SESSION_ONE_PWD="bar"
SESSION_USER=${SESSION}_USER
SESSION_PWD=${SESSION}_PWD
USER=${!SESSION_USER}
PASS=${!SESSION_PWD}
echo $USER
echo $PASS
The "!" does a level of indirection. See Shell Parameter Expansion.
If this is ksh, then this is a job for nameref
alias nameref='typeset -n'
Example Solution
function session_parameters { set -u
typeset session=${1:?session name}
nameref user=SESSION_${session}_USER
nameref pass=SESSION_${session}_PASS
print session=$session user=$user pass=$pass
}
SESSION_ONE_USER="User1"
SESSION_ONE_PASS="Pass1"
SESSION_TWO_USER="User2"
SESSION_TWO_PASS="Pass2"
for s in ONE TWO THREE; do
session_parameters $s
done
Sample output
session=ONE user=User1 pass=Pass1
session=TWO user=User2 pass=Pass2
test_session_parameters[12]: session_parameters: line 5:
SESSION_THREE_USER: parameter not set
Note the usage of set -u to force the error message on line 3.
nameref usage: (from the builtin help text)
NAME
typeset - declare or display variables with attributes
SYNOPSIS
typeset [ options ] [name[=value]...]
-n
Name reference.
The value is the name of a variable that name references. name cannot contain a ... Cannot be use with any other options.

Bash: pass a function as parameter

I need to pass a function as a parameter in Bash. For example, the following code:
function x() {
echo "Hello world"
}
function around() {
echo "before"
eval $1
echo "after"
}
around x
Should output:
before
Hello world
after
I know eval is not correct in that context but that's just an example :)
Any idea?
If you don't need anything fancy like delaying the evaluation of the function name or its arguments, you don't need eval:
function x() { echo "Hello world"; }
function around() { echo before; $1; echo after; }
around x
does what you want. You can even pass the function and its arguments this way:
function x() { echo "x(): Passed $1 and $2"; }
function around() { echo before; "$#"; echo after; }
around x 1st 2nd
prints
before
x(): Passed 1st and 2nd
after
I don't think anyone quite answered the question. He didn't ask if he could echo strings in order. Rather the author of the question wants to know if he can simulate function pointer behavior.
There are a couple of answers that are much like what I'd do, and I want to expand it with another example.
From the author:
function x() {
echo "Hello world"
}
function around() {
echo "before"
($1) <------ Only change
echo "after"
}
around x
To expand this, we will have function x echo "Hello world:$1" to show when the function execution really occurs. We will pass a string that is the name of the function "x":
function x() {
echo "Hello world:$1"
}
function around() {
echo "before"
($1 HERE) <------ Only change
echo "after"
}
around x
To describe this, the string "x" is passed to the function around() which echos "before", calls the function x (via the variable $1, the first parameter passed to around) passing the argument "HERE", finally echos after.
As another aside, this is the methodology to use variables as function names. The variables actually hold the string that is the name of the function and ($variable arg1 arg2 ...) calls the function passing the arguments. See below:
function x(){
echo $3 $1 $2 <== just rearrange the order of passed params
}
Z="x" # or just Z=x
($Z 10 20 30)
gives: 30 10 20, where we executed the function named "x" stored in variable Z and passed parameters 10 20 and 30.
Above where we reference functions by assigning variable names to the functions so we can use the variable in place of actually knowing the function name (which is similar to what you might do in a very classic function pointer situation in c for generalizing program flow but pre-selecting the function calls you will be making based on command line arguments).
In bash these are not function pointers, but variables that refer to names of functions that you later use.
there's no need to use eval
function x() {
echo "Hello world"
}
function around() {
echo "before"
var=$($1)
echo "after $var"
}
around x
You can't pass anything to a function other than strings. Process substitutions can sort of fake it. Bash tends to hold open the FIFO until a command its expanded to completes.
Here's a quick silly one
foldl() {
echo $(($(</dev/stdin)$2))
} < <(tr '\n' "$1" <$3)
# Sum 20 random ints from 0-999
foldl + 0 <(while ((n=RANDOM%999,x++<20)); do echo $n; done)
Functions can be exported, but this isn't as interesting as it first appears. I find it's mainly useful for making debugging functions accessible to scripts or other programs that run scripts.
(
id() {
"$#"
}
export -f id
exec bash -c 'echowrap() { echo "$1"; }; id echowrap hi'
)
id still only gets a string that happens to be the name of a function (automatically imported from a serialization in the environment) and its args.
Pumbaa80's comment to another answer is also good (eval $(declare -F "$1")), but its mainly useful for arrays, not functions, since they're always global. If you were to run this within a function all it would do is redefine it, so there's no effect. It can't be used to create closures or partial functions or "function instances" dependent on whatever happens to be bound in the current scope. At best this can be used to store a function definition in a string which gets redefined elsewhere - but those functions also can only be hardcoded unless of course eval is used
Basically Bash can't be used like this.
A better approach is to use local variables in your functions. The problem then becomes how do you get the result to the caller. One mechanism is to use command substitution:
function myfunc()
{
local myresult='some value'
echo "$myresult"
}
result=$(myfunc) # or result=`myfunc`
echo $result
Here the result is output to the stdout and the caller uses command substitution to capture the value in a variable. The variable can then be used as needed.
You should have something along the lines of:
function around()
{
echo 'before';
echo `$1`;
echo 'after';
}
You can then call around x
eval is likely the only way to accomplish it. The only real downside is the security aspect of it, as you need to make sure that nothing malicious gets passed in and only functions you want to get called will be called (along with checking that it doesn't have nasty characters like ';' in it as well).
So if you're the one calling the code, then eval is likely the only way to do it. Note that there are other forms of eval that would likely work too involving subcommands ($() and ``), but they're not safer and are more expensive.

Create variable from string/nameonly parameter to extract data in bash?

I want to save the variable name and its contents easily from my script.
Currently :-
LOGFILE=/root/log.txt
TEST=/file/path
echo "TEST : ${TEST}" >> ${LOGFILE}
Desired :-
LOGFILE=/root/log.txt
function save()
{
echo "$1 : $1" >> ${LOGFILE}
}
TEST=/file/path
save TEST
Obviously the above save function just saves TEST : TEST
Want I want it to save is TEST : /file/path
Can this be done? How? Many thanks in advance!
You want to use Variable Indirection. Also, don't use the function keyword, it is not POSIX and also not necessary as long as you have () at the end of your function name.
LOGFILE=/root/log.txt
save()
{
echo "$1 : ${!1}" >> ${LOGFILE}
}
TEST=/file/path
save TEST
Proof of Concept
$ TEST=foo; save(){ echo "$1 : ${!1}"; }; save TEST
TEST : foo
Yes, using indirect expansion:
echo "$1 : ${!1}"
Quoting from Bash reference manual:
The basic form of parameter expansion is ${parameter} [...] If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion
Consider using the printenv function. It does exactly what it says on the tin, prints your environment. It can also take parameters
$ printenv
SSH_AGENT_PID=2068
TERM=xterm
SHELL=/bin/bash
LANG=en_US.UTF-8
HISTCONTROL=ignoreboth
...etc
You could do printenv and then grep for any vars you know you have defined and be done in two lines, such as:
$printenv | grep "VARNAME1\|VARNAME2"
VARNAME1=foo
VARNAME2=bar

Resources