1

I got a config file called user.conf that contains this list:

USER1_USERNAME="John"
#USER2_USERNAME="Mike"
USER3_USERNAME="David"
USER4_USERNAME="James"
USER5_USERNAME="Jenny"

Notice that user can comment out that line USER2_USERNAME so, only 4 users are used in source file in bash script test.sh here:

#!/bin/bash

#source the username
source "user.conf"

n=1
 while :; do
   ((n++))
   if [ -n "${USER${n}_USERNAME}"]; then
     echo "This variable USER${n}_USERNAME is set: ${USER${n}_USERNAME}"
   else
     echo "This variable USER${n}_USERNAME is not set"
   fi
done

I want to display which variable is set with its value and skip the commented variables but at this point my code just display an error:

./test.sh: line 8: ${USER${n}_USERNAME}: bad substitution

Is it possible to loop that variable like above?

Also, user can define much more USER in that user.conf, for example

USER6_USERNAME="George "

Expected output:

This variable USER1_USERNAME is set to John
This variable USER2_USERNAME is not set
This variable USER3_USERNAME is set to David
This variable USER4_USERNAME is set to James
This variable USER5_USERNAME is set to Jenny
5
  • Some of the answers to "Dynamic variable names in Bash" look like they might work for you. Also, see BashFAQ #6: "How can I use variable variables (indirect variables, pointers, references) or associative arrays?" Commented Jan 24, 2021 at 17:24
  • The link provided is useful, but doesn't give hint how to solve this case which is a bit puzzled and it is restricted to change the source variable file. Commented Jan 24, 2021 at 17:36
  • Please don't edit your solution into the question - post a new answer instead. Commented Jan 24, 2021 at 20:24
  • @janw sorry it is because I want to give respect to people who answers my question by not answering it to get reputation, putting my answer below the question because I ask the same question seems like not polite. That's my thought. Commented Jan 24, 2021 at 20:29
  • 1
    I understand that, but actually this is completely fine on this site: The best and most complete answer gets upvoted. Questions should not be used for answers. If your approach is very near to the accepted answer, you may ask the author whether they want to include it themselves. Else, you could also post a "community wiki" answer: This way the content is preserved and can be voted on, but you don't get reputation for it. Simply tick the "Community Wiki" checkbox for that. Commented Jan 24, 2021 at 20:37

3 Answers 3

2

You can use bash variable substitution like this:

#!/usr/bin/env bash

source "user.conf"
for i in "${!USER@}"
do
    echo $i ${!i}
done

Like your code, this first sources the users file, as it looks like a bash script. This of course has the potentially dangerous side effect that any other code in user.conf runs as well, so be careful and don't let strangers modify that file.

Then it uses ${!var@}, which expands to the names of variables whose names begin with a prefix, here "var", or for you "USER". You could also use ${!var*}, depending on whether you want all values in one quoted variable or multiple ones. See shell parameter expansion for details.

The whole approach is tied to a common prefix for your config variables. In this case, you'll also see $USER in the output, which is the name of the currently logged in user. You can filter that with e.g., grep or a simple if [ "$i" != "USER" ] in the loop.


If you want undefined variables as well, sourcing the users file may not be a good solution. You could instead read the file line by line and check for a leading #:

#!/usr/bin/env bash

set -eu

while IFS= read -r line
do
    var=$(echo "$line" | cut -d '=' -f 1)
    name=$(echo "$line" | cut -d '=' -f 2)
    if [[ "$var" =~ ^# ]]
    then
        var=$(echo "$var" | cut -c 2-)
        echo "The variable $var is not set"
    else
        echo "The variable $var is set to $name"
    fi
done

Output:

bash users.sh < users.conf 
The variable USER1_USERNAME is set to "John"
The variable USER2_USERNAME is not set
The variable USER3_USERNAME is set to "David"
The variable USER4_USERNAME is set to "James"
The variable USER5_USERNAME is set to "Jenny"

However, this approach is brittle as it doesn't understand bash syntax. A leading space would be fine when sourcing, but would trip the comment detection on this code. Variables in user names would not get expanded, which may or may not be a good thing.

Sign up to request clarification or add additional context in comments.

Comments

1

I liked what Robert suggested. But IMHO whole concept of your script is wrong. This should be done with array(s) not dynamic vars.

users=(
    "John"
    "Mike"
    "David"
    "James"
    "Jenny"
)

And than you just loop over that array

for user in "${users[@]}"; { echo "$user"; }

If you want to make this config editable by users this can also be done, thay can comment, delete or add items in array:

users=(
    "John"
#    "David"
    "James"
    "Jenny"
    "Lenny"
)

1 Comment

I like to use array but in this case I have the reason not to use array, it doesn't mean my script is wrong. One reason is that I want to allow user to key in the configuration files without dealing with array and sourcing files with array is very complex.. BTW, I actually solved this problem using another method where @Robert's answer is good for reference.
0

I also found my own way to deal with this by modifying the source file user.conf to have suffix at the end:

#user.conf
USER_USERNAME1="John"
#USER_USERNAME2="Mike"
USER_USERNAME3="David"
USER_USERNAME4="James"
USER_USERNAME5="Jenny"

#!/bin/bash
source "user.conf"
usernames="${!USER_USERNAME@}"
username_count=$(echo "${usernames}" | wc -w)
count=1
while [[ ${count} -le ${username_count} ]]; do
  typeset -n "username"="USER_USERNAME${count}"
  if [ -n "${username}" ]; then
    echo "The variable USER_USERNAME${count} is set to ${username}"
  else
    echo "The variable USER_USERNAME${count} is not set"
  fi
  ((count = count + 1))
done

Since it's restricted to put this in question, I post it here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.