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
janw
  • 8,758
  • 11
  • 40
  • 62
ToiletGuy
  • 331
  • 1
  • 2
  • 11
  • Some of the answers to ["Dynamic variable names in Bash"](https://stackoverflow.com/questions/16553089/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?"](http://mywiki.wooledge.org/BashFAQ/006) – Gordon Davisson Jan 24 '21 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. – ToiletGuy Jan 24 '21 at 17:36
  • Please don't edit your solution into the question - post a new answer instead. – janw Jan 24 '21 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. – ToiletGuy Jan 24 '21 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. – janw Jan 24 '21 at 20:37

3 Answers3

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.

Robert
  • 7,394
  • 40
  • 45
  • 64
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"
)
Ivan
  • 6,188
  • 1
  • 16
  • 23
  • 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. – ToiletGuy Jan 24 '21 at 20:02
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.

ToiletGuy
  • 331
  • 1
  • 2
  • 11