BASH : Problem regarding hash table/dictionary and bad substitution - bash

Context:
I started writing this script for easily changing connections for my raspberry pi zero (Raspibian Lite as OS), this is because I always needed to edit the wpa-supplicant config file and decided to do something about it as it is a really portable pc.
How it works:
The core of the program is to create profiles in format of dictionaries to store the name and passwd and apply that profile when needed. The profiles are added to the script code itself. I made it like this, every time a new profile is created this 2 lines are generated with the profile name corresponded. For example:
declare -A profile1
profile1=( ["name"]="name" ["pass"]="pass")
Problem:
To apply that profile I put at terminal prompt "./script --use profile1" so my goal is that it gets the details of the profile desired.
When I write that by :
echo "${$2[name]}" it outputs me a " bad substitution" error.
Things I tried and checked:
Shebang is #!/bin/bash
I tried substituting the $2 in a string and trying to execute it but I dont get anything good.
Things to consider:
Here is the link to the script so you can test it yourself, there are some things are a bit more complex than the thing indicated in the post but I just simplified it.
https://github.com/gugeldot23/wpa_scrip

You need nameref variables if you want to address the profile array name by reference:
declare -n profile # nameref variable profile
profile="$2" # Fills-in the nameref from argument 2
# Address the nameref instead of echo "${$2[name]}"
echo "${profile[name]}"
See: gnu.org Bash Manual / Bash Builtins / declare:
-n
Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except for those using or changing the -n attribute itself, are performed on the variable referenced by name’s value. The nameref attribute cannot be applied to array variables.

Related

Use bash variable to access command arguments? [duplicate]

This question already has answers here:
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
Let's say I have a variable's name stored in another variable:
myvar=123
varname=myvar
Now, I'd like to get 123 by just using $varname variable.
Is there a direct way for that? I found no such bash builtin for lookup by name, so came up with this:
function var { v="\$$1"; eval "echo "$v; }
so
var $varname # gives 123
Which doesn't look too bad in the end, but I'm wondering if I missed something more obvious.
From the man page of bash:
${!varname}
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.
There isn't a direct Posix-conforming syntax, only a bashism. I usually do this:
eval t="\$$varname"
This will work on any Posix shell, including those systems where bash is the login shell and /bin/sh is something smaller and faster like ash. I like bash and use it for my login shell but I avoid bashisms in command files.
Note: One problem with writing bash-specific scripts is that even if you can count on bash being installed, it could be anywhere on the path. It might be a good idea in that case to use the fully general /usr/bin/env shebang style, but note that this is still not 100% portable and has security issues.
${!varname} should do the trick
$ var="content"
$ myvar=var
$ echo ${!myvar}
content
I usually look at Advance Bash-Scripting Guide when I need to freshen up my Bash skills.
Regarding your question look at Indirect References
Notation is:
Version < 2
\$$var
Version >= 2
${!varname}
# bmuSetIndirectVar()
# TO DOUBLE CHECK THIS COMMENT AND DEMO
# This function is an helper to read indirect variables.
# i.e. get the content of a variable whose name is saved
# within an other variable. Like:
# MYDIR="/tmp"
# WHICHDIR="MYDIR"
# bmuSetIndirectVar "WHICHDIR" "$MYDIR"
#
bmuSetIndirectVar(){
tmpVarName=$1
locVarName=$1
extVarName=$2
#echo "debug Ind Input >$1< >$2<"
eval tmpVarName=\$$extVarName
#echo "debug Ind Output >$tmpVarName< >$extVarName<"
export $locVarName="${tmpVarName}"
}
I am currently using this little function. I am not fully happy with it, and I have seen different solutions on the web (if I could recall I would write them here), but it seems to work. Within these few lines there is already some redundancy and extra data but it was helpful for debugging.
If you want to see it in place, i.e. where I am using it, check:
https://github.com/mariotti/bmu/blob/master/bin/backmeup.shellfunctions.sh
Of course it is not the best solution, but made me going on with the work, in
the hope I can replace it with something a bit more general soon.

Creating a list of variables and calling them later bash

I want to create a list of variables,so later i can do a prompts for users and if their input is matching any variable within that list, I want to use that variable to execute it within a command, for example:
var1=a
var2=b
...
read input
(user chooses var1) command $var1,rest of the command
The biggest problem is that this list will be huge, what would be the best solution? Thanks!
You are looking for the associative array feature in bash 4 or later.
declare -A foo
foo[us]="United States"
foo[uk]="Great Britain"
read -p "Region? " region
echo "${foo[$region]}"
If the value of $region is not a defined key, then ${foo[$region]} will be treated like any unset variable.

Why is this Bash script not inheriting all environment variables?

I'm trying something very straightforward:
PEOPLE=(
"nick"
"bob"
)
export PEOPLE="$(IFS=, ; echo "${PEOPLE[*]}")"
echo "$PEOPLE" # prints 'nick,bob'
./process-people.sh
For some reason, process-people.sh isn't seeing $PEOPLE. As in, if I echo "$PEOPLE" from inside process-people.sh, it prints an empty line.
From what I understand, the child process created by invoking ./process-people.sh should inherit all the parent process's environment variables, including $PEOPLE.
Yet, I've tried this on both Bash 3.2.57(1)-release and 4.2.46(2)-release and it doesn't work.
What's going on here?
That's a neat solution you have there for joining the elements of a Bash array into a string. Did you know that in Bash you cannot export array variables to the environment? And if a variable is not in the environment, then the child process will not see it.
Ah. But you aren't exporting an array, are you. You're converting the array into a string and then exporting that. So it should work.
But this is Bash! Where various accidents of history conspire to give you the finger.
As #PesaThe and #chepner pointed out in the comments below, you cannot actually convert a Bash array variable to a string variable. According to the Bash reference on arrays:
Referencing an array variable without a subscript is equivalent to referencing with a subscript of 0.
So when you call export PEOPLE=... where PEOPLE was previously assigned an array value, what you're actually doing is PEOPLE[0]=.... Here's a fuller example:
PEOPLE=(
"nick"
"bob"
)
export PEOPLE="super"
echo "$PEOPLE" # masks the fact that PEOPLE is still an array and just prints 'super'
echo "${PEOPLE[*]}" # prints 'super bob'
It's unfortunate that the export silently fails to export the array to the environment (it returns 0), and it's confusing that Bash equates ARRAY_VARIABLE to ARRAY_VARIABLE[0] in certain situations. We'll just have to chalk that up to a combination of history and backwards compatibility.
Here's a working solution to your problem:
PEOPLE_ARRAY=(
"nick"
"bob"
)
export PEOPLE="$(IFS=, ; echo "${PEOPLE_ARRAY[*]}")"
echo "$PEOPLE" # prints 'nick,bob'
./process-people.sh
The key here is to assign the array and derived string to different variables. Since PEOPLE is a proper string variable, it will export just fine and process-people.sh will work as expected.
It's not possible to directly change a Bash array variable into a string variable. Once a variable is assigned an array value, it becomes an array variable. The only way to change it back to a string variable is to destroy it with unset and recreate it.
Bash has a couple of handy commands for inspecting variables that are useful for investigating these kinds of issues:
printenv PEOPLE # prints 'nick,bob'
declare -p PEOPLE_ARRAY # prints: declare -ax PEOPLE_ARRAY='([0]="nick" [1]="bob")'
printenv will only return a value for environment variables, vs. echo, which will print a result whether the variable has been properly exported or not.
declare -p will show the full value of a variable, without the gotchas related to including or leaving out array index references (e.g. ARRAY_VARIABLE[*]).

Lookup shell variables by name, indirectly [duplicate]

This question already has answers here:
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
Let's say I have a variable's name stored in another variable:
myvar=123
varname=myvar
Now, I'd like to get 123 by just using $varname variable.
Is there a direct way for that? I found no such bash builtin for lookup by name, so came up with this:
function var { v="\$$1"; eval "echo "$v; }
so
var $varname # gives 123
Which doesn't look too bad in the end, but I'm wondering if I missed something more obvious.
From the man page of bash:
${!varname}
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.
There isn't a direct Posix-conforming syntax, only a bashism. I usually do this:
eval t="\$$varname"
This will work on any Posix shell, including those systems where bash is the login shell and /bin/sh is something smaller and faster like ash. I like bash and use it for my login shell but I avoid bashisms in command files.
Note: One problem with writing bash-specific scripts is that even if you can count on bash being installed, it could be anywhere on the path. It might be a good idea in that case to use the fully general /usr/bin/env shebang style, but note that this is still not 100% portable and has security issues.
${!varname} should do the trick
$ var="content"
$ myvar=var
$ echo ${!myvar}
content
I usually look at Advance Bash-Scripting Guide when I need to freshen up my Bash skills.
Regarding your question look at Indirect References
Notation is:
Version < 2
\$$var
Version >= 2
${!varname}
# bmuSetIndirectVar()
# TO DOUBLE CHECK THIS COMMENT AND DEMO
# This function is an helper to read indirect variables.
# i.e. get the content of a variable whose name is saved
# within an other variable. Like:
# MYDIR="/tmp"
# WHICHDIR="MYDIR"
# bmuSetIndirectVar "WHICHDIR" "$MYDIR"
#
bmuSetIndirectVar(){
tmpVarName=$1
locVarName=$1
extVarName=$2
#echo "debug Ind Input >$1< >$2<"
eval tmpVarName=\$$extVarName
#echo "debug Ind Output >$tmpVarName< >$extVarName<"
export $locVarName="${tmpVarName}"
}
I am currently using this little function. I am not fully happy with it, and I have seen different solutions on the web (if I could recall I would write them here), but it seems to work. Within these few lines there is already some redundancy and extra data but it was helpful for debugging.
If you want to see it in place, i.e. where I am using it, check:
https://github.com/mariotti/bmu/blob/master/bin/backmeup.shellfunctions.sh
Of course it is not the best solution, but made me going on with the work, in
the hope I can replace it with something a bit more general soon.

ls command in UNIX

I have to ls command to get the details of certain types of files. The file name has a specific format. The first two words followed by the date on which the file was generated
e.g.:
Report_execution_032916.pdf
Report_execution_033016.pdf
Word summary can also come in place of report.
e.g.:
Summary_execution_032916.pdf
Hence in my shell script I put these line of codes
DATE=`date +%m%d%y`
Model=Report
file=`ls ${Model}_execution_*${DATE}_*.pdf`
But the value of Model always gets resolved to 'REPORT' and hence I get:
ls: cannot access REPORT_execution_*032916_*.pdf: No such file or directory
I am stuck at how the resolution of Model is happening here.
I can't reproduce the exact code here. Hence I have changed some variable names. Initially I had used the variable name type instead of Model. But Model is the on which I use in my actual code
You've changed your script to use Model=Report and ${Model} and you've said you have typeset -u Model in your script. The -u option to the typeset command (instead of declare — they're synonyms) means "convert the strings assigned to all upper-case".
-u When the variable is assigned a value, all lower-case characters are converted to upper-case. The lower-case attribute is disabled.
That explains the upper-case REPORT in the variable expansion. You can demonstrate by writing:
Model=Report
echo "Model=[${Model}]"
It would echo Model=[REPORT] because of the typeset -u Model.
Don't use the -u option if you don't want it.
You should probably fix your glob expression too:
file=$(ls ${Model}_execution_*${DATE}*.pdf)
Using $(…) instead of backticks is generally a good idea.
And, as a general point, learn how to Debug a Bash Script and always provide an MCVE (How to create a Minimal, Complete, and Verifiable Example?) so that we can see what your problem is more easily.
Some things to look at:
type is usually a reserved word, though it won't break your script, I suggest you to change that variable name to something else.
You are missing an $ before {DATE}, and you have an extra _ after it. If the date is the last part of the name, then there's no point in having an * at the end either. The file definition should be:
file=`ls ${type}_execution_*${DATE}.pdf`
Try debugging your code by parts: instead of doing an ls, do an echo of each variable, see what comes out, and trace the problem back to its origin.
As #DevSolar pointed out you may have problems parsing the output of ls.
As a workaround
ls | grep `date +%m%d%y` | grep "_execution_" | grep -E 'Report|Summary'
filters the ls output afterwards.
touch 'Summary_execution_032916.pdf'
DATE=`date +%m%d%y`
Model=Summary
file=`ls ${Model}_execution_*${DATE}*.pdf`
worked just fine on
GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu)
Part of question:
But the value of Model always gets resolved to 'REPORT' and hence I get:
This is due to the fact that in your script you have exported Model=Report
Part of question:
ls: cannot access REPORT_execution_*032916_*.pdf: No such file or directory
No such file our directory issue is due to the additional "_" and additional "*"s that you have put in your 3rd line.
Remove it and the error will be gone. Though, Model will still resolve to Report
Original 3rd line :
file=`ls ${Model}_execution_*${DATE}_*.pdf`
Change it to
file=`ls ${Model}_execution_${DATE}.pdf`
Above change will resolve the could not found issue.
Part of question
I am stuck at how the resolution of Model is happening here.
I am not sure what you are trying to achieve, but if you are trying to populate the file parameter with file name with anything_exection_someDate.pdf, then you can write your script as
DATE=`date +%m%d%y`
file=`ls *_execution_${DATE}.pdf`
If you echo the value of file you will get
Report_execution_032916.pdf Summary_execution_032916.pdf
as the answer
There were some other scripts which were invoked before the control reaches the line of codes which I mentioned in the question. In one such script there is a code
typeset -u Model
This sets the value of the variable model always to uppercase which was the reason this error was thrown
ls: cannot access REPORT_execution_032916_.pdf: No such file or directory
I am sorry that
i couldn't provide a minimal,complete and verifiable code

Resources