I have doubts:

The exercise is:

Make a script to detect users who have not logged in in the last 15 days AND belong to group "alumnos". The list will be added to /var/log/alumnos_sin_login.

My approach:

#Scripts which detects users whose group is "alumnos" who have been not logged in,
#in the last 15 days. The users' list is added to /var/log/alumnos_sin_login
#RESULTADO holds those users who haven't entered in the last 15 days
#LISTA holds alumnos' users' list
#USUAEIO actual user being parsed
#RAIZ != "" if user has logged -15 days

#Read and check if group is not created
exec 0 </etc/gshadow
if [ $(grep "alumnos:") -e ]
    echo "Grupo alumnos no existe"
    exit 1

RESULTADO="Los alumnos que llevan más de 15 días sin loguearse son: "
LISTA=$(grep "alumnos:" | cut -d ':' -f 4)
    RAIZ=$(find /home/$USUARIO -atime +15 | grep "/home/$USUARIO")
    if [ $RAIZ != "" ]

echo $RESULTADO >> /var/log/alumnos_sin_login
exit 0

Doubts: How can I improve this sentence?

if [ $(grep "alumnos:") -e ]

to check if the group is already created. It works fine if it doesn't exist, but if it exists it reports:

bash: [: ==: unary operator was expected


And second, is there a better way to know if a user has not logged in in the last 15 days. Because in the code I am assuming its directory is /home/name_of_user and it is not a standard. I mean, is there a command to get last user's logging time, or at least a way to know its directory properly; because I don't know it.

Thank you for the help.

2 Answers 2


The easiest way to have a list of users that have not logged in the last 15 days, instead of getting it from /home is using the lastlogcommand

lastlog -b 15 

Looking at a line of output:

alumno1 pts/14 Sat Oct 22 22:18:57 +0100 2016

If you just want the name (1st field):

lastlog -b 15 | sed "1d" | awk ' { print $1 } '

(sed "1d" if for deleting lastlog header)

From man lastlog:

OPTIONS The options which apply to the lastlog command are:

   -b, --before DAYS
       Print only lastlog records older than DAYS.

If the user has never logged in the message ** Never logged in** will be displayed instead of the port and time.

As for the actual script to check for the group:


getent group alumnos > /dev/null
if [ $? -ne 0 ]
    echo "group alumnos does not exist"
    exit 1

for i in ` lastlog -b 15 | sed "1d" | awk ' { print $1 } '`
    if [ `id -ng $i` == "alumnos" ]
        echo $i
exit 0

the variable i will iterate over the name of all users given by lastlog
id -ng user gives the name of the group of the user to which the current iteration "$i" is pointing to
getent group alumnos checks for the presence of the group

As for the if error you mention; when grep does not find anything it misses an operand, and then becomes empty; hence the error. One of the tricks is making a string comparation padded with "x" as in:

if [ "x"$i == "xstring" ]

Another alternative to checking for group alumnos existence:

if [ $(grep "^alumnos:" /etc/group) -e ]
    echo "No group alumnos"
    exit 1

Yet another alternative to check for a user with multiple groups:

groups user | grep alumnos
  • 1
    Thank you Ribeiro. I have learnt to list those users who have not logged in the last -b n days, with lastlog -b 15. Also I have learnt to determine complementary user's groups with: id -ng "user_name". Also is very convenient to check if a group exist with getent group "name_of_group". I have tried it and works fine. In my case it list a user called "prueba". Also I noticed it doesn't list other user "invitado" who is into group alumnos but appears to have as primary group alumnos, and not as complementary one. Thank you.
    – Yone
    Nov 9, 2016 at 18:11
  • you are welcome. I added as the last edition another simple possibility to test users with several groups. getent also has the ability to flag you users with groups not in files, but coming via network configurations, namely from LDAP. Nov 10, 2016 at 8:26

If you want to list the users that belong to the supplementary group foo, use

getent group foo | cut -d ':' -f 4- | tr -s '\n,' '\n\n'

The getent command uses the "name service switch"; it means that even on systems where the user information and passwords are stored on a remote server, getent can still obtain the user information:

  • getent passwd instead of parsing /etc/passwd

  • getent group instead of parsing /etc/group

If a third parameter is specified to the above, only the corresponding entry is shown. (There are also shadow and gshadow, but getent only provides the information to users with special privileges.)

The output of getent group foo is something like


where the first field is the group name, second field is the group password (* for no password, or x if it is only in the gshadow file), third field is the group ID number, and the fourth field is the comma-separated list of users having this group as a supplementary group.

The cut -d ':' -f 4- command removes the first three colon-separated fields from each line. (Or, put the other way, it outputs only the fourth and any following colon-separated fields from each input line.) This removes the group name, password reference, and group ID number.

The tr -s '\n,' '\n\n' converts all commas to newlines, and combines all consecutive newlines (and commas) into a single newline.

Thus, the output is just the names of users with foo as a supplementary group, one username per line.

The list does not include users whose primary group (id -gn) is foo; only those who have foo as a supplementary group (id -Gn).

If you want to list users who have foo as their primary group, use e.g.

getent passwd | awk -F ':' '$4=='$(id -g foo)' { print $1 }'

This time, we dump the entire passwd database, and filter it using awk.

The $(id -g foo) part is actually outside single quotes, and thus expands to the group ID number of group foo. The awk rule compares the fourth colon-separated field on each line to the group ID number (of group foo), and if it matches, prints the first field (that user's name).

The lastlog command can be used to obtain the login record of the users. The -t option lets you specify the number of days considered for the record.

The output (specifically, the date format) of the lastlog command is localized. This means the user locale affects the way dates are shown in the lastlog output. If you want to ensure the output is always in English and uses the English date format, use

LANG=C LC_ALL=C lastlog

The LANG=C LC_ALL=C are two environment variables most commonly used to set the locale. Shells let you override the environment temporarily, for a single command, simply by putting them before the command itself.

For example, to list only the users who have not logged in the last 10 days, you can use

LANG=C LC_ALL=C lastlog -t 10 | awk '/Never logged in/ { print $1 }'

Note that this will not restrict the selection to the members of the foo group, though.

You can always check what supplementary groups an user is a member of by using

id -gn username

With Bash, you can use

groups=" $(id -gn "$user") "
if [ "${groups// foo / }" != "$groups" ]; then
    # user $user is a member of group foo
    # user $user is not a member of group foo

to check if user $user is a member of group foo. (Again, this only checks for supplementary groups, not the primary group. Each user has a single primary group, and that can be obtained using id -Gn "$user".)

That works, because $groups is first set to the space-separated list of supplementary groups for user $user, with a leading and a trailing space. Note the quoting! The Bash expression ${groups// foo / } expands to the value of $groups, but all instances of foo (note the leading and trailing space) deleted (replaced with a space).

The if clause compares the value of $groups with foo deleted to the value of $groups. If the two differ, then $group must have contained foo. If you dislike this, you can use the equivalent

if id -Gn "$user" | tr -s ' \n' '\n\n' | grep -qxF foo ; then
    # user "$user" is a member of group foo

Here, tr combines all consecutive spaces and newlines into newlines, splitting the group names to each on their own line. The grep -qxF foo does a "quiet" grep (-q), i.e. it does not output anything, only returns success or failure (and in a piped command like that in Bash, the success/failure is determined by the final command in the pipeline); the -x options means entire lines are compared (so we don't accept all groups having foo in them, only groups exactly matching foo); and finally, the -F option means grep interprets foo as a fixed string, and not as a regular expression. (It only matters if foo were to contain characters with special meaning in regular expressions, like *, [, ], and so on.)

To write a Bash script as described in the question, you need some of the above -- there are at least two different ways to use some of the above to solve this --, plus some kind of "read each input into a variable" loop, i.e.

some command | while read line ; do
    # Here, "$line" iterates through the lines
    # output by some command

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .