如何循环用户?

如何循环用户?

如何在 shell 脚本中循环遍历所有用户?

我正在编写一个 shell 脚本来在系统上执行清理。如果脚本以 root 身份运行,我想在所有用户的主目录中执行请求的操作。

我想我也许可以循环遍历/etc/passwd;然而,我在那里看到的条目比活生生的用户还要多。有什么特殊的方法可以清除虚拟用户吗/etc/passwd

我更喜欢为 Bourne Shell 定制的解决方案(为了可移植性)。也欢迎伪代码和简单的解释。

答案1

没有标准命令可以枚举所有现有用户帐户。在大多数 Unix 变体上,/etc/passwd包含本地帐户列表,始终采用相同的传统格式(冒号分隔的列)。在带有 Glibc 的 Linux(即任何非嵌入式 Linux)上,您可以使用getent命令:getent passwd与 类似cat /etc/passwd,但还包括远程帐户(NIS、LDAP,...)。

以下代码片段枚举了用户帐户:

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

以完全可靠的方式过滤有血有肉的用户是不可能的,因为用户数据库中没有任何内容表明用户是否有血有肉。 (另外:测试帐户算不算?来宾帐户算不算?等等)以下是您可以应用的一些启发法。

  • 您可以过滤其主目录似乎不是系统目录的用户。

    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
    
  • 您可以测试用户是否拥有其主目录。对于实际用户来说几乎总是如此,对于系统用户通常不是这样,但这不是很可靠,因为某些系统用户确实拥有自己的主目录。此外,如果实际用户的主目录当前不可用,则它的主目录可能不存在,尽管通常该目录会自动挂载。在 Linux 上:

    if [ -d "$home" ] && [ "$(stat -c %u "$home")" = "$uid" ]; then
      echo "Likely flesh-and-blood"
    else 
      echo "Probably a system user"
    fi
    
  • 您可以测试用户是否拥有一个真正的 shell。但许多系统用户也这样做,因此它的可靠性甚至比主目录还要低。

  • 您可以测试该账户是否有密码。这并不完全可靠,因为实际用户并不总是有密码(例如他们可能只有 SSH 密钥)。有时系统帐户有密码——特别是 root 帐户经常有。如何执行此操作取决于 Unix 变体,并且通常需要 root 访问权限。在具有影子密码的 Linux 上(这对于 Linux 来说是正常的),您需要查找/etc/shadow密码。

    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
    
  • 许多系统为系统帐户和实际用户定义 UID 范围。这些范围取决于系统:默认值取决于 Unix 变体和发行版,通常可以由系统管理员配置。在大多数 Linux 发行版上,范围定义在login.defsUID_MIN对于UID_MAX人类用户,其他值用于系统帐户。

我认为主目录的路径是最可靠的单一指标,但您可能希望将其与其他启发式方法结合起来。

答案2

根据对问题的评论和一些实验,这是我想到的......

#!/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

或者按照评论中的建议,getent passwd可以用于/etc/passwd没有完整列表的情况。感谢布拉奇利的那颗宝石。 :)

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

但请注意,不同操作系统上的检查>= 1000可能需要不同。

答案3

我刚刚使用 @gilles Awesome 答案作为模板编写了以下内容。对我来说效果很好。使用 zsh 在 Ubuntu 16.04 上进行测试。

# 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

相关内容