2

Using the following code, I'm trying to read a file into an array:

GROUPS=()

while IFS=: read -r g1 g2 g3 g4
do
    GROUPS+=("$g3")
    echo "$g3"
done < /etc/group

This doesn't work, it doesn't even output anything, however, if I leave only the echo it does print the contents of the file. As long as the instruction to add to an array is present somewhere in the loop, nothing is printed. It seems very strange since I can do this to other files without problems.

Any idea what's causing this? I checked with bash -x and when the problematic instruction is present, it doesn't even enter the loop.

Also, in case it is relevant, I'm running this as root.

  • It is generally advisable to make it a habit to use all-lowercase or mixed-case variable names in Bash in order to avoid potential name collisions with shell and environment variables. – Dennis Williamson Jun 29 '16 at 21:46
6

You unluckily selected name of the array which is already reserved by the bash itself and is read only, so you cannot change it.

GROUPS

An array variable containing the list of groups of which the current user is a member. Assignments to GROUPS have no effect and return an error status. If GROUPS is unset, it loses its special properties, even if it is subsequently reset.

Just use another name and the code should work.

  • Why echo command does not print anything? – coffeMug Jun 29 '16 at 19:34
  • @coffeMug That's a little mystery to me. It seems that trying to override GROUPS kills the rest of the entire command line: GROUPS=(); echo test gives nothing and exit code 1. Similarly GROUPS=("$(echo test >/dev/tty)") – jimmij Jun 29 '16 at 19:48
  • @coffeeMug - as with the OP's symptom "it doesn't even enter the loop", I see it exiting the while loop with RC=1 after the first attempted += assignment, so the echo is not even executed. See more by putting the commands in a file and running it with bash -x thatfile – Jeff Schaller Jun 29 '16 at 20:23
0

You already have an answer saying how to do this entirely in bash with a while ... read ... loop...I'd like to suggest that doing it in bash alone is one of the worst ways to do this.

Also, reading /etc/group itself is not a good idea because that file is just one of the possible sources of group data (other sources may include LDAP, Active Directory, winbind, NIS, mysql, pgsql, Berkeley db files, extra group files from libnss-extrausers, and many more).

Instead, use the output of getent group.

Try this:

g=( $(getent group | awk -F: '{print $3}') )

or

g=( $(getent group | cut -d: -f3) )

The awk method is particularly useful if you want to combine it with regexp or fixed-text search and/or other selection criteria (e.g. $1 == groupname or $3 >= 500 && $3 <= 1000)

BTW for, e.g., debugging you can print the groups in array g with, e.g.:

# all on one line
echo "${g[@]}"

# one element per line
printf "%s\n" "${g[@]}"

# in a format that can be re-used to define the variable in another
# bash shell or script.
declare -p g

I particularly like declare -p for debugging - it not only shows you the variable name (e.g. similar to echo "foo='$foo'"), it also shows the array indices for array variables, and everything is properly quoted and backslash-escaped:

$ g=( $(getent group | awk -F: '$1 ~ /my/ {print $1}') )
$ declare -p g
declare -a g='([0]="mysql" [1]="mythtv")'

BTW typeset is a synonym for declare that works in bash and also some other shells like ksh.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.