eval in a loop

Hello all

I have the following script:

The content of the USERS-File is like this:
user1
user2
user3

The Script is like this:


USERSFILE="/tmp/users.txt"

for USER in <`$USERSFILE`; do
...
...
eval echo LOGGEDIN_${USER}=OK
done



for USER in <`$USERSFILE`; do
if  ! -z $(eval echo "\$LOGGEDIN_\$USER") ]; then
[INDENT]echo "something"[/INDENT]
else
[INDENT]echo "something else"[/INDENT]
fi
done


My problem is, that I cannot verify the content of the variable $LOGGEDIN_${USER} (if it’s zero or not), because the “LOGGEDIN”-Variable will not be resolved.

Any hints?
Thanks a lot.
Tom

I do notexactly understand what you want to do, but the command

for USER in <`$USERSFILE` ; do
...
done

I do not understand at all.
First it $USERFILE is replaced by the value /tmp/users.txt so the whole then is:

for USER in <`/tmp/users.txt` ; do
...
done

Then the quotes let /temp/users.txt execute, but it is no executable script or program!
AND my bash complains against the < because no redirection is possible in this context:

./hh: line 5: syntax error near unexpected token `&lt;'
./hh: line 5: `for USER in <`$USERSFILE`; do'

When I understand correct what you want to do, I would do like

while read USER ; do
....
done <${USERFILE}

So I am not even reaching to your eval.
But that looks strange also. When the eval is evaluated the reslult is een echo command:

echo LOGGEDIN_user1=OK

which means that this text is output to stdout. There wiill be no variable asignment. If that is what you mean just write

eval LOGGEDIN_${USER}=OK

In my first loop, I want to set a variable like this:
LOGGEDIN_${USER}=“OK”

In a second loop in the same script, I want to readout this already set variable. I want to check, if a value (in this case “OK” is set or even not):

if  ! -z $(eval echo "\${LOGGEDIN}_\${USER}") ]; then
echo "something"
fi

The output (set -x) is like this:


++ eval echo '${LOGGEDIN}_${USER}'
+++ echo _int-windows
+ '' '!' -z _int-windows ']'

As you can see, there’s no query to my first defined Variable "LOGGEDIN_${USER}=“OK”. The word “LOGGEDIN” (in the 2nd line of the code) is not appended. I think, that’s the reason, why I can’t query, if the variable has a zero length or not.

Thanks.

This is an interesting question, because what you are basically asking for is an associative array feature in bash. Unfortunately this is not a current feature in bash although it has been proposed. If you search you will find various articles suggesting techniques for faking this feature, e.g.

The Linux and Unix Menagerie: How To Fake Associative Arrays In Bash

However if I were in your place, I would rewrite it in any language that supports associative arrays, e.g. awk, perl, python, you name it. But that didn’t really answer your question. :expressionless:

Such a think should be buildup piece by piece else nobody does understand how to do it. Below I give you everytime a listing of my tiny script and what it prints. My first version:

henk@boven:~> cat hh
#!/bin/bash

LOGGEDIN_user1="OK"
USER="user1"

echo "\${LOGGEDIN_${USER}}"


henk@boven:~> ./hh
${LOGGEDIN_user1}
henk@boven:~>        

Notice how the ${…} constructs embrace each other and that the first $ is escaped. Now we add eval:

henk@boven:~> cat hh
#!/bin/bash

LOGGEDIN_user1="OK"
USER="user1"

eval echo "\${LOGGEDIN_${USER}}"


henk@boven:~> ./hh
OK
henk@boven:~>     

And now the test:

henk@boven:~> cat hh
#!/bin/bash

LOGGEDIN_user1="OK"
USER="user1"

if  $(eval echo "\${LOGGEDIN_${USER}}") = OK ]]
then    echo "It is OK"
else    echo "It is NOT ok"
fi


henk@boven:~> ./hh
It is OK
henk@boven:~>     

Hope this helps.

The file “/etc/appl/users” looks like this:

user1
user2
user3

Here ist my script:


LOGFILE="/var/log/user.log"
USERS="/etc/appl/users"
NOW_MONTH=`date -d "0 month ago" +%b`
ONE_MONTH_BEFORE=`date -d "1 month ago" +%b`
TWO_MONTH_BEFORE=`date -d "2 month ago" +%b`
THREE_MONTH_BEFORE=`date -d "3 month ago" +%b`
ARRAY_USERS=(`cat $USERS`)

check_login()
{
        for USER in ${ARRAY_USERS[@]}; do
                                for MONTH in ${ARRAY_ALL_MONTH[li]}; do
[/li]                                        cat $LOGFILE | grep -i $MONTH | grep -i $USER >/dev/null 2>&1
                                        if [ "$?" = "0" ]; then
                                                        eval echo LOGGEDIN_$USER=OK
                                                else
                                                        echo "User $USER has never logged in during $MONTH." >/dev/null 2>&1
                                        fi
                                done
        echo -e "
"
        done
}


print_message()
{
        for USER in ${ARRAY_USERS[@]}; do
                        if [[ $(eval echo "\${LOGGEDIN_${USER}}") = OK ]]; then
                                echo -e "User \"$USER\" is OK."
                        else
                                echo -e "User \"$USER\" can be deleted."
                        fi
        done
}



### Main
check_login
print_message

And this is the output of the print_message()-function:

  • print_message
  • for USER in ‘${ARRAY_USERS[@]}’
    ++ eval echo ‘${LOGGEDIN_user1}’
    +++ echo
  • [[ ‘’ = OK ]]
  • echo -e ‘User “user1” can be deleted.’
    User “user1” can be deleted.
  • for USER in ‘${ARRAY_USERS[@]}’
    ++ eval echo ‘${LOGGEDIN_user2}’
    +++ echo
  • [[ ‘’ = OK ]]
  • echo -e ‘User “user2” can be deleted.’
    User “user2” can be deleted.

As you can see, the value “ok” - set in the first function - will not be interpreted.
Any hints?

Thanks a lot.

I see you understood the long post I gave you earlier. Glad you did because it took me some time :wink:

Now the same principles on the next one:

eval echo LOGGEDIN_$USER=OK

is wrong because you do not want an echo statement to be executed and after the eval this is what is presented again to bash:

echo LOGGEDIN_user1=OK

And that just prints

LOGGEDIN_user1=OK

to stdout.

The following is what you need:

eval LOGGEDIN_$USER=OK

becuase after the eval it will be

LOGGEDIN_user1=OK

and that is what you want to happen.

BTW I prefer to use ${…} always. It may be a bit more typing, but in the more complicated situations (as those we are in now) it improves so much the reading and the error detection. For the same reason I always use $(…) instead of the, very old fashioned and error prone, ....

Hello Henk

Thanks a lot for your answer. It solved my problem with 50% :slight_smile:

When the user-variable only contains “user1”, all works fine. But when the user-variable contains a value like this
“abc-efg-user1”, then I receive again an error:

eval LOGGEDIN_abc-efg-user1=ok
-bash: LOGGEDIN_abc-def-user1=ok: command not found

How can I set this value? I tried with escaping the special characters…but no success.
Thanks a lot.
Tom

Blweh! Usernames with - signs in them! :frowning:

I am afraid this can not be solved in the way we are working now because it always ends up in a asignment to a variable name that isn’t a variable name.

A-B="something"

is always wrong in bash (and in other shells).

When you do not mind (and it is allright with the rest of your script) you could change all - in _ . Either by doing something like

USC_USER=$(echo ${USER} | sed 's/-/_/g')

for each individual user within the loop (and that twice I think coz you have two loops, thus a lot of calls to sed). You have than USER and USC_USER available.

I would prefer a solution that I showed earlier. Making one big loop:
cat ${USERS} | sed ‘s/-/_/g’ | while read USER
do

done
But then you would not have the original version (with the - ) available within the script which you may need.

I hope with these suggestions you may compose a solution.

But I repeat: Blweh. You should revise your username policy. I hope there are not more ‘strange’ characters to be found in them.

From man adduser:

The account name must begin with an alphabetic character and the rest of the string should be from the POSIX portable character class ([A-Za-z_][A-Za-z0-9_-.]*[A-Za-z0-9_-.$]).

So they are allowed, but not all that is allowed is sound practice.

I will not take over the job of writing your script :slight_smile: but as the usage of the username as part of a variable name can give problems (because both have different definitions about the allowed characters) may be another approach is better.

What about two arrays. One with the username and one with the ok. Initialize a counter

integer I=0

and add 1 every cycle

USERARR*="${USER}"
USERSTAT*="OK" 
(( I += 1 ))

The second loop should then count from 0 to I and use the values.
Strange, I have uppercase I between the ] and this forums makes lower case i out off them >:)

This would be a primitive sort of the associative array ken_yap mentioned earlier. Just a suggestion.**

Hello Henk

I seems, that our usernames are really not posix-compatible. So I changed the usernames to contain a “_” instead of a “-”. Now the script works really fine.
Thanks a lot for your support.

Kind regards,
Tom

You are welcome. Programming is realy nice to exercise ones brains rotfl!