in bash what does the -n parameter in "local -n" mean? - linux

in bash what does the -n parameter in local -n var... mean? - how does it differ from local var...
I can't find a good example / explanation for it. There are no man pages for keywords (it seems?). The closest I have found is a comment here: local: -n: invalid option - which suggests something about not using ! param expansion

Parameters to local are unfortunately not documented in help local, but in help declare:
`-n` make NAME a reference to the variable named by its value
How does it work?
#! /bin/bash
f () {
local -n x=y
y=12
x=42
echo $x $y # 42 42
}
f
You can achieve similar behaviour with the ! indirection (that's what the comment in the linked question means):
#! /bin/bash
f () {
x=y
y=12
echo ${!x} # 12
}
f

-n declares the variable to be a nameref:
A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see Bash Builtins) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced, assigned to, unset, or has its attributes modified (other than using or changing the nameref attribute itself), 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. For instance, if a variable name is passed to a shell function as its first argument, running
declare -n ref=$1
inside the function creates a nameref variable ref whose value is the variable name passed as the first argument. References and assignments to ref, and changes to its attributes, are treated as references, assignments, and attribute modifications to the variable whose name was passed as $1.
It's worth noting that namerefs and -n were added in Bash 5 (January 2019).

Related

How to create an associate array with value of a list of items in bash

I would like to create such a associated array in bash:
myarr = {
'key1' : ["command_name", "command name with arguments"],
'key2' : ["command_name", "command name with arguments"],
}
The reason I want to do the above is so that I can pass a key to the script and then do something like this:
Use the key to index into the associative array
Use some tool to check whether the application given by command_name is open
If the window is not open, lunch the application given by command name with arguments
Such a task is trivial in a popular programming language, but it doesn't seem to be as trivial in bash.
EDIT
I'd like to be able to create something like this:
declare -A array=(
[c]=("code" "code")
[e]=("dolphin" "XDG_CURRENT_DESKTOP=KDE dolphin")
[n]=("nvim" "kitty nvim")
)
Edit: take comments into account and replace now useless arrays by scalar strings.
As you want to set bash variables in the command's context we cannot execute them with "$cmd", this would not work for variable assignments. The following uses eval, which is extremely risky, especially if you do not fully control the inputs. A better solution, but more complicated, would be to use other variables for the execution environment, declare functions to limit the scope of variables and/or restore them afterwards, use eval only in last resort and only after sanitizing its parameters (printf '%q')... Anyway, you have been warned.
Storing bash commands and their arguments in variables is not recommended. But if you really need this it would be better to store the command names and the full commands in 2 different variables. They could be associative arrays or, if your bash is recent enough and supports namerefs, scalar variables named from your keys (if they are valid bash variable names).
Example where the key is stored in bash variable k, and the command is the second of your own example, plus some dummy arguments:
k="e"
# add a new command with key "$k"
declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
cmd="dolphin"
lcmd="XDG_CURRENT_DESKTOP=KDE dolphin arg1 arg2 'arg 3'"
...
# launch command with key "$k"
declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
if not_running "$cmd"; then
eval "$lcmd"
fi
Demo with key foo, command printf and arguments '%s\n' 'a b' 'c d':
$ k="foo"
$ declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
$ cmd="printf"
$ lcmd="printf '%s\n' 'a b' 'c d'"
$ eval "$lcmd"
a b
c d

Set environment variable to parent directory if not already set

I'm writing a bash script, and it needs to check if an environment variable exists, and set it to the parent directory of where the script is being run if the variable isn't already set. If it is already set, it should do nothing. What's the best way of doing this?
There are two parts. First, the parent of the current working directory is just $PWD/... Second, you can assign a value to a variable if it isn't already set with
: ${MYVAR:=$PWD/..}
The first : is the do-nothing command, but its arguments are still evaluated. The parameter expansion operator := has the side effect of setting MYVAR to the given value if it isn't already set.
Three things:
An environment variable is a globally available, in a program and it child programs. A shell variable is only available in the current shell. You asked to know whether the environment variable exists. Are you sure that is what you want? In that case you may want to do a
if set | grep ^variable_name= > /dev/null ; then
#set the variable
fi
Note that this just checks if the variable exists. If you do it very early in your script, you are almost sure that the variable is indeed an environment variable. But do you really care if it is a variable specific to your local shell or an environment variable?
Second is, do you care if the variable exists? Or do you just want it to contain the value of the parent directory? Do you need the set | grep in the example above, or is it sufficient to test that [ "$variable" != "" ] ? , as in
if [ "$variable" = "" ] ; then
# set the value to the parent-dir
fi
Third, the parent-dir is generally dirname $PWD. $PWD/.. is also the parent-dir.
So, if you do not care whether it is is an environment variable or not, and if you just want it to contain an actual directory, the code would be something like:
if [ "$variable" = "" ] ; then
variable=$(dirname "$PWD")
fi
(which is perhaps a bit more readable Chepner's answer)

Text manipulation with bash

I have bash variables defined in some file:
VAR1=....
VAR2="some value"
VAR3=....
....
How can I change the value of some variable and add one more variable in the specific line? I need to do it in the single shell script.
EDIT:
Expected output is:
VAR1=....
VAR2="another value"
VAR3=....
NEW_VAR=....
....
I believe that the following code would achieve the results you want, if I understood your question correctly:
#!/bin/bash
#change the value of a certain variable
sed -i -e 's/^VAR2=.*$/VAR2="another-value"/gi' /folder/file
#add a new variable to the variable declaration area, using one existing variable as a reference point
sed -i -e '/^VAR2=/i \NEW_VAR="another-value"' /folder/file
exit 0
That would substitute the value of a variable and add a new variable to the list using one existing variable as the reference point as to where to insert the new variable. This code would work in many Bash versions.

set default values for bash variables only if they were not previously declared

Here's my current process:
var[product]=messaging_app
var[component]=sms
var[version]=1.0.7
var[yum_location]=$product/$component/$deliverable_name
var[deliverable_name]=$product-$component-$version
# iterate on associative array indices
for default_var in "${!var[#]}" ; do
# skip variables that have been previously declared
if [[ -z ${!default_var} ]] ; then
# export each index as a variable, setting value to the value for that index in the array
export "$default_var=${var[$default_var]}"
fi
done
The core functionality I'm looking for is to set a list of default variables that will not overwrite previously declared variables.
The above code does that, but it also created the issue of these variables can now not depend on one another. This is because the ordering of the associative array's indices output from "${!var[#]}" is not always the same as the order they were declared in.
Does a simpler solution exist like:
declare --nooverwrite this=that
I haven't been able to find anything akin to that.
Also, I'm aware that this can be done with an if statement. However using a bunch of if statements would kill the readability on a script with near 100 default variables.
From 3.5.3 Shell Parameter Expansion:
${parameter:=word}
If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional parameters and special parameters may not be assigned to in this way.
So
: ${this:=that}
: needed because otherwise the shell would see ${this:=that} as a request to run, as a command, whatever that expanded to.
$ echo "$this"
$ : ${this:=that}
$ echo "$this"
that
$ this=foo
$ echo "$this"
foo
$ : ${this:=that}
$ echo "$this"
foo
You can also to this the first place you use the variable (instead of on its own) if that suits things better (but make sure that's clear because it is easy to mess that up in later edits).
$ echo "$this"
$ echo "${this:=that}"
that
$ echo "$this"
that
Doing this dynamically, however, is less easy and may require eval.

check if environment variable is already set [duplicate]

This question already has answers here:
What's a concise way to check that environment variables are set in a Unix shell script?
(14 answers)
Closed 9 years ago.
I am writing a shell script, where I have to check if environment variable is set, if not set then I have to set it. Is there any way to check in shell script, whether an environment variable is already set or not ?
The standard solution to conditionally assign a variable (whether in the environment or not) is:
: ${VAR=foo}
That will set VAR to the value "foo" only if it is unset.
To set VAR to "foo" if VAR is unset or the empty string, use:
: ${VAR:=foo}
To put VAR in the environment, follow up with:
export VAR
You can also do export VAR=${VAR-foo} or export VAR=${VAR:=foo}, but some older shells do not support the syntax of assignment and export in the same line. Also, DRY; using the name on both sides of the = operator is unnecessary repetition. (A second line exporting the variable violates the same principal, but feels better.)
Note that it is very difficult in general to determine if a variable is in the environment. Parsing the output of env will not work. Consider:
export foo='
VAR=var-value'
env | grep VAR
Nor does it work to spawn a subshell and test:
sh -c 'echo $VAR'
That would indicate the VAR is set in the subshell, which would be an indicator that VAR is in the environment of the current process, but it may simply be that VAR is set in the initialization of the subshell. Functionally, however, the result is the same as if VAR is in the environment. Fortunately, you do not usually care if VAR is in the environment or not. If you need it there, put it there. If you need it out, take it out.
[ -z "$VARIABLE" ] && VARIABLE="abc"
if env | grep -q ^VARIABLE=
then
echo env variable is already exported
else
echo env variable was not exported, but now it is
export VARIABLE
fi
I want to stress that [ -z $VARIABLE ] is not enough, because you can have VARIABLE but it was not exported. That means that it is not an environment variable at all.
What you want to do is native in bash, it is called parameter substitution:
VARIABLE="${VARIABLE:=abc}"
If VARIABLE is not set, right hand side will be equal to abc. Note that the internal operator := may be replaced with :- which tests if VARIABLE is not set or empty.
if [ -z "$VARIABLE" ]; then
VARIABLE=...
fi
This checks if the length of $VARIABLE is zero.

Resources