9

I put this together, but it sucks: (e.g. magic numbers in there, text parsing.. boo!)

awk -F: '{if($3 >= 1000 && $3 < 2**16-2) print $1}' /etc/passwd

What's the proper way to do this?

1
  • What's wrong with text parsing? Commented May 20, 2013 at 11:17

6 Answers 6

13

Some unix systems don't use /etc/passwd, or have users that are not specified there. You should use getent passwd instead of reading /etc/passwd.

My system also has users that are disabled and can lo longer login with their login command set to /bin/false or /usr/sbin/nologin. You probably want to exclude them as well.

Here is what works for me including arheops awk command and ansgar's code to get the min and max from login.defs:

getent passwd | \
grep -vE '(nologin|false)$' | \
awk -F: -v min=`awk '/^UID_MIN/ {print $2}' /etc/login.defs` \
-v max=`awk '/^UID_MAX/ {print $2}' /etc/login.defs` \
'{if(($3 >= min)&&($3 <= max)) print $1}' | \
sort -u
Sign up to request clarification or add additional context in comments.

3 Comments

Works nicely on my home network, despite LDAP users, automounted home directories with strange paths, etc. It does produce duplicates for users who have both LDAP and fallback /etc/passwd entries, but otherwise, nice.
Duplicates can be eliminated by appending a | sort -u.
... or by using a temporary array as part of the awk command.
2

I am unsure why you do only > 1000, beacuase on redhat system it start from 500.

For me this awk script work ok:

awk -F: '{if(($3 >= 500)&&($3 <65534)) print $1}' /etc/passwd

Only uses with passwords:

awk -F: '{if(!(( $2 == "!!")||($2 == "*"))) print $1}' /etc/shadow 

4 Comments

You are totally right, the base was chosed incorrectly. 1000 is a stupid "magic number", my fault.. but then.. so is 500?! Or, is it not?
This is not needed, your linked document states the operators in order of increasing precedence so && does have a lower precedence than >= and <.
for redhat it is 500. Looks like no service used after 499. But that is actually only for RHEL. You can add something like "grep /home" or "grep /bin/bash" depend of your system settings. Also you can got only users with password - see above.
I believe Debian-based systems start at 1000.
2

Extract the minimum and maximum user IDs from /etc/login.defs and then select the users with IDs between these margins from /etc/passwd:

UID_MIN=$(awk '/^UID_MIN/ {print $2}' /etc/login.defs)
UID_MAX=$(awk '/^UID_MAX/ {print $2}' /etc/login.defs)

awk -F: -v min=$UID_MIN -v max=$UID_MAX '$3 >= min && $3 <= max{print $1}' /etc/passwd

4 Comments

On a side note, the if is superfluous...just do '$3 >= min && $3 <= max{print $1}'.
This will miss many, in some cases, nearly all users at sites using LDAP, NIS, and other user mechanisms that include databases stored outside of the /etc/passwd file.
@AlexNorth-Keys True. However, I was under the impression that he was looking for local users.
Some crazy folks use LDAP for local users too ;-) He didn't specify whether only the /etc/passwd file was to be used as the source.
2

Here's another approach that only spawns one external program, getent (suggested by @AnsgarWiechers) so that both local and networked passwd databases will be used. This one reduces the number of forks to just one for getent itself. Its portability is limited somewhat by requiring bash4, however.

get_users ()
{ 
    local IFS=$' \t#'
    while read var val ; do
        case "$var" in 
            UID_MIN) min="$val" ;;
            UID_MAX) max="$val" ;;
        esac
    done < /etc/login.defs
    declare -A users
    local IFS=:
    while read user pass uid gid gecos home shell; do
        if (( min <= uid && uid <= max )) && [[ ! $shell =~ '/(nologin|false)$' ]]; then
            users[$user]=1
        fi
    done < <(getent passwd 2>/dev/null)
    echo ${!users[@]}
}

1 Comment

+1. You should mention though that this requires bash4, so is not a very portable solution.
1

So you're just trying to get a list of all users from /etc/passwd? If so, I believe this would be an easier solution:

cut -d":" -f1 /etc/passwd

Edit:

In case you only want a list of user-defined users (not the system users), you can use one of these:

grep -E ":[0-9]{4,6}:[0-9]{4,6}:" /etc/passwd | cut -d: -f1

^ This assumes your system uses 1000 and up for UID and GID for user-defined users

grep /home /etc/passwd | cut -d: -f1

^ This assumes every user-defined user has a home directory.

Other solutions depend on more detailed criteria and your system settings.

3 Comments

Cool ideas in your edit. I learned something. Still looks like a hack though, doesn't it?
@Robottinosino: Nope, that's just regular shell scripting :)
Many sites have user home directories scattered across filesystems and often even across the network on different hosts - and not all of them create a /home meta directory for them. /net/server1/fs/d3/users/tom for example.
1

Here's a simplification of @StephenOstermiller's answer which gets it done with only two processes. I think it is easier to read, too (provided you are familiar with the awk NR==FNR idiom).

getent passwd |
awk 'NR==FNR { if ($1 ~ /^UID_(MIN|MAX)$/) m[$1] = $2; next }
{ split ($0, a, /:/);
  if (a[3] >= m["UID_MIN"] && a[3] <= m["UID_MAX"] && a[7] !~ /(false|nologin)$/)
    print a[1] }' /etc/login.defs -

The different split patterns in the two inputs is a bit of a wart; maybe you could fix that more elegantly somehow.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.