Gabi Barrientos
Gabi Barrientos

Reputation: 1886

Check if a user is in a group in Bash

I have a server running where I use PHP to run a Bash script to verify certain information of a user. For example, I have a webhosting server set up, and in order to be able to add another domain to their account I want to verify if the user is actually a member of the customers group. What would be the best way to do this in Bash?

Upvotes: 70

Views: 115442

Answers (17)

Valerio Bozz
Valerio Bozz

Reputation: 1468

If you want to avoid "grep" or parsing

You may be surprised to discover that, even nowadays, this task is still completely un-trivial in scripts and commonly resolved parsing id with grep. Parsing is not efficient and can introduce unexpected broken corner-cases, for instance with the root user (that is not explicitly in any group), but also causing problems if a particular group/user is crafted with a dash in the name (-) or something else equally unpredictable, that may change from distribution to distribution.

So, you can avoid id ... | grep ... or similar. Instead:

Alternative: Design a Unix Permission Check

Instead of checking if the current user is in a particular group, what about checking if the current user can really perform a particular action?

Example:

if [ -w /home/customers ]; then
    echo "You are authorized (I checked you can write)"
else
    echo "You are NOT authorized :-("
    echo "Join the group 'customers' please."
    echo "This incident will be reported."
    echo "Your current user is:"
    id
fi

This is just an example and can be bomb-proof, as long as you have a filename (or a directory) that is not writable by unknown people, but is at least writable by the group customers.

Example permissions:

  • /home/customers
  • owned by root:customers
  • permissions 775

Again, the file can be whatever, a file or a directory. Just use your Unix skills to design a file/directory (empty or not) that is not writable by anybody, but it's at least writable by that group. Accordingly to your real-world needs in that path, you may want to tune the "others" permission (last octet). So to have 755 if others should enter in that directory, or 770 if the directory should be locked down to others.

THAT FILE/DIRECTORY MUST NOT BE A STUPID 777 - THAT MEANS THAT EVERYONE CAN WRITE IT, SO THE CHECK WOULD BE ALWAYS TRUE FOR EVERYBODY. THIS IS NONSENSE.

Have fun with a secure and efficient way to check for permissions.

Additional documentation for file/directory permissions in Bash:

https://www.man7.org/linux/man-pages/man1/test.1.html

Additional notes

Note that the question is assuming a particular non-portable environment with a specific group called customers. That is the reason why I suggest this solution based on a related directory /home/customers - that the user very probably already has in place. But indeed do not adopt this solution as-is in a general-purpose script to be executed on every citizen of the world since they may have not that directory, and it's nonsense to be created ad-hoc for just this test. Anyway, in general, this is still useful and widely adopted. Look at the Filesystem Hierarchy Standard. There are plenty of files with dedicated groups that are writable only by that group to write on a particular media, device, serial port, etc. So this answer is still generally relevant to understand how to do things, if your priority is readability, performance, etc. So you can check for test -w /dev/something to know if the user can use that device, etc. Take inspiration:

https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard

Upvotes: -1

Gilles Quénot
Gilles Quénot

Reputation: 185035

Try doing this :

username=ANY_USERNAME
if getent group customers | grep -qw "$username"; then
    echo true
else
    echo false
fi

or

username=ANY_USERNAME
if groups $username | grep -qw 'customers'; then
    echo true
else
    echo false
fi

Upvotes: 48

user21287952
user21287952

Reputation: 1

Here is a quick effective way

check=`id $username` # Get the groups membership of the username

# Check if the groups membership contain the group
if [[ $check == *"$group"* ]]; then 
  echo $userName "is a member of" $group
else
  echo $userName "is not a member of" $group
fi

Upvotes: 0

Adelino Safeca
Adelino Safeca

Reputation: 115

There are good solutions above, but I just want to reinforce that when using simple filter grep -w "soughtwordcan resolve. Sometimes there is no need to use regex at all, it's already there.

Upvotes: 0

lucidelirium
lucidelirium

Reputation: 1

I decided to go with matching the entire line after iterating through each group name.

#!/bin/bash

read -p "Enter username: " userName
read -p "Enter group: " groupName

groupCheck=$(id -nG $userName 2>/dev/null | awk -F " " '{for (i=1; i<=NF; i++) print $i}' | grep -cx $groupName)

if [ $groupCheck = 1 ]; then
  echo $userName "is a member of" $groupName
else
  echo $userName "is not a member of" $groupName
fi

Upvotes: 0

Corin
Corin

Reputation: 2467

For all those golf fans out there:

ingroup(){ [[ " `id -Gn $2` " == *" $1 "* ]]; }

Usage: ingroup group [user]

Example:

if ingroup video; then
  echo 'Enjoy the show!'
fi

TL;DR The point is I have taken advantage of the built in globbing in order to find the substring.

Edit: Thanks to @Anthony Geoghegan for the id -Gn tip.

Upvotes: 7

artfulrobot
artfulrobot

Reputation: 21397

Here's mine.

First the long version

#!/bin/bash
if [[ $# -eq 0 ]]
then
  echo "Usage: $0 [-v] user group"
  echo ""
  echo "  -v verbose. Outputs a sentence for humans."
  echo ""
  echo "Example:"
  echo ""
  echo "  ingroup wilma sudo && echo Wilma has superpowers"
  exit 2
fi

if [[ "$1" == "-v" ]]
then
  verbose=1
  shift
fi

user=$1
grp=$2
# Get groups output
grps=$(groups $user)
# Create a regexp. Note that we must create the regexp in a var
# because it's the only way to allow for spaces in the regexp.
# Strangely we provide this var unquoted when using it; even 
# though it has spaces.
re="^.*:.* $2 "
if [[ "$grps" =~ $re ]]
then
  [[ -n "$verbose" ]] && echo "$user is in group $grp"
  # Success error code
  exit 0
else
  [[ -n "$verbose" ]] && echo "$user is not in group $grp"
  # Fail error code
  exit 1
fi

Want a shorter version as a function?

ingroup() {
  re="^.*:.* $2 "
  [[ "$(groups $1) " =~ $re ]] || return 1
}

Tests

# Basic positive test
$ ingroup -v wilma sudo && echo 'and therefore is cool'
wilma is in group sudo
and therefore is cool

# Basic negative test
$ ingroup -v wilma myprivateclub || echo 'sorry bout that'
wilma is not in group sudo
sorry bout that

# Test with hyphens in the group name
$ ingroup -v wilma systemd-journal
wilma is in group systemd-journal

# If the group does not exist, it's a negative
$ ingroup -v wilma somewronggroup
wilma is not in group somewronggroup

Upvotes: 0

fdb
fdb

Reputation: 2038

Bash single line:

[[ " $(groups) " =~ ' spark ' ]] && echo 'Is in group'

Bash multi line:

if [[ " $(groups) " =~ ' spark ' ]]; then
    echo 'Is in group'
fi

Upvotes: 0

Anthony Geoghegan
Anthony Geoghegan

Reputation: 11983

A while ago, I wrote a shell function to check if a user is a member of a group. To maximise portability, I wanted it be POSIX-compatible (while this question is tagged as bash, this function will still work). For performance, I wanted to use builtin shell features as much as possible: the only external command it uses is id, the POSIX-standardised utility for getting data about a user’s identity.

is_in_group()
{
  groupname="$1"
  # The second argument is optional -- defaults to current user.
  current_user="$(id -un)"
  user="${2:-$current_user}"
  for group in $(id -Gn "$user") ; do
    if [ "$group" = "$groupname" ]; then
      return 0
    fi
  done
  # If it reaches this point, the user is not in the group.
  return 1
}

Example usage to test both positive and negative cases – and ensure it handles a non-existent username gracefully:

g=mail
userlist="anthony postfix xxx"
for u in $userlist; do
  if is_in_group "$g" "$u"; then
    printf "%s is in ‘%s’\n" "$u" "$g"
  else
    printf "%s is NOT in ‘%s’\n" "$u" "$g"
  fi
done

Running the above command prints the following output:

anthony is NOT in ‘mail’
postfix is in ‘mail’
id: ‘xxx’: no such user
xxx is NOT in ‘mail’

It hasn’t been tested for the case where a group or user has a space or other unusual characters in their name but some research shows that such names are not legal: the POSIX Base Definition for Group Name states that

To be portable across conforming systems, the value is composed of characters from the portable filename character set.

The Portable Filename Character Set is specified as the alphanumeric characters, A-Z, a-z, 0-9 along with the period, underscore, and hyphen-minus characters.

Upvotes: 2

cs2889
cs2889

Reputation: 79

I know this is probably old thread but just in case this also works well:

id -Gn "username"|grep -c "groupname"

if any number > 0 is returned then user is a member of that group.

Upvotes: 7

Danny Kopping
Danny Kopping

Reputation: 5190

Using the zero delimiter to split by lines:

id -nGz user | tr '\0' '\n' | grep '^group$'

Upvotes: 6

Nicholas Sushkin
Nicholas Sushkin

Reputation: 13770

A slightly more error-proof method to check for group membership using zero char delimited fixed string grep.

if id -nGz "$USER" | grep -qzxF "$GROUP"
then
    echo User \`$USER\' belongs to group \`$GROUP\'
else
    echo User \`$USER\' does not belong to group \`$GROUP\'
fi

or using long opts

if id --name --groups --zero "$USER" | 
   grep --quiet --null-data --line-regexp --fixed-strings "$GROUP"
then
    echo User \`$USER\' belongs to group \`$GROUP\'
else
    echo User \`$USER\' does not belong to group \`$GROUP\'
fi

Upvotes: 30

Antxon
Antxon

Reputation: 1943

if id -nG "$USER" | grep -qw "$GROUP"; then
    echo $USER belongs to $GROUP
else
    echo $USER does not belong to $GROUP
fi

Explanation:

  1. id -nG $USER shows the group names a user belongs to.
  2. grep -qw $GROUP checks silently if $GROUP as a whole word is present in the input.

Upvotes: 60

odrac1r
odrac1r

Reputation: 61

You could use groups $username_here | grep -q '\busergroup\b'

The exitcode will be 0 if a match was found, 1 if no match was found.

user_in_group()
{
    groups $1 | grep -q "\b$2\b"
}

you could use this function as user_in_group userfoo groupbar

Upvotes: 6

MatthieuP
MatthieuP

Reputation: 1126

My version not relying on grep.

First parameter (mandatory): group
Second parameter (optional, defaults to current user)

isInGroup(){
   group="$1"
   user="${2:-$(whoami)}"
   ret=false 
   for x in $(groups "$user" |sed "s/.*://g")
   do [[ "$x" == "$group" ]] && { ret=true ; break ; }
   done
   eval "$ret"
}

Upvotes: 0

Aleks-Daniel Jakimenko-A.
Aleks-Daniel Jakimenko-A.

Reputation: 10653

Sounds like an another answer:

username='myuser'
if grep -q -E "^customers:.*[:,]$username(,.*|\b)" /etc/group; then
    echo 'true'
else
    echo 'false'
fi

As reported by sputnick the output of the groups command may depend on your OS. I am not sure how this code is going to perform, but most probably it will do better.

Upvotes: 1

Aleks-Daniel Jakimenko-A.
Aleks-Daniel Jakimenko-A.

Reputation: 10653

username='myuser'
if groups "$username" | grep -q -E ' customers(\s|$)'; then
    echo 'yes'
else
    echo 'no'
fi

I have to clear one thing: groups will probably return something like this:

myuser : cdrom floppy sudo audio dip video plugdev fuse

But there is one cornercase when your user is named customers:

customers : cdrom floppy sudo audio dip video plugdev fuse

For example, \bcustomers\b pattern is going to find the username, not the group. So you have to make sure that it is not the first word in the output.

Also, another good regexp is:

grep -q ' customers\b'

Upvotes: 0

Related Questions