¿Cómo verifica si existe un archivo dentro de awk? [-d 'nombre de archivo'] falla

10

Estoy tratando de generar una lista de usuarios que tienen un conjunto de directorios de inicio que no existe. Parece que debería poder hacer esto con awk, pero algo está mal con mi sintaxis.

Sigue diciéndome "Sintaxis inválida" en el]. ¿Qué estoy haciendo mal?

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

El código final que probablemente terminaré usando es:

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

Y tengo una pregunta relacionada aquí .

Doug
fuente

Respuestas:

14

Podrías usar

system (comando) 
    Ejecute el comando de comando del sistema operativo y luego
    regrese al programa awk. Estado de salida del comando de retorno . 

p.ej:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
fuente
No pude hacer que esto funcione por alguna razón, así que tuve que hacer esto: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) print $ 0 } '
Alexander Kjäll
1
@ AlexanderKjäll - esto prueba la existencia de un directorio - si está tratando de ver si existe un archivo normal, entonces es obvio que el código anterior fallará ...
don_crissti
3
¡Esto es extremadamente peligroso! Dependiendo de la entrada , se pueden ejecutar comandos arbitrarios . Por ejemplo, las siguientes ejecuciones /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Tino el
6

No creo que [ -d ]sea ​​una awkcosa, eso es una cáscara. En cambio, lo haría de esta manera:

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

Por supuesto, como señaló muy correctamente @Janis, puede hacer todo en el shell:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
terdon
fuente
Ok, creo que realmente quiero pasar $ 6 como dir y do -d $ dir, pero esto ayuda mucho. Ahora solo tengo que descubrir cómo repetir "sin resultados" si no se encuentra ninguno. ¡Gracias!
Doug
2
Tenga en cuenta que awkno se necesita realmente; ya que de todos modos puedes hacer un bucle en shell, también puedes hacerlo while IFS=: read -r name x uid x x x shell rest ; do ... ; done </etc/passwd.
Janis
@ Doug Solo hazlo awk -F: '{print $6}' /etc/passwd | while read dir; do [ -d "$dir" ] || echo "no results for $dir"; done.
terdon
@Janis de hecho, buen punto, respuesta editada.
terdon
Como señala @terdon, en [ -d "$6"]realidad es una sintaxis bash, no awk. Las [miradas como la sintaxis normal, pero (en uno de bash / mejores weirdnesses de Linux) en realidad es un sinónimo para el testprograma ejecutable (o tal vez una fiesta de versión integrada de la misma, y, sólo para estar más raro, fiesta requiere una coincidencia ]que doesn no haga nada de lo que tenga conocimiento que no sea engañarlo para que piense que todo es realmente sintaxis y no un programa). En cualquier caso, no es algo que awk sepa. Es por eso que necesita la system()función para acceder a ella al hacer referencia a ella en el contexto de bash donde se entiende
Joe
6

Puedes usar getline :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Steven Penny
fuente
1
Esto es seguro awk, pero desafortunadamente no funciona para directorios.
Tino
5

Si realmente está utilizando gawk(aunque puede estar utilizando nawk, o mawk, en cuyo caso esto no se aplica), puede hacerlo de forma nativa utilizando una de las extensiones cargables disponibles desde v4.0. Estoy usando gawk-4.1.x(v4.0 tenía una variación en la sintaxis para cargar extensiones).

Al cargar la filefuncsextensión se agrega (entre otras) una stat()función:

@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 la filefuncs(3am)página del manual para obtener detalles sobre esta extensión.

Corre con algo como:

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

Puede confirmar que su gawkbinario admite extensiones con:

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"])
}

Aparte: gawktambién viene con una pequeña función de biblioteca para leer el passwdarchivo, puede invocarlo como:

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

Prefiero usar getenten sistemas Linux / glibc ya que admite nsswitch.

Sr. púrpura
fuente
1
Interesante. No estaba al tanto de gawkv4 ofrece esto. ¡Gracias!
Tino
3

Es casi awk ...

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

Aquí hay una solución que

  • usos gawky/bin/sh
  • ¿El directorio verifica con algún comando externo?
  • pero lo hace de manera 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 $?es un código de shell que lee una ruta desde stdin y genera salidas 0si es un directorio, de lo contrario1
  • print $6 |& cmdcanaliza el nombre del archivo al comando. |&es una extensión GNU-awk.
  • cmd |& getline x lee la salida del comando en GNU-awk
  • close(cmd) finaliza el comando, por lo que la siguiente línea puede ejecutar uno nuevamente
  • if (x)ejecuta el printúnico if xis not 0(por lo que el directorio no existe)

Sin embargo, no recomiendo hacerlo de esta manera, porque es muy lento y torpe. Pero esta receta es segura , de modo que la entrada irregular no puede hacer daño. (Es poco probable que /etc/passwdcontenga datos dañinos, pero tal vez alguien quiera usarlo con datos de una fuente no confiable).

Si no puede usar gawk, esto es lamentable. En ese caso no tienes |&. Normal awksolo puede hacer uno de los siguientes tres:

  • print "data" | cmd; close(cmd): Canalizar datos en un comando
  • getline data < cmd; close(cmd): Leer datos de un comando
  • ret = system(cmd): Obtener el código de retorno del comando

"Normal" awksimplemente no puede canalizar datos en un script y obtener algo de él nuevamente al mismo tiempo (al menos no encontré una manera de hacerlo), por lo que necesita un archivo intermedio (archivo temporal) que sea aún más torpe.

La observación interesante es que una tarea tan fácil se puede hacer solo con el 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

No es necesario bash, cada Bourne-shell normal puede ejecutar el código anterior.

Tenga en cuenta que el código de shell anterior es un poco más elaborado de lo que realmente se necesita, pero esto será un indicador de cómo trabajar realmente con él (para personas que no tienen experiencia completa en cómo funciona shell).

Tino
fuente