Can a function be extended in Bash? - bash

If I define a function in a file, say test1.sh:
#!/bin/bash
foo() {
echo "foo"
}
And in a second, test2.sh, I try to redefine foo:
#!/bin/bash
source /path/to/test1.sh
...
foo() {
...
echo "bar"
}
foo
Is there a way to change test2.sh to produce:
foo
bar
I know it is possible to do with Bash built-ins using command, but I want to know if it is possible to extend a user function?

I don't know of a nice way of doing it (but I'd love to be proven wrong).
Here's an ugly way:
# test2.sh
# ..
eval 'foo() {
'"$(declare -f foo | tail -n+2)"'
echo bar
}'

I'm not sure I see the need to do something like this. You can use functions inside of functions, so why reuse the name when you can just call the original sourced function in a newly created function like this:
AirBoxOmega:stack d$ cat source.file
#!/usr/local/bin/bash
foo() {
echo "foo"
}
AirBoxOmega:stack d$ cat subfoo.sh
#!/usr/local/bin/bash
source /Users/d/stack/source.file
sub_foo() {
foo
echo "bar"
}
sub_foo
AirBoxOmega:stack d$ ./subfoo.sh
foo
bar
Of course if you REALLY have your heart set on modifying, you could source your function inside the new function, call it, and then do something esle after, like this:
AirBoxOmega:stack d$ cat source.file
#!/usr/local/bin/bash
foo() {
echo "foo"
}
AirBoxOmega:stack d$ cat modify.sh
#!/usr/local/bin/bash
foo() {
source /Users/d/stack/source.file
foo
echo "bar"
}
foo
AirBoxOmega:stack d$ ./modify.sh
foo
bar

No it's not possible. A new declaration would override the previous instance of a function. But despite not having that capability it's still helpful when you want to disable a function without having to unset it like:
foo() {
: ## Do nothing.
}
It's also helpful with lazy initializations:
foo() {
# Do initializations.
...
# New declaration.
if <something>; then
foo() {
....
}
else
foo() {
....
}
fi
# Call normally.
foo "$#"
}
And if you're brave and capable enough to use eval, you can even optimize your function so it would act without additional ifs based on a condition.

Yes you can,
see this page : https://mharrison.org/post/bashfunctionoverride/
save_function() {
local ORIG_FUNC=$(declare -f $1)
local NEWNAME_FUNC="$2${ORIG_FUNC#$1}"
eval "$NEWNAME_FUNC"
}
save_function foo old_foo
foo() {
initialization_code()
old_foo()
cleanup_code()
}

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.

Bash. Inherit functions scopes

уI need a global counter and function which returns numbers one by one. For example I want this script to echo 6,7,8 (but it echo 6,6,6):
#!/bin/bash
port_counter=5
function get_free_port {
port_counter=$((port_counter + 1))
echo ${port_counter}
}
function foo {
echo $(get_free_port)
}
foo
foo
(foo;)&
How can I obtain 6,7,8?
UPDATE:
Ok, after chepner's answer I need to specify a little my question.
If I need to use get_free_port as variable in foo, I can't use this approach, isn't it?
So I can't write
function foo {
variable=get_free_port # variable=$(get_free_port) was ok, but returns 6,6,6
echo ${variable}
}
Also foo & - like usages is hardly desirable
You can't modify variables from a subprocess (which is what $(...) runs). You don't need one in this case:
function foo {
get_free_port
}
However, for the same reason, you cannot call foo from a subshell or background job, either. Neither foo &, (foo), nor (foo)& will update the value of port_counter in the current shell.
If you really need to call get_free_port and capture its output, you'll need to use a temporary file. For example:
foo () {
get_free_port > some_temp_file
cat some_temp_file
}
If this is not suitable, you may need to rethink your script's design.
The below code would give you the desired behavior:
#!/bin/bash
port_counter=5
function get_free_port {
port_counter=$(( port_counter + 1 ))
echo ${port_counter}
}
function foo {
get_free_port
# $(get_free_port) spawns a subshell and the parent shell variables are not
# available in the subshell.
}
foo #runs fine
foo #runs fine
foo #(foo;)& again spawns a subshell and the parent shell pariables are not available here.

Bash expand array whose name is contained in a function parameter

I would like to render this code more natural:
#!/bin/bash
declare -ra dev_monikers=(foo bar baz)
declare -ra starwars_expletives=(farkled kark crink)
function f() {
local -r weird_expansion="$1[#]"
local -ra words_to_use=(${!weird_expansion})
echo "${words_to_use[#]}"
}
f dev_monikers
echo ---
f starwars_expletives
Specifically: it seems very odd indeed to use that weird_expansion auxiliary variable. Is there a simpler way to do this in Bash?
Output from the above:
foo bar baz
---
farkled kark crink
Things I have tried which do not work:
words_to_use=(${${1}[#]}) # error
words_to_use=(${$1[#]}) # error
words_to_use=(${!$1[#]}) # error
words_to_use=(${!1[#]}) # error
words_to_use=(${!${1}[#]}) # error
words_to_use=(${${!1}[#]} # error
Making, in comparison, the initial absurdity look less unreasonable..
Try this:
function f() {
eval "echo \${$1[#]}"
}
This isn't necessarily any more pretty, but does it without the auxiliary variable:
function f() {
local -a words_to_use=($(eval echo \$\{${1}[#]\}))
echo ${words_to_use[#]}
}

Get function name in KornShell script

I'd like to get the function name from within the function, for logging purposes.
KornShell (ksh) function:
foo ()
{
echo "get_function_name some useful output"
}
Is there anything similar to $0, which returns the script name within scripts, but which instead provides a function's name?
If you define the function with the function keyword, then $0 is the function name:
$ function foo {
> echo "$0"
> }
$ foo
foo
(Tested in pdksh.)
[...] what are the main pros/cons of using keyword function?
Main pro is that "typeset myvar=abc" inside the function is now a local variable, with no possible side effects outside the function. This makes KSH noticeably safer for large shell scripts. Main con is, perhaps, the non-POSIX syntax.
Use the ksh "function foo ..." form:
$ cat foo1
#!/bin/ksh
foo3() { echo "\$0=$0"; }
function foo2 { echo "\$0=$0"; }
foo2
foo3
$ ./foo1
$0=foo2
$0=./foo1
The function below seems to get its name in both Bash and ksh:
# ksh or bash
function foo {
local myname="${FUNCNAME[0]:-$0}"
echo "$myname"
}
# test
foo
# ...

idioms for returning multiple values in shell scripting

Are there any idioms for returning multiple values from a bash function within a script?
http://tldp.org/LDP/abs/html/assortedtips.html describes how to echo multiple values and process the results (e.g., example 35-17), but that gets tricky if some of the returned values are strings with spaces in.
A more structured way to return would be to assign to global variables, like
foo () {
FOO_RV1="bob"
FOO_RV2="bill"
}
foo
echo "foo returned ${FOO_RV1} and ${FOO_RV2}"
I realize that if I need re-entrancy in a shell script I'm probably doing it wrong, but I still feel very uncomfortable throwing global variables around just to hold return values.
Is there a better way? I would prefer portability, but it's probably not a real limitation if I have to specify #!/bin/bash.
In the special case where your values never contain spaces, this read trick can be a simple solution:
get_vars () {
#...
echo "value1" "value2"
}
read var1 var2 < <(get_vars)
echo "var1='$var1', var2='$var2'"
But of course, it breaks as soon as there is a space in one of the values. You could modify IFS and use a special separator in your function's echo, but then the result is not really simpler than the other suggested solutions.
This question was posted 5 years ago, but I have some interesting answer to post. I have just started learning bash, and I also encounter to the same problem as you did. I think this trick might be helpful:
#!/bin/sh
foo=""
bar=""
my_func(){
echo 'foo="a"; bar="b"'
}
eval $(my_func)
echo $foo $bar
# result: a b
This trick is also useful for solving a problem when a child process can not send back a value to its parent process.
Much as I love shell, it's probably the case that as soon as you're throwing arbitrary structured data around, Unix bourne/posix shell is not the right choice.
If there are characters which do not occur inside fields, then separate with one of those. The classic example is /etc/passwd, /etc/group and various other files which use a colon as a field separator.
If using a shell which can handle a NUL character inside strings, then joining on the NUL and separating on it (via $IFS or whatever) can work well. But several common shells, including bash, break on NUL. A test would be an old .sig of mine:
foo=$'a\0b'; [ ${#foo} -eq 3 ] && echo "$0 rocks"
Even if that would work for you, you've just reached one of the warning signs that it's time to switch to a more structured language (Python, Perl, Ruby, Lua, Javascript ... pick your preferred poison). Your code is likely to become hard to maintain; even if you can, there's a smaller pool of people who'll understand it well enough to maintain it.
Yet another way:
function get_tuple()
{
echo -e "Value1\nValue2"
}
IFS=$'\n' read -d '' -ra VALUES < <(get_tuple)
echo "${VALUES[0]}" # Value1
echo "${VALUES[1]}" # Value2
In order version of Bash which doesn't support nameref (introduced in Bash 4.3-alpha) I may define helper function in which the return value is assigned to the given variable. It's sort of like using eval to do the same kind of variable assignment.
Example 1
## Add two complex numbers and returns it.
## re: real part, im: imaginary part.
##
## Helper function named by the 5th positional parameter
## have to have been defined before the function is called.
complexAdd()
{
local re1="$1" im1="$2" re2="$3" im2="$4" fnName="$5" sumRe sumIm
sumRe=$(($re1 + $re2))
sumIm=$(($im1 + $im2))
## Call the function and return 2 values.
"$fnName" "$sumRe" "$sumIm"
}
main()
{
local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
## Define the function to receive mutiple return values
## before calling complexAdd().
retValAssign() { bazRe="$1"; bazIm="$2"; }
## Call comlexAdd() for the first time.
complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 'retValAssign'
## Redefine the function to receive mutiple return values.
retValAssign() { quxRe="$1"; quxIm="$2"; }
## Call comlexAdd() for the second time.
complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 'retValAssign'
echo "foo = $fooRe + $fooIm i"
echo "bar = $barRe + $barIm i"
echo "baz = foo + bar = $bazRe + $bazIm i"
echo "qux = bar + baz = $quxRe + $quxIm i"
}
main
Example 2
## Add two complex numbers and returns it.
## re: real part, im: imaginary part.
##
## Helper functions
## getRetRe(), getRetIm(), setRetRe() and setRetIm()
## have to have been defined before the function is called.
complexAdd()
{
local re1="$1" im1="$2" re2="$3" im2="$4"
setRetRe "$re1"
setRetRe $(($(getRetRe) + $re2))
setRetIm $(($im1 + $im2))
}
main()
{
local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
## Define getter and setter functions before calling complexAdd().
getRetRe() { echo "$bazRe"; }
getRetIm() { echo "$bazIm"; }
setRetRe() { bazRe="$1"; }
setRetIm() { bazIm="$1"; }
## Call comlexAdd() for the first time.
complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm"
## Redefine getter and setter functions.
getRetRe() { echo "$quxRe"; }
getRetIm() { echo "$quxIm"; }
setRetRe() { quxRe="$1"; }
setRetIm() { quxIm="$1"; }
## Call comlexAdd() for the second time.
complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm"
echo "foo = $fooRe + $fooIm i"
echo "bar = $barRe + $barIm i"
echo "baz = foo + bar = $bazRe + $bazIm i"
echo "qux = bar + baz = $quxRe + $quxIm i"
}
main
you can make use of associative arrays with you have bash 4 eg
declare -A ARR
function foo(){
...
ARR["foo_return_value_1"]="VAR1"
ARR["foo_return_value_2"]="VAR2"
}
you can combine them as strings.
function foo(){
...
echo "$var1|$var2|$var3"
}
then whenever you need to use those return values,
ret="$(foo)"
IFS="|"
set -- $ret
echo "var1 one is: $1"
echo "var2 one is: $2"
echo "var3 one is: $3"
I would go for the solution I suggested here, but using an array variable instead. Older bash:es don't support associative arrays.
E.g.,
function some_func() # ARRVAR args...
{
local _retvar=$1 # I use underscore to avoid clashes with return variable names
local -a _out
# ... some processing ... (_out[2]=xxx etc.)
eval $_retvar='("${_out[#]}")'
}
Calling site:
function caller()
{
local -a tuple_ret # Do not use leading '_' here.
# ...
some_func tuple_ret "arg1"
printf " %s\n" "${tuple_ret[#]}" # Print tuple members on separate lines
}
Later version of Bash supports nameref. Use declare -n var_name to give var_name the nameref attribute. nameref gives your function the ability to "pass by reference" which is commonly used in C++ functions to return multiple values. According to Bash man page:
A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced or assigned to, the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to the function.
The following are some interactive command line examples.
Example 1:
$ unset xx yy
$ xx=16
$ yy=xx
$ echo "[$xx] [$yy]"
[16] [xx]
$ declare -n yy
$ echo "[$xx] [$yy]"
[16] [16]
$ xx=80
$ echo "[$xx] [$yy]"
[80] [80]
$ yy=2016
$ echo "[$xx] [$yy]"
[2016] [2016]
$ declare +n yy # Use -n to add and +n to remove nameref attribute.
$ echo "[$xx] [$yy]"
[2016] [xx]
Example 2:
$ func()
> {
> local arg1="$1" arg2="$2"
> local -n arg3ref="$3" arg4ref="$4"
>
> echo ''
> echo 'Local variables:'
> echo " arg1='$arg1'"
> echo " arg2='$arg2'"
> echo " arg3ref='$arg3ref'"
> echo " arg4ref='$arg4ref'"
> echo ''
>
> arg1='1st value of local assignment'
> arg2='2st value of local assignment'
> arg3ref='1st return value'
> arg4ref='2nd return value'
> }
$
$ unset foo bar baz qux
$
$ foo='value of foo'
$ bar='value of bar'
$ baz='value of baz'
$ qux='value of qux'
$
$ func foo bar baz qux
Local variables:
arg1='foo'
arg2='bar'
arg3ref='value of baz'
arg4ref='value of qux'
$
$ {
> echo ''
> echo '2 values are returned after the function call:'
> echo " foo='$foo'"
> echo " bar='$bar'"
> echo " baz='$baz'"
> echo " qux='$qux'"
> }
2 values are returned after the function call:
foo='value of foo'
bar='value of bar'
baz='1st return value'
qux='2nd return value'
I am new to bash, But found this code helping.
function return_multiple_values() {
eval "$1='What is your name'"
eval "$2='my name is: BASH'"
}
return_var=''
res2=''
return_multiple_values return_var res2
echo $return_var
echo $res2
Shell script functions can only return the exit status of last command executed or the exit status of that function specified explicitly by a return statement.
To return some string one way may be this:
function fun()
{
echo "a+b"
}
var=`fun` # Invoke the function in a new child shell and capture the results
echo $var # use the stored result
This may reduce your discomfort although it adds the overhead of creation of a new shell and hence would be marginally slower.

Resources