Как я могу обойти всех пользователей в сценарии оболочки?
Я пишу сценарий оболочки для выполнения очистки в системе. И если скрипт запускается от имени root, я хочу выполнить запрошенные действия в домашних каталогах всех пользователей.
Я думал, что мог бы перебрать строки в /etc/passwd
; однако я вижу там гораздо больше записей, чем есть живые, дышащие пользователи. Есть ли какой-то особый способ, с помощью которого я могу избавить фиктивных пользователей от /etc/passwd
?
Я бы предпочел решения, разработанные для Bourne Shell (для переносимости). Также приветствуются псевдокод и простые объяснения.
Нет стандартной команды для перечисления всех существующих учетных записей пользователей. В большинстве вариантов Unix /etc/passwd
содержит список локальных учетных записей, всегда с тем же традиционным форматом (столбцы, разделенные двоеточиями). В Linux с помощью Glibc (т.е. любой не встроенный 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
Вы можете проверить, есть ли у пользователя оболочка, которая является реальной оболочкой. Но многие пользователи системы также делают это, так что это даже менее надежно, чем домашний каталог.
Вы можете проверить, имеет ли учетная запись пароль. Это не полностью надежно, потому что пользователи плоти и крови не всегда имеют пароль (например, у них могут быть только ключи 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.defs
: UID_MIN
для UID_MAX
для пользователей пользователей, другие значения для системных учетных записей.
Я думаю, что путь к домашнему каталогу – самый надежный единственный индикатор, но вы можете объединить его с другими эвристиками.
Основываясь на комментариях к вопросу и некоторых экспериментах, вот что я придумал …
#!/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
может быть различной для разных ОС.
Я просто написал следующее, используя @gilles удивительный ответ в качестве шаблона. Хорошо работает для меня. Протестировано на Ubuntu 16.04 с использованием zsh.
# 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