id

The account a process is running as, and its groups

Updated 2026-09-28

id prints the credentials the kernel checks against, which can differ from what the prompt shows and from what /etc/passwd records: the uid, the primary group and the supplementary groups, each as a number with its name in brackets.

Given a username, it reads the account database instead of the running process, so id tips-ops reports what that account is entitled to, and a bare id reports what this process actually holds. The gap between them is where most of the confusion lives: a process is given its group list when it starts and never asks again, so a shell that was open before someone ran usermod -aG keeps the old list until it exits.

id -u is the usual root test at the top of an install script, because it prints the number the kernel checks. The other test you will meet is [ "$USER" = root ]. $USER is an environment variable holding the login name, filled in when the session starts, and it is only a variable: any account can run USER=root ./install.sh and the script will believe it. whoami asks the kernel rather than the environment, but it prints a name rather than the number, and it knows only the effective uid, never the real one.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

the accounts, and a copy of id that runs as root The same accounts as getent, and the first two are the ones managing-users starts from, so the three pages name the same people. tips-ops carries tips-deploy as a supplementary group and tips-site carries it as its primary one, which is the difference -g and -G are read for. id-setuid is a copy of /usr/bin/id owned by root with the setuid bit set: every ordinary command runs with its real and effective uid equal, so there is no way to show the two apart without a program that has them apart.

tips-dev:x:1001:1001:Deployment account:/home/tips-dev:/bin/bash
tips-ops:x:1002:1002:Operations account:/home/tips-ops:/bin/bash
tips-site:x:1010:4000:Website account:/home/tips-site:/bin/bash
tips-deploy:x:4000:tips-ops
-rwsr-xr-x root id-setuid
16 outputs, collapsed by default

The three parts of the line

Every field is printed twice, once as the number the kernel uses and once as the name a person reads. A flag narrows the output to one of them, which is what makes id usable in a script as well as at a prompt.

See everything about an account at once

id tips-ops

The uid, then the primary group, then every group the account is in including the primary one. This is the first command to run when a permission refusal does not make sense, because it tells you what the account is rather than what any one file allows.

Show output
uid=1002(tips-ops) gid=1002(tips-ops) groups=1002(tips-ops),100(users),4000(tips-deploy)

Get the uid on its own

id -u tips-ops

A bare number and a newline, the form a script wants. Every field flag works this way: no brackets, no label, nothing to parse.

Show output
1002

Turn a number back into a name

id -un 1002

The argument is a uid here rather than a name, and -n prints the result as a name, so the pair reads a number back into an account. Useful on output that carries bare uids, such as ls -n or a find -printf '%U'.

Show output
tips-ops

Tell the primary group from the rest

id -g tips-ops; id -G tips-ops

-g is the primary group alone, -G is all of them with the primary first. The primary is the one a new file is group-owned by, unless the directory's setgid bit says otherwise; the others grant access.

Show output
1002
1002 100 4000

Get the group names rather than the numbers

id -Gn tips-ops

-n applies to whichever field flag it is given, so -Gn is the whole group list by name. Space-separated on one line, which is worth knowing before writing a loop over it.

Show output
tips-ops users tips-deploy

Spot an account whose primary group is a shared one

id -gn tips-ops; id -gn tips-site

Debian gives each account a group of its own with the same name, which is what the first line shows. tips-site was made with --ingroup instead, so everything it creates is group-owned by tips-deploy and readable by the rest of the team without anybody running chgrp.

Show output
tips-ops
tips-deploy

Read the groups with the other command that prints them

groups tips-ops

groups is id -Gn with the account name and a colon in front, and it takes no options at all. Fine to read, but awkward to parse, so a script that wants this list should use id instead.

Show output
tips-ops : tips-ops users tips-deploy

Ask about an account that is not there

id nosuchuser; echo "exit $?"

The message goes to standard error and the status is 1, so id -u "$name" >/dev/null 2>&1 is a serviceable test for whether an account exists before a script tries to create it.

Show output
id: 'nosuchuser': no such user
exit 1

What the current process is running as

With no argument at all, id stops reading the database and reports the credentials of the process asking. These examples run as root, so the first is root's own line and the second borrows an ordinary account to stand next to it.

See root's line

id

Root is uid 0 in group 0, and on a stock Debian system it is in no supplementary groups at all. It needs none: the kernel lets uid 0 read and write whatever a file's mode says, so no group is consulted.

Show output
uid=0(root) gid=0(root) groups=0(root)

See an ordinary account's

runuser -u user -- id

runuser starts a process as another account without a password, which only root may do. The group list here was built from the database at the moment the process started and is never rebuilt, which is the subject of the last section.

Show output
uid=1000(user) gid=1000(user) groups=1000(user)

Test for root the way an install script does

[ "$(id -u)" -eq 0 ] && echo "running as root"

The uid is what the kernel checks, so it is what to test. -eq compares numbers, so the test holds whatever uid 0 happens to be called.

Show output
running as root

See why a script should not trust $USER

runuser -u user -- env USER=root sh -c '[ "$USER" = root ] && echo "\$USER says root"; id -u'

An ordinary account sets USER to root for one command, and the variable test believes it. id -u in the same shell still prints 1000, because it asks the kernel. Nothing stops anyone doing this, so a script that decides what it may do from $USER can be talked into it.

Show output
$USER says root
1000

Real against effective, and a group list that has gone stale

A process carries two uids. The real one is who started it and the effective one is what gets checked, and they are equal until a program with the setuid bit sets them apart. The examples here use id-setuid, which the page's setup script makes: a copy of /usr/bin/id, owned by root, with that bit set, so it shows its own uids while running as root.

Watch a fourth field appear

runuser -u user -- ./id-setuid

./id-setuid is that copy, in the page's working directory. user starts it, and it runs as root: id prints euid only when it differs from uid, so the field appearing at all is the thing to look for. Everywhere else on this page the two are equal, which is why it has not been printed.

Show output
uid=1000(user) gid=1000(user) euid=0(root) groups=1000(user)

Ask for the real uid rather than the effective one

runuser -u user -- ./id-setuid -u; runuser -u user -- ./id-setuid -ur

-u gives the effective uid, the one the kernel checks when the program opens a file. -r gives the real uid, the account that started it. A setuid program needs both, and passwd is the everyday case: it is setuid root, so its effective uid is allowed to write /etc/shadow, and it reads its real uid to find out whose password it is changing. That is why an ordinary account can run it and still change only its own.

Show output
0
1000

Watch a running shell miss a group it was just given

runuser -u tips-dev -- sh -c 'sleep 1; id -Gn' > shell-view.txt & sleep 0.2; usermod -aG tips-deploy tips-dev; wait; cat shell-view.txt; id -Gn tips-dev

A shell starts, usermod adds a group while it is still running, and the two lines disagree: the first is what that shell holds, the second is what the database now says. The sleep is what makes the order certain rather than a race. This is why the advice after adding somebody to a group is to log out and back in, and why id without an argument is the one to trust when a permission is refused.

Show output
tips-dev users
tips-dev users tips-deploy

Ask for the SELinux context on a kernel without one

id -Z; echo "exit $?"

Debian enables AppArmor by default rather than SELinux, so -Z has no context to report and says so. A script carrying this flag from a Red Hat machine fails here with status 1.

Show output
id: --context (-Z) works only on an SELinux-enabled kernel
exit 1