Como você verifica se existe um arquivo no awk? [-d 'filename'] falhando

10

Estou tentando gerar uma lista de usuários que possuem um conjunto de diretórios pessoais que não existe. Parece que eu deveria conseguir fazer isso com o awk, mas algo está errado com a minha sintaxe.

Ele continua dizendo "Sintaxe inválida" no]. O que estou fazendo errado?

awk -F: '{ if(![ -d "$6"]){ print $1 " " $3 " " $7}}' /etc/passwd

O código final que provavelmente vou usar é:

awk -F: '{if(system( "[ -d " $6 " ]") == 1 && $7 != "/sbin/nologin" ) {print "The directory " $6 " does not exist for user " $1 }}' /etc/passwd

E eu tenho uma pergunta relacionada aqui .

Doug
fonte

Respostas:

14

Você poderia usar

system (command) 
    Execute o comando de comando do sistema operacional e, em seguida,
    retorne ao programa awk. Retornar o status de saída do comando . 

por exemplo:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
fonte
Não consegui fazer com que isso funcionasse por algum motivo, então tive que fazer o seguinte: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) print $ 0 } '
Alexander Kjäll 25/03
1
@ AlexanderKjäll - este testes para a existência de um diretório - se você está tentando ver se um arquivo regular existe, então é óbvio que o código acima irá falhar ...
don_crissti
3
Isso é extremamente perigoso! Dependendo da entrada, comandos arbitrários podem ser executados . Por exemplo, as seguintes execuções /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Tino
6

Eu não acho que isso [ -d ]é uma awkcoisa, é uma coisa de casca. Eu apenas faria desta maneira:

awk -F: '{ print $1,$3,$7}' /etc/passwd | 
    while read name uid shell; do 
        [ -d "/home/$name" ] || echo "$name $uid $shell"; 
    done

Obviamente, como bem apontado por @Janis, você pode fazer tudo no shell:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
terdon
fonte
Ok, acho que realmente quero passar $ 6 como dir e fazer -d $ dir, mas isso ajuda muito. Agora só tenho que descobrir como ecoar "sem resultados" se nenhum for encontrado. Obrigado!
Doug
2
Observe que awknão é realmente necessário; desde que você faça um loop no shell de qualquer maneira, você também pode fazer while IFS=: read -r name x uid x x x shell rest ; do ... ; done </etc/passwd.
Janis
@ Doug Apenas faça awk -F: '{print $6}' /etc/passwd | while read dir; do [ -d "$dir" ] || echo "no results for $dir"; done.
terdon
@ Janis de fato, bom ponto, resposta editada.
terdon
Como o @terdon observa, [ -d "$6"]na verdade é a sintaxe do bash, não o awk. Os [olhares como a sintaxe normal do, mas (em um dos festança / melhores estranhezas do Linux) é realmente um sinônimo para o testprograma executável (ou talvez uma festa de built-in versão dele, e, apenas para ser mais estranho, o bash exige uma correspondência ]que doesn faça qualquer coisa que eu saiba além de induzi-lo a pensar que a coisa toda é realmente sintaxe e não um programa). De qualquer forma, não é algo que a awk saiba. É por isso que você precisa da system()função para acessá-lo por referência a ele no contexto de festa, onde se entende
Joe
6

Você pode usar o getline :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Steven Penny
fonte
1
Isso é seguro awk, mas infelizmente não funciona para diretórios.
Tino
5

Se você realmente estiver usando gawk(embora possa estar usando nawk, ou mawk, nesse caso, isso não se aplicará), você pode fazer isso nativamente usando uma das extensões carregáveis disponíveis desde a v4.0. Estou usando gawk-4.1.x(v4.0 tinha uma variação na sintaxe para carregar extensões).

O carregamento da filefuncsextensão adiciona (entre outras) uma stat()função:

@load "filefuncs"
BEGIN {FS=":"}
(NF==7) {
   printf("user: %s %i %i\n",$1,$3,$4)
   rc=stat($6,fstat)
   err=ERRNO  # ERRNO is a string, not an int!
   if (rc<0) { 
       printf(" error: %s rc=%i %s\n",$6,rc,err)
   } else {
      if (fstat["type"]!="directory") 
        printf("  ENOTDIR: %s %s\n",$6,fstat["type"])
      if (fstat["uid"]!=$3) 
        printf("  uid mismatch: %s %i!=%i\n",$6,fstat["uid"],$3)
      if (fstat["gid"]!=$4) 
        printf("  gid mismatch: %s %i!=%i\n",$6,fstat["gid"],$4)
   }
}

Consulte a filefuncs(3am)página do manual para obter detalhes sobre esta extensão.

Execute com algo como:

gawk -f testhome.awk <(getent passwd)    # bash/zsh and glibc
gawk -f testhome.awk /etc/passwd

Você pode confirmar que seu gawkbinário suporta extensões com:

BEGIN { 
  if (!("api_major" in PROCINFO)) 
    printf("No extension API.\n")
  else
    printf("Extension API v%s.%s.\n",PROCINFO["api_major"],PROCINFO["api_minor"])
}

Além: gawktambém vem com uma pequena função de biblioteca para ler o passwdarquivo, você pode invocá-lo como:

gawk -i passwd.awk -- 'BEGIN { while(uu=getpwent()) {print uu;} endpwent(); }'

Eu prefiro usar getentem sistemas Linux / glibc, pois suporta nsswitch.

mr.spuratic
fonte
1
Interessante. Eu não estava ciente das gawkofertas v4 isso. Obrigado!
Tino
3

É quase estranho ...

perl -F: -ane 'if(!-d $F[5]){ print "$F[0] $F[2] $F[6]" }' /etc/passwd
JJoao
fonte
0

Aqui está uma solução que

  • usos gawke/bin/sh
  • o diretório verifica com algum comando externo
  • mas faz isso de uma maneira segura

Código:

gawk -F: '{
    cmd="IFS=\"\" read -r dir; [ -d \"$dir\" ]; echo $?";
    print $6 |& cmd;
    cmd |& getline x;
    close(cmd);
    if (x) print x " " $1 " " $3 " " $7
}' /etc/passwd

explicado:

  • IFS="" read -r dir; [ -d "$dir" ]; echo $?é um código de shell que lê um caminho do stdin e gera 0um diretório, caso contrário1
  • print $6 |& cmdcanaliza o nome do arquivo para o comando |&é uma extensão GNU-awk.
  • cmd |& getline x lê a saída do comando no GNU-awk
  • close(cmd) finaliza o comando, para que a próxima linha possa executar uma novamente
  • if (x)executa o printúnico se xnão estiver 0(portanto, o diretório não existe)

Porém, eu não recomendo fazê-lo dessa maneira, porque é muito lento e desajeitado. Mas esta receita é segura , de modo que entradas irregulares não podem causar danos. (É improvável que /etc/passwdcontenha dados prejudiciais, mas talvez alguém queira usá-los com dados de uma fonte não confiável).

Se você não pode usar gawk, isso é lamentável. Nesse caso, você não tem |&. Normal awkapenas pode executar um dos três seguintes:

  • print "data" | cmd; close(cmd): Canalize dados em um comando
  • getline data < cmd; close(cmd): Ler dados de um comando
  • ret = system(cmd): Obtenha o código de retorno do comando

"Normal" awksimplesmente não pode canalizar dados para um script e obter algo novamente, ao mesmo tempo (pelo menos não encontrei uma maneira de fazê-lo), então você precisa de algum arquivo intermediário (tempfile) que é ainda mais desajeitado.

A observação interessante é que uma tarefa tão fácil também pode ser realizada apenas com o shell:

dump_problems()
{
have=0;
while IFS=: read -r user pw id grp gcos dir shl;
do
  [ -d "$dir" ] && continue;
  echo "$user $id $shl";
  have=1;
done </etc/passwd;
return $have;
}

dump_problems && echo ALL OK >&2 || echo "Problems were found" >&2

Você não precisa bash, cada Bourne-shell normal pode executar o código acima.

Observe que o código do shell acima é um pouco mais elaborado do que o realmente necessário, mas isso deve ser um indicativo de como realmente trabalhar com ele (para pessoas que não têm muita experiência em como o shell funciona).

Tino
fonte