4

How can I loop over all users in a shell script?

I am writing a shell script to perform cleanup on a system. And if the script is run as root, I want to perform the requested actions in all users' home directories.

I thought I could maybe loop over the lines in /etc/passwd; however, I see many more entries there than there are living, breathing users . Is there some special way I can weed out dummy users from /etc/passwd?

I would prefer solutions tailored for the Bourne Shell (for portability). Pseudocode and mere explanations are also welcome.

  • 2
    You need to take UID >= 1000 ..might be UID >= 500 in Redhat based.. – heemayl Apr 28 '15 at 19:40
  • 5
    Can't you just loop over the directories in /home/? That's where all your real users' directories should be, and nothing else, if your system is set up anywhere near sanely. – marinus Apr 28 '15 at 19:41
  • 1
    I don't think the home directory is a good solution because people do change that to be something outside /home occasionally. Going off the UID and Shell (filtering /bin/false, /bin/true, and /sbin/nologin users) is probably a safer bet. – Bratchley Apr 28 '15 at 19:49
  • 3
    Depending on your system, you may also think of starting with getent passwd output so that it goes through NSS instead of only doing this for local users. – Bratchley Apr 28 '15 at 19:51
  • 1
    @marinus on our servers at my place of work, the home directories are all on network drives. This allows your home directory to be shared between servers. So looping through /home won't cut it. – Sildoreth Apr 28 '15 at 22:53
5

There's no standard command to enumerate all existing user accounts. On most Unix variants, /etc/passwd contains the list of local accounts, always with the same traditional format (colon-separated columns). On Linux with Glibc (i.e. any non-embedded Linux), you can use the getent command: getent passwd is similar to cat /etc/passwd, but also includes remote accounts (NIS, LDAP, …).

The following snippet enumerates user accounts:

getent passwd | while IFS=: read -r name password uid gid gecos home shell; do
  echo "$name's home directory is $home"
done

Filtering the flesh-and-blood users is not possible in a completely reliable way, because nothing in the user database says whether a user is flesh-and-blood. (Plus: do test accounts count? Do guest accounts count? etc.) Here are some heuristics you can apply.

  • You can filter users whose home directory seems not to be a system directory.

    top=${home#/}; top=${top%%/*}
    case $top in
      |bin|dev|etc|lib*|no*|proc|sbin|usr|var) echo "Looks like a system user";;
      *) echo "Looks like a user with a real home directory";;
    esac
    
  • You can test if the user owns their home directory. That's almost always the case for flesh-and-blood users, and usually not the case for system users, but this is not very reliable because some system users do own their home directory. Furthermore it's possible for the home directory of flesh-and-blood users not to exist if it's a remote directory that isn't currently available, though normally the directory would be automounted. On Linux:

    if [ -d "$home" ] && [ "$(stat -c %u "$home")" = "$uid" ]; then
      echo "Likely flesh-and-blood"
    else 
      echo "Probably a system user"
    fi
    
  • You can test if the user has a shell that's an actual shell. But many system users also do, so that's even less reliable than the home directory.

  • You can test whether the account has a password. This isn't fully reliable because flesh-and-blood users don't always have a password (for example they might only have SSH keys). Occasionally system accounts have a password — in particular the root account often does. How to do this depends on the Unix variant and often requires root access. On Linux with shadow passwords (which is the normal thing for Linux), you need to look in /etc/shadow for the password.

    case $(getent shadow "$name" | awk -F: '{print $2}') in
      ?|??) echo "No password on this account";;
      |\$*) echo "This account has a password";;
      *) echo "Weird password field";;
    esac
    
  • Many systems define UID ranges for system accounts vs flesh-and-blood users. The ranges are system-dependent: the default depends on the Unix variant and distribution, and it can usually be configured by the system administrator. On most Linux distributions, the ranges are defined in login.defs: UID_MIN to UID_MAX for human users, other values for system accounts.

I think the path of the home directory is the most reliable single indicator, but you may want to combine it with other heuristics.

2

Based on the comments on the question and some experimentation, here's what I came up with...

#!/bin/bash
#Use awk to do the heavy lifting.
#For lines with UID>=1000 (field 3) grab the home directory (field 6)
usrInfo=$(awk -F: '{if ($3 >= 1000) print $6}' < /etc/passwd)

IFS=$'\n' #Use newline as delimiter for for-loop
for usrLine in $usrInfo
do
  #Do processing on each line
  echo $usrLine
done

Or as suggested in the comments, getent passwd could be used for cases where /etc/passwd doesn't have the full list. Kudos to Bratchley for that gem. :)

usrInfo=$(getent passwd | awk -F: '{if ($3 >= 1000) print $6}')

Note, however, that the check >= 1000 might need to be different on different OSes.

1

I just wrote the following using @gilles awesome answer as a template. Works well for me. Tested on Ubuntu 16.04 using zsh.

# get all users
getent passwd | while IFS=: read -r name password uid gid gecos home shell; do
    # only users that own their home directory
    if [ -d "$home" ] && [ "$(stat -c %u "$home")" = "$uid" ]; then
        # only users that have a shell, and a shell is not equal to /bin/false or /usr/sbin/nologin
        if [ ! -z "$shell" ] && [ "$shell" != "/bin/false" ] && [ "$shell" != "/usr/sbin/nologin" ]; then
            echo "$name's home directory is $home using shell $shell"
        fi
    fi
done

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.