Como fazer um loop sobre os usuários?

3

Como posso fazer um loop em todos os usuários em um script de shell?

Estou escrevendo um script de shell para executar a limpeza em um sistema. E se o script for executado como root, quero executar as ações solicitadas nos diretórios iniciais de todos os usuários.

Eu pensei que talvez pudesse passar as linhas em /etc/passwd ; no entanto, vejo mais entradas do que usuários vivos. Existe alguma maneira especial de eliminar usuários fictícios de /etc/passwd ?

Eu preferiria soluções adaptadas para o Bourne Shell (para portabilidade). Pseudocódigo e meras explicações também são bem-vindos.

    
por Sildoreth 28.04.2015 / 21:37

3 respostas

5

Não há comando padrão para enumerar todas as contas de usuário existentes. Na maioria das variantes Unix, /etc/passwd contém a lista de contas locais, sempre com o mesmo formato tradicional (cólon- colunas separadas). No Linux com o Glibc (ou seja, qualquer Linux não embutido), você pode usar o comando getent : getent passwd é semelhante a cat /etc/passwd , mas também inclui contas remotas (NIS, LDAP,…).

O snippet a seguir enumera as contas de usuário:

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

Filtrar os usuários de carne e osso não é possível de uma maneira completamente confiável, porque nada no banco de dados do usuário diz se um usuário é de carne e osso. (Mais: as contas de teste contam? As contas de convidados contam? Etc.) Aqui estão algumas heurísticas que você pode aplicar.

  • Você pode filtrar usuários cujo diretório pessoal não parece ser um diretório do sistema.

    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
    
  • Você pode testar se o usuário possui seu diretório pessoal. Esse é quase sempre o caso dos usuários de carne e osso, e geralmente não é o caso dos usuários do sistema, mas isso não é muito confiável porque alguns usuários do sistema possuem seu diretório pessoal. Além disso, é possível que o diretório pessoal de usuários de carne e osso não exista se for um diretório remoto que não está disponível atualmente, embora normalmente o diretório seja montado automaticamente. No Linux:

    if [ -d "$home" ] && [ "$(stat -c %u "$home")" = "$uid" ]; then
      echo "Likely flesh-and-blood"
    else 
      echo "Probably a system user"
    fi
    
  • Você pode testar se o usuário tem um shell que é um shell real. Mas muitos usuários do sistema também, o que é ainda menos confiável do que o diretório inicial.

  • Você pode testar se a conta tem uma senha. Isso não é totalmente confiável porque os usuários de carne e osso nem sempre têm uma senha (por exemplo, eles podem ter apenas chaves SSH). Ocasionalmente, as contas do sistema têm uma senha - em especial, a conta raiz geralmente tem. Como fazer isso depende da variante Unix e geralmente requer acesso root. No Linux com senhas shadow (o que é normal para o Linux), você precisa procurar em /etc/shadow pela senha.

    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
    
  • Muitos sistemas definem intervalos de UID para contas do sistema versus usuários de carne e osso. Os intervalos são dependentes do sistema: o padrão depende da variante e distribuição do Unix e geralmente pode ser configurado pelo administrador do sistema. Na maioria das distribuições do Linux, os intervalos são definidos em login.defs : UID_MIN to UID_MAX para usuários humanos, outros valores para contas do sistema.

Acho que o caminho do diretório pessoal é o indicador único mais confiável, mas você pode querer combiná-lo com outras heurísticas.

    
por 29.04.2015 / 01:12
0

Com base nos comentários sobre a pergunta e algumas experiências, aqui está o que eu criei ...

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

Ou, como sugerido nos comentários, getent passwd poderia ser usado para casos em que /etc/passwd não tem a lista completa. Muitos elogios para Bratchley para essa jóia. :)

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

Observe, no entanto, que a verificação >= 1000 pode precisar ser diferente em sistemas operacionais diferentes.

    
por 29.04.2015 / 00:32
0

Acabei de escrever o seguinte usando a @gilles resposta incrível como modelo. Funciona bem para mim. Testado no Ubuntu 16.04 usando 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
    
por 28.01.2017 / 04:18