Loop Through Variables in bash and change their content [duplicate] - bash

This question already has answers here:
Indirect variable assignment in bash
(7 answers)
Iterating over variable name in bash script
(2 answers)
What is indirect expansion? What does ${!var*} mean?
(6 answers)
Closed 5 months ago.
How can I make this into a loop? I have trouble with looping through variables.
WSTATUS="exited" //example output from command
VSTATUS="running"
NSTATUS="running"
JSTATUS="running"
if [[ $WSTATUS == run* ]]; then
WSTATUS=${GREEN}$WSTATUS
else
WSTATUS=${RED}$WSTATUS
fi
if [[ $VSTATUS == run* ]]; then
VSTATUS=${GREEN}$VSTATUS
else
VSTATUS=${RED}$VSTATUS
fi
if [[ $NSTATUS == run* ]]; then
NSTATUS=${GREEN}$NSTATUS
else
NSTATUS=${RED}$NSTATUS
fi
if [[ $JSTATUS == run* ]]; then
JSTATUS=${GREEN}$JSTATUS
else
JSTATUS=${RED}$JSTATUS
fi
I have tried this:
...varibles
array=( $WSTATUS $VSTATUS $NSTATUS $JSTATUS )
for value in "${array[#]}"
do
if [[ $value == run* ]]; then
WSTATUS=${GREEN}$value
else
WSTATUS=${RED}$value
fi
done
How can i iterate through bash variables, not their content?
changing this wstatus into value does not work --> WSTATUS=${GREEN}$value

Not a loop, but a function already helps a lot:
colorme() {
if [[ "$1" == run* ]]; then
printf '%s' "${GREEN}$1"
else
printf '%s' "${RED}$1"
fi
}
WSTATUS=$(colorme "$WSTATUS")
VSTATUS=$(colorme "$VSTATUS")
NSTATUS=$(colorme "$NSTATUS")
JSTATUS=$(colorme "$JSTATUS")

You can use nameref (declare -n var in following script)
#!/usr/bin/env bash
GREEN=GREEN
RED=RED
WSTATUS="exited"
VSTATUS="running"
NSTATUS="running"
JSTATUS="running"
array=( WSTATUS VSTATUS NSTATUS JSTATUS )
declare -n var
for var in "${array[#]}"
do
[[ $var == run* ]] && prefix="${GREEN}" || prefix="${RED}"
var="$prefix$var"
done
declare -p WSTATUS VSTATUS NSTATUS JSTATUS

What you are looking for is declare and ! expansion
a=b
declare $b=12
echo $b
=>
12
That is for setting a variable whose name is computed (here from another variable)
echo ${!a}
=>
12
That is for accessing the content of a variable whose name is stored in another variable
So in your case, it may look like
WSTATUS="exited" //example output from command
VSTATUS="running"
NSTATUS="running"
JSTATUS="running"
totest=( WSTATUS VSTATUS NSTATUS JSTATUS )
for name in ${array[#]}
do
if [[ ${!name} == run* ]]; then
declare $name=${GREEN}${!name}
else
declare $name=${RED}${!name}
fi
done
Copying without any other modification your script. I have some reservation about the idea to add those, apparently green and red escape code to the variable content, rather than taking this decision at print time. I didn't really try do understand nor the test neither the action your script is taking. Even, (XY problem parenthesis) the fact that what you need is to loop through variable is questionable. knittl solution, tho it does not answer to your question, is probably a better solution to your real problem.
But well, your question was how to loop through variable. This is a way to do it.

Related

Bash script with multiline variable

Here is my code
vmname="$1"
EXCEPTLIST="desktop-01|desktop-02|desktop-03|desktop-04"
if [[ $vmname != #(${EXCEPTLIST}) ]]; then
echo "${vmname}"
else
echo "Its in the exceptlist"
fi
The above code works perfectly but my question is , the EXCEPTLIST can be a long line, say 100 server names. In that case its hard to put all that names in one line. In that situation is there any way to make the variable EXCEPTLIST to be a multiline variable ? something like as follows:
EXCEPTLIST="desktop-01|desktop-02|desktop-03| \n
desktop-04|desktop-05|desktop-06| \n
desktop-07|desktop-08"
I am not sure but was thinking of possibilities.
Apparently I would like to know the terminology of using #(${})- Is this called variable expansion or what ? Does anyone know the documentation/explain to me about how this works in bash. ?
One can declare an array if the data/string is long/large. Use IFS and printf for the format string, something like:
#!/usr/bin/env bash
exceptlist=(
desktop-01
desktop-02
desktop-03
desktop-04
desktop-05
desktop-06
)
pattern=$(IFS='|'; printf '#(%s)' "${exceptlist[*]}")
[[ "$vmname" != $pattern ]] && echo good
In that situation is there any way to make the variable EXCEPTLIST to be a multiline variable ?
With your given input/data an array is also a best option, something like:
exceptlist=(
'desktop-01|desktop-02|desktop-03'
'desktop-04|desktop-05|desktop-06'
'desktop-07|desktop-08'
)
Check what is the value of $pattern variable one way is:
declare -p pattern
Output:
declare -- pattern="#(desktop-01|desktop-02|desktop-03|desktop-04|desktop-05|desktop-06)"
Need to test/check if $vmname is an empty string too, since it will always be true.
On a side note, don't use all upper case variables for purely internal purposes.
The $(...) is called Command Substitution.
See LESS=+'/\ *Command Substitution' man bash
In addition to what was mentioned in the comments about pattern matching
See LESS=+/'(pattern-list)' man bash
See LESS=+/' *\[\[ expression' man bash
s there any way to make the variable EXCEPTLIST to be a multiline variable ?
I see no reason to use matching. Use a bash array and just compare.
exceptlist=(
desktop-01
desktop-02
desktop-03
desktop-04
desktop-05
desktop-06
)
is_in_list() {
local i
for i in "${#:2}"; do
if [[ "$1" = "$i" ]]; then
return 0
fi
done
return 1
}
if is_in_list "$vmname" "${EXCEPTLIST[#]}"; then
echo "is in exception list ${vmname}"
fi
#(${})- Is this called variable expansion or what ? Does anyone know the documentation/explain to me about how this works in bash. ?
${var} is a variable expansion.
#(...) are just characters # ( ).
From man bash in Compund commands:
[[ expression ]]
When the == and != operators are used, the string to the right of the operator is considered a pattern and matched according to the rules
described below under Pattern Matching, as if the extglob shell option were enabled. ...
From Pattern Matching in man bash:
#(pattern-list)
Matches one of the given patterns
[[ command receives the #(a|b|c) string and then matches the arguments.
There is absolutely no need to use Bash specific regex or arrays and loop for a match, if using grep for raw string on word boundary.
The exception list can be multi-line, it will work as well:
#!/usr/bin/sh
exceptlist='
desktop-01|desktop-02|desktop-03|
deskop-04|desktop-05|desktop-06|
desktop-07|deskop-08'
if printf %s "$exceptlist" | grep -qwF "$1"; then
printf '%s is in the exceptlist\n' "$1"
fi
I wouldn't bother with multiple lines of text. This is would be just fine:
EXCEPTLIST='desktop-01|desktop-02|desktop-03|'
EXCEPTLIST+='desktop-04|desktop-05|desktop-06|'
EXCEPTLIST+='desktop-07|desktop-08'
The #(...) construct is called extended globbing pattern and what it does is an extension of what you probably already know -- wildcards:
VAR='foobar'
if [[ "$VAR" == fo?b* ]]; then
echo "Yes!"
else
echo "No!"
fi
A quick walkthrough on extended globbing examples: https://www.linuxjournal.com/content/bash-extended-globbing
#!/bin/bash
set +o posix
shopt -s extglob
vmname=$1
EXCEPTLIST=(
desktop-01 desktop-02 desktop-03
...
)
if IFS='|' eval '[[ ${vmname} == #(${EXCEPTLIST[*]}) ]]'; then
...
Here's one way to load a multiline string into a variable:
fn() {
cat <<EOF
desktop-01|desktop-02|desktop-03|
desktop-04|desktop-05|desktop-06|
desktop-07|desktop-08
EOF
}
exceptlist="$(fn)"
echo $exceptlist
As to solving your specific problem, I can think of a variety of approaches.
Solution 1, since all the desktop has the same desktop-0 prefix and only differ in the last letter, we can make use of {,} or {..} expansion as follows:
vmname="$1"
found=0
for d in desktop-{01..08}
do
if [[ "$vmname" == $d ]]; then
echo "It's in the exceptlist"
found=1
break
fi
done
if (( !found )); then
echo "Not found"
fi
Solution 2, sometimes, it is good to provide a list in a maintainable clear text list. We can use a while loop and iterate through the list
vmname="$1"
found=0
while IFS= read -r d
do
if [[ "$vmname" == $d ]]; then
echo "It's in the exceptlist"
found=1
break
fi
done <<EOF
desktop-01
desktop-02
desktop-03
desktop-04
desktop-05
desktop-06
desktop-07
desktop-08
EOF
if (( !found )); then
echo "Not found"
fi
Solution 3, we can desktop the servers using regular expressions:
vmname="$1"
if [[ "$vmname" =~ ^desktop-0[1-8]$ ]]; then
echo "It's in the exceptlist"
else
echo "Not found"
fi
Solution 4, we populate an array, then iterate through an array:
vmname="$1"
exceptlist=()
exceptlist+=(desktop-01 desktop-02 desktop-03 deskop-04)
exceptlist+=(desktop-05 desktop-06 desktop-07 deskop-08)
found=0
for d in ${exceptlist[#]}
do
if [[ "$vmname" == "$d" ]]; then
echo "It's in the exceptlist"
found=1
break;
fi
done
if (( !found )); then
echo "Not found"
fi

Check ENV variables in a loop to see if they were set [duplicate]

This question already has an answer here:
Bash indirect variable referencing
(1 answer)
Closed 5 months ago.
Thought it was a simple thing, but not sure why it got messed up
I'm trying in a for-loop to check a few ENV variables and make sure they were set.
This is the current code
# Bash 4.2 ( not 4.4 )
# Verify ENV
declare -a env_vars=(
"HOME",
"PATH",
"PYTHONPATH",
"TESTNONEXISTING"
)
for evar in "${env_vars[#]}"; do
# Tried: printenv
# Tried: -v $evar
# Tried: eval
if [[ -z "${evar}" ]]; then
echo
echo "ERROR: ENV var '$evar' is missing"
echo
exit 1
fi
done
I tried many things suggested here in StackOverflow - but they don't work when used in a loop ( as a string )
An example of something that works, but it's useless ...
required_env () {
ename=$1
evalue=$2
if [[ -z "$evalue" ]]; then
echo "ENV variable '$ename' is missing"
exit 1
else
echo "Variable exists"
fi
}
required_env "HOME" $HOME
required_env "PATH" $PATH
etc ...
The problem is mainly - how to convert a string - to a real variable inside the "if" - that's what I cannot get ..
Any suggestions ?
-z tests if the string is not empty. On the first iteration evar=HOME, sp [[ -z "${evar}" ]] becomes [[ -z "HOME" ]]. You are checking if the name of the variable is not empty, not the value of the variable.
You can just check if a variable is set with -v.
[[ -v "$evar" ]]
Note that there is a difference between unset and (set and empty) and (set and not-empty). -z checks for unset or (set and empty).
how to convert a string - to a real variable inside the "if"
Use variable indirection.
[[ -z "${!evar}" ]]

How to check if array element is substring of string in Bash [duplicate]

This question already has answers here:
How to check if a string contains a substring in Bash
(29 answers)
Closed 2 years ago.
This question does not answer my question.
I have a script that checks the existence of specific environment variables and prints them with their values. Now I want to mask the values of password variables (containing SECRET, PW, PASSWORD, KEY in its name, e.g. CLIENT_SECRET) with ****.
Currently I have a script like this:
expected_env_vars=("CLIENT_ID" "CLIENT_SECRET" "BACKEND_KEY" "BACKEND_NAME")
suppress_env_vars_with_substring=("SECRET" "PASSWORD" "PW" "KEY")
for env_var in "${expected_env_vars[#]}"; do
if [[ -z "${!env_var}" ]]; then
echo "Environment variable \"$env_var\" not defined"
exit 1
else
# Perform check if an element of $suppress_env_vars_with_substring is substring of $env_var
echo "$env_var=${!env_var}...OK"
fi
done
Question
How to check if an array element is substring of a string?
You may use this script with a grep:
expected_env_vars=("CLIENT_ID" "CLIENT_SECRET" "BACKEND_KEY" "BACKEND_NAME")
suppress_env_vars_with_substring=("SECRET" "PASSWORD" "PW" "KEY")
for env_var in "${expected_env_vars[#]}"; do
if [[ -z "${!env_var}" ]]; then
echo "Environment variable \"$env_var\" not defined"
exit 1
else
# Perform check if an element of $suppress_env_vars_with_substring is substring of $env_var
printf '%s=' "$env_var"
grep -qFf <(printf '%s\n' "${suppress_env_vars_with_substring[#]}") <<< "$env_var" &&
echo '****' || echo "${!env_var}"
fi
done
It does not matter, whether or not the operands to be tested belongs to an array or not. The general form is
[[ $x == *$y* ]] && echo "$y is a substring of $x"
You can substitute for $x and $y and parameter expansion you like, including array elements.

Change variable named in argument to bash function [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
In my bash scripts, I often prompt users for y/n answers. Since I often use this several times in a single script, I'd like to have a function that checks if the user input is some variant of Yes / No, and then cleans this answer to "y" or "n". Something like this:
yesno(){
temp=""
if [[ "$1" =~ ^([Yy](es|ES)?|[Nn][Oo]?)$ ]] ; then
temp=$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/es//g' | sed 's/no//g')
break
else
echo "$1 is not a valid answer."
fi
}
I then would like to use the function as follows:
while read -p "Do you want to do this? " confirm; do # Here the user types "YES"
yesno $confirm
done
if [[ $confirm == "y" ]]; then
[do something]
fi
Basically, I want to change the value of the first argument to the value of $confirm, so that when I exit the yesno function, $confirm is either "y" or "n".
I tried using set -- "$temp" within the yesnofunction, but I can't get it to work.
You could do it by outputting the new value and overwriting the variable in the caller.
yesno() {
if [[ "$1" =~ ^([Yy](es|ES)?|[Nn][Oo]?)$ ]] ; then
local answer=${1,,}
echo "${answer::1}"
else
echo "$1 is not a valid answer." >&2
echo "$1" # output the original value
return 1 # indicate failure in case the caller cares
fi
}
confirm=$(yesno "$confirm")
However, I'd recommend a more direct approach: have the function do the prompting and looping. Move all of that repeated logic inside. Then the call site is super simple.
confirm() {
local prompt=$1
local reply
while true; do
read -p "$prompt" reply
case ${reply,,} in
y*) return 0;;
n*) return 1;;
*) echo "$reply is not a valid answer." >&2;;
esac
done
}
if confirm "Do you want to do this? "; then
# Do it.
else
# Don't do it.
fi
(${reply,,} is a bash-ism that converts $reply to lowercase.)
You could use the nameref attribute of Bash (requires Bash 4.3 or newer) as follows:
#!/bin/bash
yesno () {
# Declare arg as reference to argument provided
declare -n arg=$1
local re1='(y)(es)?'
local re2='(n)o?'
# Set to empty and return if no regex matches
[[ ${arg,,} =~ $re1 ]] || [[ ${arg,,} =~ $re2 ]] || { arg= && return; }
# Assign "y" or "n" to reference
arg=${BASH_REMATCH[1]}
}
while read -p "Prompt: " confirm; do
yesno confirm
echo "$confirm"
done
A sample test run looks like this:
Prompt: YES
y
Prompt: nOoOoOo
n
Prompt: abc
Prompt:
The expressions are anchored at the start, so yessss etc. all count as well. If this is not desired, an end anchor ($) can be added.
If neither expression matches, the string is set to empty.

Use string as bash variable name in alternative value expansion [duplicate]

This question already has answers here:
How can I look up a variable by name with #!/bin/sh (POSIX sh)?
(4 answers)
Closed 7 years ago.
How can I use the value of one variable as the name of another variable in an alternative value expansion (${var+alt}) in bash?
I would think that
#!/bin/bash
cat='dog'
varname='cat'
if [ -z ${`echo "${varname}"`+x} ]; then
echo 'is null'
fi
should be roughly equivalent to
#!/bin/bash
if [ -z ${dog+x} ]; then
echo 'is null'
fi
but when I try to do this, I get
${`echo "${cat}"`+x}: bad substitution
I guess part of the problem is that the subshell doing the command substitution doesn't know about $varname anymore? Do I need to export that variable?
My reason for doing this is that I learned from this answer how to check if a variable is null, and I'm trying to encapsulate that check in a function called is_null, like this:
function is_null {
if [ $# != 1 ]; then
echo "Error: is_null takes one argument"
exit
fi
# note: ${1+x} will be null if $1 is null, but "x" if $1 is not null
if [ -z ${`echo "${1}"`+x} ]; then
return 0
else
return 1
fi
}
if is_null 'some_flag'; then
echo 'Missing some_flag'
echo $usage
exit
fi
I'm not sure if I understand your problem.
If I got, what you need is eval command.
$ cat='dog'
$ varname='cat'
$ echo ${varname}
cat
$ eval echo \$${varname}
dog

Resources