在Linux上获取已定义用户列表的Shell脚本?

9
我做了这个,但是很糟糕:(例如,里面有魔法数字、文本解析.. 呸!)
awk -F: '{if($3 >= 1000 && $3 < 2**16-2) print $1}' /etc/passwd

这该怎么做才是正确的呢?

文本解析有什么问题吗? - jthill
6个回答

13

一些Unix系统不使用/etc/passwd,或者有未在其中指定的用户。您应该使用getent passwd而不是读取/etc/passwd

我的系统还有被禁用的用户,他们不能再使用其登录命令,而是将其设置为/bin/false/usr/sbin/nologin。您可能也希望将它们排除在外。

以下是适用于我的内容,包括arheops awk命令和ansgar的代码以从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

在我的家庭网络中运行得很好,尽管有LDAP用户、自动挂载的带有奇怪路径的主目录等问题。对于那些同时拥有LDAP和备用/etc/passwd条目的用户,它确实会产生重复,但除此之外,非常不错。 - Alex North-Keys
可以通过追加 | sort -u 来消除重复项。 - Ansgar Wiechers
可以通过在awk命令中使用临时数组来实现,也可以使用临时数组作为一部分。 - Adrian Frühwirth

2
/etc/login.defs中提取最小和最大用户ID,然后从/etc/passwd中选择ID在这些范围内的用户:
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

顺便提一下,if 是多余的...只需执行 '$3 >= min && $3 <= max{print $1}' - Adrian Frühwirth
1
这将会错过许多用户,在某些情况下,几乎所有使用LDAP、NIS和其他包含存储在/etc/passwd文件之外的数据库的用户机制的站点。 - Alex North-Keys
1
@AlexNorth-Keys 对啊,不过我以为他是在寻找本地用户。 - Ansgar Wiechers
一些疯狂的人也会将LDAP用于本地用户;-) 他没有指定是否仅使用/etc/passwd文件作为来源。 - Alex North-Keys

2

这里有另一种方法,只会产生一个外部程序getent(由@AnsgarWiechers建议),因此本地和网络密码数据库都将被使用。这种方法将forks的数量仅减少到getent本身的一个。然而,它的可移植性有些受限,需要bash4。

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. 你应该提到这需要 bash4,因此不是一个非常可移植的解决方案。 - Adrian Frühwirth

2

我不确定为什么你只做 > 1000,因为在redhat系统上它从500开始。

对我来说,这个awk脚本可以正常工作:

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

仅限使用密码的用户:

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

你是完全正确的,基数选择错误了。1000是一个愚蠢的“魔法数字”,这是我的错..但是..500也是吗?或者,它不是? - Robottinosino
不需要这个,您链接的文档说明了运算符按递增优先级排序,因此&&的优先级低于>=< - Adrian Frühwirth
对于Redhat来说,它是500。看起来似乎在499之后没有使用的服务。但实际上这只适用于RHEL。您可以根据系统设置添加类似于“grep /home”或“grep /bin/bash”的内容。此外,您还可以仅获取具有密码的用户-请参见上文。 - arheops
我相信基于Debian的系统从1000开始。 - tripleee

1
所以你只是想从 /etc/passwd 中获取所有用户的列表吗?如果是这样,我相信以下解决方案会更容易:
cut -d":" -f1 /etc/passwd

编辑:

如果你只需要用户自定义列表(而非系统用户),你可以使用以下其中之一:

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

^ 这假设您的系统对于用户定义的用户使用1000及以上的UID和GID

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

^ 这假设每个用户定义的用户都有一个主目录。

其他解决方案取决于更详细的标准和系统设置。


你的编辑中有很棒的想法,我学到了一些东西。不过看起来还是有点像 hack,是吧? - Robottinosino
@Robottinosino:不,那只是普通的Shell脚本 :) - mart1n
许多网站的用户主目录分散在不同的文件系统中,甚至跨越不同的主机网络 - 并且并非所有这些网站都为其创建 /home 元目录。例如,/net/server1/fs/d3/users/tom。 - Alex North-Keys

1
这是@StephenOstermiller答案的简化版,只需要两个进程就能完成。如果您熟悉awk中的NR==FNR习惯用法,我认为它更易读。请注意保留html标签。
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 -

两个输入中的不同分割模式有点瑕疵;也许你可以更优雅地修复它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接