Local variable declaration in /etc/init.d/functions - bash

On RHEL, the daemon() function in /etc/init.d/functions is defined as follows:
daemon() {
# Test syntax.
local gotbase= force= nicelevel corelimit
local pid base= user= nice= bg= pid_file=
local cgroup=
nicelevel=0
... and so on ...
I'm trying to understand why some of the local variables are defined with an equals sign and some others not. What's happening here? Is this multiple declaration and assignment?

local varname
declares a local variable, but doesn't initialize it with any value.
local varname=value
declares a local variable, and also initializes it to value. You can initialize it to an empty string by providing an empty value, as in
local varname=
So in your example, pid is declared but not initialized, while base is declared and initialized to an empty string.
For most purposes there's not much difference between an unset variable and having an empty string as the value. But some of the parameter expansion operators can distinguish them. E.g.
${varname:-default}
will expand to default if varname is unset or empty, but
${varname-default}
will expand to default only if varname is unset. So if you use
${base-default}
it will expand to the empty string, not default.

Related

The scope of local variables in sh

I've got quite a lot of headaches trying to debug my recursive function. It turns out that Dash interprets local variables strangely. Consider the following snippet:
iteration=0;
MyFunction()
{
local my_variable;
iteration=$(($iteration + 1));
if [ $iteration -lt 2 ]; then
my_variable="before recursion";
MyFunction
else
echo "The value of my_variable during recursion: '$my_variable'";
fi
}
MyFunction
In Bash, the result is:
The value of my_variable during recursion: ''
But in Dash, it is:
The value of my_variable during recursion: 'before recursion'
Looks like Dash makes the local variables available across the same function name. What is the point of this and how can I avoid issues when I don't know when and which recursive iteration changed the value of a variable?
local is not part of the POSIX specification, so bash and dash are free to implement it any way they like.
dash does not allow assignments with local, so the variable is unset unless it inherits a value from a surrounding scope. (In this case, the surrounding scope of the second iteration is the first iteration.)
bash does allow assignments (e.g., local x=3), and it always creates a variable with a default empty value unless an assignment is made.
This is a consequence of your attempt to read the variable in the inner-most invocation without having set it in there explicitly. In that case, the variable is indeed local to the function, but it inherits its initial value from the outer context (where you have it set to "before recursion").
The local marker on a variable thus only affects the value of the variable in the caller after the function invocation returned. If you set a local variable in a called function, its value will not affect the value of the same variable in the caller.
To quote the dash man page:
Variables may be declared to be local to a function by using a local command. This should appear as the first statement of a function, and the syntax is
local [variable | -] ...
Local is implemented as a builtin command.
When a variable is made local, it inherits the initial value and exported and readonly flags from the variable with the same name in the surrounding scope, if there is one. Otherwise, the variable is initially unset. The shell uses dynamic scoping, so that if you make the variable x local to function f, which then calls function g, references to the variable x made inside g will refer to the variable x declared inside f, not to the
global variable named x.
The only special parameter that can be made local is “-”. Making “-” local any shell options that are changed via the set command inside the function to be restored to their original values when the function returns.
To be sure about the value of a variable in a specific context, make sure to always set it explicitly in that context. Else, you rely on "fallback" behavior of the various shells which might be different across shells.

What is the purpose of setting a variable default to empty in bash?

In general, this syntax is used to guarantee a value, potentially a default argument.
(from the Bash reference manual)
${parameter:-word}
If parameter is unset or null, the expansion of word is substituted.
Otherwise, the value of parameter is substituted.
What would be the purpose of defaulting a variable to empty if the substitution is only chosen when the variable is empty anyway?
For reference, I'm looking at /lib/lsb/init-functions.
"Null" means the variable has a value, and this value is an empty string. The shell knows the variable exists.
"Unset" means the variable has not been defined : it does not exist as far as the shell is concerned.
In its usual mode, the shell will expand null and unset variable to an empty string. But there is a mode (set -u) that allows the shell to throw a runtime error if a variable is expanded when it is unset. It is good practice to enable this mode, because it is very easy to simply mis-type a variable name and get difficult to debug errors.
It can actually be useful from a computing perspective to differentiate between unset and empty variables, you can assign separate semantics to each case. For instance, say you have a function that may receive an argument. You may want to use a (non-null) default value if the parameter is unset, or any value passed to the function (including an empty string) if the parameter is set. You would do something like :
my_function()
{
echo "${1-DEFAULT_VALUE}"
}
Then, the two commands below would provide different outputs:
my_function # Echoes DEFAULT_VALUE
my_function "" # Echoes an empty line
There is also a type of expansion that does not differentiate between null and not set :
"${VAR:-DEFAULT_VALUE}"
They are both useful depending on what you need.
The way to test if a variable is set or not (without running the risk of a runtime error) is the following type of expansion :
"${VAR+VALUE}"
This will expand to an empty string if VAR is unset, or to VALUE if it is set (empty or with a value). Very useful when you need it.
Generally, it is helpful to:
Declare variables explicitely
set -u to prevent silent expansion failure
Explicitly handle unset variables through the appropriate expansion
This will make your scripts more reliable, and easier to debug.

Forcing Variable value from outside of the script if argument is passed

I was thinking what's the best way to force a variable value from outside the script.
Now i'm using configuration file, but i was asked to provide a way to force parameters from outside.
Actual code
source "$1.iconf"
// some code that uses variables declared and initialized in the configuration file
This is the standard case that use the variables valorized in a static way in that configuration file. I just run the script passing the configuration file name.
./myscript.sh configuration_file
Now in the config file some variables are defined:
Var1 = some value
Var2 = 42
Var3 = "foo"
what i should be able to do:
./myscript.sh configuration_file --Var1="Value defined from outside"
source "$1.iconf"
//Check if some variables are passed from run string
if [ <Passed_From_Run_String_condition> ]; then
//override variables value
//maybe some kind of arguments parsing??
fi
//some code that uses variables declared and initialized in the configuration file (and maybe overridden by passed values)
This script will first source variables stored in a configuration file passed as the first parameter to the script, then it will take as a second parameter variables that can be used to overwrite values from the configuration file or create new variables if they didn't previously exist.
foo.sh
#!/bin/bash
source "$1"
eval "typeset $2"
echo ${var1}
config
var1="foo"
run script:
./foo.sh config var1=yup
output:
yup
The value "foo" in the config file was replaced by the value passed in via the second parameter "yup". If you would like to make a variable read only, so the value can not be changed, you can use typeset -r to define a variable.

What does the following assignment of variable do

I am trying to understand a bash script. I couldn't understand a piece of code. I wasn't sure what to google for either. So I'm posting it here. What does it do?
VARIABLE=${VARIABLE:-foo}
It assigns to VARIABLE:
Whatever is in $VARIABLE if it's not unset
foo otherwise
This is sometimes called a "default" parameter:
${parameter-default}, ${parameter:-default}
If parameter not set, use default.
If VARIABLE is not set, or is set to the empty string, then it sets VARIABLE to foo.
Otherwise, it effectively leaves VARIABLE alone, by setting it to its existing value.
The colon makes it treat the empty string as if VARIABLE is not set. If you say ${VARIABLE-foo}, it expands to $VARIABLE even if VARIABLE is set the empty string. This version only expands to foo if VARIABLE is not set at all.

Global variables: Arrays not behaving like other variables

I have a PowerShell script that is made up of a main PS1 file that then loads a number of Modules. In one of those modules I define a variable $global:locationsXml and then proceed to add to it without the global flag, and it works great. I can reference it without the global flag from any other module.
However, I also define a $global:loadedDefinitions = #() array and add to it. But I have to refer to this variable with the global flag when adding to it with +=. I can reference it in any other module without the global flag, but in the creating module I need it. And that module is the same one where the xml variable works differently/correctly.
I also have a Hash Table that I define without the global flag, but in the top level script that loads all the modules, and that I can reference without the global flag from anywhere. Additionally I have tried initializing the problem array in the parent script, like the Hash Table, but still the array requires the global flag in the module that populates it. But NOT in a different module that just reads it.
All of this is currently being tested in Windows 7 and PS 2.0.
So, before I go tearing things apart I wonder; is there a known bug, where global arrays behave differently from other global variables, specifically when being written to in a module?
I guess including the global flag for writing to the few arrays I need won't be a big deal, but I would like to understand what is going on, especially if it is somehow intended behavior rather than a bug.
Edit: To clarify, this works
Script:
Define Hash Table without global specifier;
Load Module;
Call Function in Module;
Read and write Hash Table without global specifier;
And this works
Script:
Load Module;
Call Function in Module;
Initialize Array with global specifier;
Append to Array with global specifier;
Reference Array from anywhere else WITHOUT global specifier;
This doesn't
Script:
Load Module;
Call Function in Module;
Initialize Array WITH global specifier;
Append to Array without global specifier;
Reference Array from anywhere fails;
This approach, of only initializing the variable with the global specifier and then referencing without it works for other variables, but not for arrays, "seems" to be the behavior/bug I am seeing. It is doubly odd that the global specifier only needs to be used in the module where the Array is initialized, not in any other module. I have yet to verify if it is also just in the function where it is initialized, and/or just writing to the array, not reading.
When you read from variable without scope specifier, PowerShell first look for variable in current scope, then, if find nothing, go to parent scope, until it find variable or reach the global scope. When you write to variable without scope specifier, PowerShell write that variable in current scope only.
Set-StrictMode -Version Latest #To produce VariableIsUndefined error.
&{
$global:a=1
$global:a #1
$local:a # Error VariableIsUndefined.
$a #1 Refer to global, $a as no $a in current scope.
$a=2 # Create variable $a in current scope.
$global:a #1 Global variable have old value.
$local:a #2 New local variable have new value.
$a #2 Refer to local $a.
}
Calling object's methods, property's and indexer's accessors (including set accessors) only read from variable. Writing to object is a different from writing to variable.
Set-StrictMode -Version Latest #To produce VariableIsUndefined error.
&{
$global:a=1..3
$global:a-join',' #1,2,3
$local:a -join',' # Error VariableIsUndefined.
$a -join',' #1,2,3 Refer to global $a, as no $a in current scope.
$a[0]=4; # Write to object (Array) but not to variable, variable only read here.
$global:a-join',' #4,2,3 Global variable have different content now.
$local:a -join',' # And you still does not have local one.
$a -join',' #4,2,3 Refer to global $a, as no $a in current scope.
$a+=5 # In PowerShell V2 this is equivalents to $a=$a+5.
# There are two reference to $a here.
# First one refer to local $a, as it is write to variable.
# Second refer to global $a, as no $a in current scope.
# $a+5 expression create new object and you assing it to local variable.
$global:a-join',' #4,2,3 Global variable have old value.
$local:a -join',' #4,2,3,5 But now you have local variable with new value.
$a -join',' #4,2,3,5 Refer to local $a.
}
So if you want to write to global variable from non-global scope, then you have to use global scope specifier. But if you only want to read from global variable, which is not hided by local variable with same name, you may omit global scope specifier.

Resources