5

Is there an easy way to get a list of users who are in all specified groups?

For example, if I have the following users:

user name membership in groups
fred rainbow, dell
jane hp
zippy rainbow, hp, dell
george hp, dell
bungle rainbow, hp, dell

I would like something like this:

[me@box ~]$ magic "dell,hp"
zippy, george, bungle

i. e. returning the users who are a member of both groups dell and hp.

If it is multiple steps that is fine, though if it’s not really possible without having to resort to lots of Bash black magic, and it is quicker to do it in a regular expression‐enabled text editor that is also fine.

I’m running on Red Hat Enterprise Linux 4 if that makes any difference.

Rich
  • 1,373

5 Answers5

4

This works for two groups at a time:

getent group dell hp | cut -d: -f 4 | tr , '\n' | \
sort | uniq -d | sed ':a;$s/\n/, /g;N;ba'

Put it in a function with some modifications and it will handle any number of groups:

grmagic () {
    getent group "$@" | 
        cut -d: -f 4 | 
        tr , '\n' | 
        sort | 
        uniq -dc | 
        grep "^[[:blank:]]*$#" |
        awk '{all = all d $3; d = ", "} END {print all}'
}

Run it:

$ grmagic dell hp
zippy, george, bungle
$ grmagic dell hp rainbow
zippy, bungle

A function consisting mostly of an AWK script:

grmagic () {
    getent group "$@" |
    awk -F: -v "c=$#" '{
            split($4, a, ","); 
            for (i in a) n[a[i]]++
        } 
        END { 
            for (i in n) 
            if (n[i] == c) {
                printf d i; d=", "
            }; 
            printf "\n"  }'
}
3

I don't known any tool doing this but it is easy to script.

At first get the list of the users on the system, then run groups on each and at the end, grep on the desired groups :

getent passwd | sed 's/:.*$//g' | \
    while read user; do groups $user; done | \
    grep group1 | grep group2
jon_d
  • 693
1

Small python script:

#!/usr/bin/python

import subprocess,sys

group={}

for user in sys.argv[1:]:
    group[user] = set(subprocess.Popen("id -nG %s"%user, stdout=subprocess.PIPE, shell=True).stdout.read().split())

for g in group[sys.argv[1]] & group[sys.argv[2]]:
    print g

Test:

# id user1
uid=1001(user1) gid=1001(user1) groups=1001(user1),1004(us1),1005(dell)
# id user2
uid=1002(user2) gid=1002(user2) groups=1002(user2),1004(us1),1005(dell)
# ./test.py user1 user2
dell
us1
ooshro
  • 11,502
0

I’m afraid there is no easy way. It is an unusual requirement. This answer assumes you use exclusively portable group and user identifiers as defined by the POSIX™ standard.

  • You can, as as jon_d already suggested, chain multiple greps effectively meaning a logical and. I would like to emphasize the need of proper anchoring in order to avoid false positives, for example by surrounding every group name with spaces.
    getent passwd | while IFS=':' read -r username remainder
    do
        printf '%s\t %s \n' "${username}" "$(id -Gn ${username})"
    done | grep -F ' group0 ' \
         | grep -F ' group1 ' \
         | grep -F ' group2 ' | cut -f1
    
    Keep in mind that id -Gn may produce very long lines and this might cause some command to fail or simply produce an incorrect result.
  • In a shell script you could assemble a pipeline in a shell variable as a string and execute it via evaluate.
  • Another implementation uses shell patterns and still keeps the number of spawned concurrent processes fairly low:
    #!/bin/sh -u
    #        name: group user
    # description: show members of a group or in multiple groups simultaneously
    #  maintainer: J. Doe <mailbox@host> # fill in in multi-sysadmin environments
    : "${1:?Error: Specify at least one group name.}"
    # Some rudimentary input validation.
    for group
    do
        # Bail out if non‑existent group was specified.
        getent group "${group}" >&- || exit
    done
    # Abbreviate pipelines with recurring functions:
    s2n() { tr ' '  '\n' ; }
    n2s() { tr '\n' ' '  ; }
    # Alphabetically order the argument list.
    set -- $(printf "${*}" | s2n | sort)
    # Form a pattern expression `' * group0 * group1 * '`.
    search="$(printf " ${*} " | sed -e 's/ / * /g' )"
    # Iterate over the list of users.
    getent passwd | while IFS=':' read -r username remainder
    do
        # `id -G` obtains all groups a user belongs to,
        # including the primary group specified in `passwd`.
        # `-n` resolves numeric IDs to textual representations.
        groups="  $(id -Gn ${username} | s2n | sort | n2s | sed 's/ /  /g')  "
        # If the search pattern can eliminate all occurrences,
        # an empty string is the result. `[ "" ]` returns `false`.
        [ "${groups##${search}}" ] || printf '%s\n' "${username}"
    done | sort | { n2s ; printf '\n' ; } | fmt
    
  • If you do not care about a user’s primary group (the group specified in the 4th field of a passwd file), you can eliminate the invocation of id.
    #!/bin/sh -u
    for group
    do
        # Drop out if a non‑existent group was specified.
        getent group ${group} || { printf '%s?\n' "${group}" ; exit 1 ; }
    done | cut -d':' -f4 | grep -v '^$' \
         | tr ',' '\n' | sort | uniq -c \
         | while read -r count user
    do
    # This test assumes that `group` is not malformed,
        # in particular no lines of the form
        # mygroup:*:1234:foo,bar,foo,foo,foo
        [ ${count} -eq ${#} ] && printf '%s\n' "${user}"
    done
    
    The algorithm counts how often a user name appears in the group membership lists produced by getent group.
0

A bash solution that takes more than 1 user per line and print only unique groups. Normal output is one group per line, use -c to get the output separated by commas.

#!/bin/bash
\unalias -a

if [ $# -lt 1 ] then echo "Need at least one user" 1>&2 fi

RESULT='' COMMA=0 while [ $# -gt 0 ] do if [ $1 == '-c' ] then COMMA=1 else RESULT="$RESULT $(id -Gn $1)" fi shift done if [ $COMMA -eq 0 ] then echo $RESULT |tr ' ' '\n'| sort -u else echo $RESULT |tr ' ' '\n' | sort -u |tr '\n' ', ' |sed 's/.$//g' echo fi

Example output:

# ./magic.sh coredump mongodb
audio
cdrom
coredump
dialout
floppy
lpadmin
mongodb
netdev
nogroup
plugdev
powerdev
video

Using -c:

# ./magic.sh -c coredump mongodb
audio,cdrom,coredump,dialout,floppy,lpadmin,mongodb,netdev,nogroup,plugdev,powerdev,video
coredump
  • 12,921