how to differentiate between function arguments and script arguments - bash

lets say I have a script called hello
$ cat hello
function1 () {
echo $1
}
function1 what
echo $1
and I call
$ sh hello chicken
what
chicken
How do i refer to the script parameters (chicken) inside the function. Would I have to rename all the script arguments or store them somewhere else? Whats the best way to handle this?

This is a case of shadowing, you can find information about it below
https://www.gnu.org/software/bash/manual/html_node/Shell-Functions.html
If you try to picture it, the inner scope variable casts a "shadow" over the outer scope variable and hides it from view. As soon as the inner scope variable is gone, the program can again "find" the outer scope variable.
It's pretty much another variation of a general rule in programming where things that are more specific or refer to an inner scope, override things that are more generic or part of an outer scope.
If you wrote
temp="hi"
phrase(){
echo "$temp"
temp="hello"
echo "$temp"
}
phrase
The result would be
hi
hello
because the variable of the inner scope "overshadows" the variable of the outer scope.
That can be prevented by storing your script's $1 parameter using another name.
So, as you said, the best approach is to make sure all variables have different names by storing your script parameters inside distinctly named variables.
temp=$1
function1 () {
echo "$1"
echo "$temp"
}
function1 what
echo "$1"
Edit: I forgot to account for the fact that script variables are not available directly inside functions like #gordondavisson said, so even if you weren't passing the word "what" as a parameter to your function, you still wouldn't be able to print the word "chicken".
So, in this case, the only possible way to use the parameter inside the function would be to assign $1 to a variable.

Related

Bash local variable scope best practice

I've seen that some people when writing bash script they define local variables inside an if else statement like example 1
Example 1:
#!/bin/bash
function ok() {
local animal
if [ ${A} ]; then
animal="zebra"
fi
echo "$animal"
}
A=true
ok
For another example, this is the same:
Example 2:
#!/bin/bash
function ok() {
if [ ${A} ]; then
local animal
animal="zebra"
fi
echo "$animal"
}
A=true
ok
So, the example above printed the same result but which one is the best practice to follow. I prefer the example 2 but I've seen a lot people declaring local variable inside a function like example 1. Would it be better to declare all local variables on top like below:
function ok() {
# all local variable declaration must be here
# Next statement
}
the best practice to follow
Check your scripts with https://shellcheck.net .
Quote variable expansions. Don't $var, do "$var". https://mywiki.wooledge.org/Quotes
For script local variables, prefer to use lowercase variable names. For exported variables, use upper case and unique variable names.
Do not use function name(). Use name(). https://wiki.bash-hackers.org/scripting/obsolete
Document the usage of global variables a=true. Or add local before using variables local a; then a=true. https://google.github.io/styleguide/shellguide.html#s4.2-function-comments
scope best practice
Generally, use the smallest scope possible. Keep stuff close to each other. Put local close to the variable usage. (This is like the rule from C or C++, to define a variable close to its usage, but unlike in C or C++, in shell declaration and assignment should be on separate lines).
Note that your examples are not the same. In the case variable A (or a) is an empty string, the first version will print an empty line (the local animal variable is empty), the second version will print the value of the global variable animal (there was no local). Although the scope should be as smallest, animal is used outside of if - so local should also be outside.
The local command constrains the variables declared to the function scope.
With that said, you can deduce that doing so inside an if block will be the same as if you did outside of it, as long as it's inside of a function.

Delayed expansion of composite variable in Bash

I'm defining a variable as a composition of other variables and some text, and I'm trying to get this variable to not expand its containing variables on the assigning. But I want it to expand when called later. That way I could reuse the same template to print different results as the inner variables keep changing. I'm truing to avoid eval as much as possible as I will be receiving some of the inner variables from third parties, and I do not know what to expect.
My use case, as below, is to have some "calling stack" so I can log all messages with the same format and keep a record of the script, function, and line of the logged message in some format like this: script.sh:this_function:42.
My attempted solution
called.sh:
#!/bin/bash
SCRIPT_NAME="`basename "${BASH_SOURCE[0]}"`"
CURR_STACK="${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
echo
function _func_1 {
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
}
_func_1
So, I intend to get the same results while printing the "${CURR_STACK}" as when printing the previous line.
If there is some built-in or other clever way to log this 'call stack', by all means, let me know! I'll gladly wave my code good-bye, but I'd still like to know how to prevent the variables from expanding right away on the assigning of CURR_STACK, but still keep them able to expand further ahead.
Am I missing some shopt?
What I've tried:
Case 1 (expanding on line 4):
CURR_STACK="${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}"
CURR_STACK="`echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"`"
CURR_STACK="`echo "\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}"`"
called.sh::7 <------------------| These are control lines
called.sh::4 <---------------. .------------| With the results I expect to get.
X
called.sh:_func_1:12 <---´ `-------| Both indicate that the values expanded
called.sh::4 <-------------------------| on line 4 - when CURR_STACK was set.
Case 2 (not expanding at all):
CURR_STACK="\${SCRIPT_NAME}:\${FUNNAME[0]}:\${LINENO[0]}"
CURR_STACK=\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}
CURR_STACK="`echo '${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}'`"
called.sh::7
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <-------.----| No expansion at all!...
/
called.sh::12 /
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <----´
Shell variables are store plain inert text(*), not executable code; there isn't really any concept of delayed evaluation here. To make something that does something when used, create a function instead of a variable:
print_curr_stack() {
echo "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
echo "We are now at $(print_curr_stack)"
# Or just run it directly:
print_curr_stack
Note: using BASH_SOURCE[1] and FUNCNAME[1] gets info about context the function was run from, rather than where it is in the function itself. But for some reason I'm not clear on, BASH_LINENO[1] gets the wrong info, and BASH_LINENO[0] is what you want.
You could also write it to allow the caller to specify additional text to print:
print_curr_stack() {
echo "$#" "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
print_curr_stack "We are now at"
(* There's an exception to what I said about variables just contain inert text: some variables -- like $LINENO, $RANDOM, etc -- are handled specially by the shell itself. But you can't create new ones like this except by modifying the shell itself.)
Are you familiar with eval?
$ a=this; b=is; c=a; d=test;
$ e='echo "$a $b $c $d"';
$ eval $e;
this is a test
$ b='is NOT'; # modify one of the variables
$ eval $e;
this is NOT a test
$ f=$(eval $e); # capture the value of the "eval" statement
$ echo $f;
this is NOT a test

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.

Local variable scope when one function calls another inside bash shell

# ! /bin/sh
function pqr()
{
# This prints value to 10 even though variable is local inside a
echo "Displaying value of var a $a"
}
function abc()
{
local a=10
# call function pqr and don't pass value of a
pqr
}
Even though I don't pass variable a to pqr() function I get a=10 inside pqr(). My question is is scope and visibility of a is same inside pqr() as that of abc() ?Is this because we are calling pqr() from function abc()?I was expecting new variable would get created inside pqr and will display blank value.(As this is how variable scope and visibility works inside modern languages so I am curious how this works inside bash )
I understood that In the above example If I re declare a inside pqr() then new variable will get created and hence displaying blank value. Thanks in advance!!!
As mentioned in the comments (from man bash):
When local is used within a function, it causes the variable name to have a visible scope restricted to that function and its children.
So calling pqr from within abc means that the variable $a is visible inside both functions.
It's worth mentioning that since you're using bash-specific features such as local and the non-portable function syntax, you should change your shebang to #!/bin/bash.

how to access an automatically named variable in a Bash shell script

I have some code that creates a variable of some name automatically and assigns some value to it. The code is something like the following:
myVariableName="zappo"
eval "${myVariableName}=zappo_value"
How would I access the value of this variable using the automatically generated name of the variable? So, I'm looking for some code a bit like the following (but working):
eval "echo ${${myVariableName}}"
(... which may be used in something such as myVariableValue="$(eval "echo ${${myVariableName}}")"...).
Thanks muchly for any assistance
If you think this approach is madness and want offer more general advice, the general idea I'm working on is having variables defined in functions in a library with such names as ${usage} and ${prerequisiteFunctions}. These variables that are defined within functions would be accessed by an interrogation function that can, for instance, ensure that prerequisites etc. are installed. So a loop within this interrogation function is something like this:
for currentFunction in ${functionList}; do
echo "function: ${currentFunction}"
${currentFunction} -interrogate # (This puts the function variables into memory.)
currentInterrogationVariables="${interrogationVariables}" # The variable interrogationVariables contains a list of all function variables available for interrogation.
for currentInterrogationVariable in ${currentInterrogationVariables}; do
echo "content of ${currentInterrogationVariable}:"
eval "echo ${${currentInterrogationVariable}}"
done
done
Thanks again for any ideas!
IIRC, indirection in bash is by !, so try ${!myVariableName}
Try:
echo ${!myVariableName}
It will echo the variable who's name is contained in $myVariableName
For example:
#!/bin/bash
VAR1="ONE"
VAR2="TWO"
VARx="VAR1"
echo ${VARx} # prints "VAR1"
echo ${!VARx} # prints "ONE"

Resources