Objective: Check in /etc/shadow if user password is locked, i.e. if the first character in the 2nd field in /etc/shadow, which contains the user's hashed password, is an exclamation mark ('!')

Desired output: a variable named $disabled containing either 'True' or 'False'

Username is in the $uname varable and I do something like this:

disabled=`cat /etc/shadow |grep $uname |awk -F\: '{print$2}'`
# I now have the password and need one more pipe into the check for the character
# which is where I'm stuck. I would like to do like (in PHP syntax):
| VARIABLE=="!"?"True":"False"`

This is a fragment of a script that will be run by Cron with root permissions, so there is access to all desirable information.

  • 4
    Bear in mind that not all operating systems have a "shadow" file, and those that do differ in the flag conventions for "locked" accounts. This question is actually specific, per the reference to !, to the shadow password mechanism in Linux operating systems.
    – JdeBP
    Commented Nov 1, 2018 at 13:44
  • 2
    Only until those distributions adopt replacement systems such as tcb from OpenWall or an implementation of Daniel Rench's userdir, or someone decides to employ LDAP. (-:
    – JdeBP
    Commented Nov 1, 2018 at 14:02
  • 2
    Useless use of cat; grep can take a file as a command line argument. Commented Nov 1, 2018 at 21:48
  • 2
    Useless use of grep too; awk is perfectly capable of finding a string in a file.
    – mustaccio
    Commented Nov 1, 2018 at 22:07
  • 2
    @PatrickMevzek I'm guessing DavDav meant to ask if the first character of the field that normally stores the password hash (not the password itself) is a !. So, you're technically right, but that seems like a typo in the question rather than a flaw in the underlying intent.
    – David Z
    Commented Nov 1, 2018 at 23:16

4 Answers 4


Don't parse the shadow file manually

Parsing such files is fragile if you fail to account for all eventualities (for example, disabled passwords are often encoded as a single *; do other solutions deal with that?).

Additionally, authentication may not happen through shadow (but instead through NIS or ldap or who knows what).There are standard tools that will deal with all this for you. In this case, passwd:

-S, --status Display account status information. The status information consists of 7 fields. The first field is the user's login name. The second field indicates if the user account has a locked password (L), has no password (NP), or has a usable password (P). The third field gives the date of the last password change. The next four fields are the minimum age, maximum age, warning period, and inactivity period for the password. These ages are expressed in days.

So passwd -S | cut -d ' ' -f 2 will yield what you need. A simple if/then will translate it to your desired variable:

if [ "$(passwd -S "$USER" | cut -d ' ' -f 2)" = "P" ]

The same applies to locking a user's password; this is preferably done through usermod (--lock option), not editing shadow manually.

  • Also, you need root permissions to read the shadow file (the permissions requirement is the main reason the file exists after all). Commented Nov 1, 2018 at 16:43
  • 2
    @SimonRichter As far as I can gather, you need root to run passwd -S for other users, too. Not for your own user, though.
    – marcelm
    Commented Nov 1, 2018 at 17:16
  • 3
    FWIW, it (incorrectly) reports L for my own (Kerberos with LDAP) account here, I suppose because it can't get hold of the password hash. Chances are passwd -S is not going to be useful for many authentication schemes beyond /etc/shadow anyway. Commented Nov 1, 2018 at 19:25
  • @StéphaneChazelas Hmm, that's a shame... I'd rather hoped they would map some LDAP attribute to that field. (I haven't used LDAP in a while, so I couldn't check that it actually did)
    – marcelm
    Commented Nov 1, 2018 at 19:52
  • 2
    getent passwd $ACCOUNT gets the entry from whatever source behind is used for authentication. Commented Nov 1, 2018 at 22:51

Why not just do it all with awk?

awk -F: '/<username>/ {if(substr($2,1,1) == "!"){print "True"} else {print "False"}}' /etc/shadow
  • 1
    This was exactly what I was after. My script is now completed - thanks.
    – DavDav
    Commented Nov 1, 2018 at 13:28
  • Well, this needs to create a sub process. My proposal works in the shell and does not need a sub-process.
    – schily
    Commented Nov 1, 2018 at 13:33
  • 1
    @schily no need for a script wrapper round awk, if you really wanted you could make it an alias...
    – hardillb
    Commented Nov 1, 2018 at 13:34
  • /<username>/ matches on whole records that match the <username> extended regexp. So for instance, j.doe would match on a record that has b.judoer in the user field or j7doe in the password hash field. Commented Nov 1, 2018 at 19:29
  • It's just a placeholder to show sheet to put the username
    – hardillb
    Commented Nov 1, 2018 at 19:32
U=$user LC_ALL=C awk -F: < /etc/shadow '
  $1 "" == ENVIRON["U"] {
    user_found = 1
    if ($2 ~ /^!/) {
      print "True"
      exit 0
    } else {
      print "False"
      exit 1
  END {
    if (!user_found) {
      print "False"
      print "User "ENVIRON["U"]" not found" > "/dev/stderr"
      exit 2

$1 "" == ENVIRON["U"] compares the first field with ENVIRON["U"] lexically. Without the "", the fields could end-up being compared numerically if they look like numbers (causing inf to match against INF or Infinity for instance).

Without LC_ALL=C, since some awk implementations use strcoll() for the == lexical comparison, it could end-up checking wrong entries for user names that sort the same.


A user is locked when the passwd field is the string *LK*, but you cannot check this as /etc/shadow is only readable by root for security reasons.

If permissions are not an issue, try this:

while IFS=: read USER PW REST; do 
    if [ "$USER" = "$uname" ]; then 
            if [ "$PW" = "*LK*" ]; then 
                    echo "$uname" Locked 
done < /etc/shadow 

Edit: Moved IFS=: to make the code simpler

  • 1
    I have edited my question - permissions is not an issue. Last time I checked, an exclamation mark in first position in the password means that the password is locked.
    – DavDav
    Commented Nov 1, 2018 at 13:20
  • schillix.sourceforge.net/man/man4/shadow.4.html Anything that cannot be an encrypted passwd makes the account unusable, but if you use the incorrect pattern, this may be missinterpreted and please note: this file is shared between different platforms via e.g. NIS.
    – schily
    Commented Nov 1, 2018 at 13:22
  • 4
    *LK* is only for SysV systems, passwd -l (for locking) on Linux-based does use !. passwd -l doesn't make the account unusable, it only disables password authentication. Commented Nov 1, 2018 at 13:26
  • 2
    Indeed, the question's objective says "is the first character in user's password an exclamation mark ('!')"
    – Jeff Schaller
    Commented Nov 1, 2018 at 13:30
  • Using IFS=: read -r user pw rest avoids having to save and restore $IFS. See also -r to avoid the backslash processing (backslash not expected in /etc/shadow though) Commented Nov 1, 2018 at 13:51

You must log in to answer this question.

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