Comment vérifiez-vous si un fichier existe dans awk? [-d 'nom de fichier'] a échoué

10

J'essaie de générer une liste d'utilisateurs qui ont un ensemble de répertoires personnels qui n'existe pas. Il semble que je devrais être capable de le faire avec awk, mais quelque chose ne va pas avec ma syntaxe.

Il continue de me dire "Syntaxe invalide" sur le]. Qu'est-ce que je fais mal?

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

Le code final que je vais probablement finir par utiliser est:

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

Et j'ai une question connexe ici .

Doug
la source

Réponses:

14

Vous pourriez utiliser

système (commande) 
    Exécuter la commande du système d'exploitation commande , puis
    revenir au programme awk. Retourne l' état de sortie de la commande . 

par exemple:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
la source
Je n'ai pas pu faire fonctionner cela pour une raison quelconque, j'ai donc dû le faire: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) print $ 0 } '
Alexander Kjäll
1
@ AlexanderKjäll - cela teste l'existence d'un répertoire - si vous essayez de voir si un fichier normal existe alors il est évident que le code ci-dessus échouera ...
don_crissti
3
C'est extrêmement dangereux! Selon l'entrée, des commandes arbitraires peuvent être exécutées . Par exemple, les exécutions suivantes /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Tino
6

Je ne pense pas que ce [ -d ]soit une awkchose, c'est une chose coquille. Je le ferais simplement de cette façon à la place:

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

Bien sûr, comme l'a très bien souligné @Janis, vous pouvez tout faire dans le shell:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
terdon
la source
Ok, je pense que je veux réellement passer 6 $ en dir et faire -d $ dir, mais cela aide beaucoup. Maintenant, je dois juste comprendre comment faire écho à "aucun résultat" si aucun n'est trouvé. Merci!
Doug
2
Notez que le awkn'est pas réellement nécessaire; puisque vous bouclez dans le shell de toute façon, vous pouvez aussi le faire while IFS=: read -r name x uid x x x shell rest ; do ... ; done </etc/passwd.
Janis
@Doug Just do awk -F: '{print $6}' /etc/passwd | while read dir; do [ -d "$dir" ] || echo "no results for $dir"; done.
terdon
@Janis en effet, bon point, réponse éditée.
terdon
Comme le note @terdon, [ -d "$6"]c'est en fait la syntaxe bash, pas awk. La [syntaxe ressemble à la normale, mais (dans l'une des meilleures bizarreries de bash / Linux), c'est en fait un synonyme du testprogramme exécutable (ou peut-être une version intégrée de bash de celui-ci, et, pour être plus étrange, bash nécessite une correspondance ]qui ne fait pas Je ne fais rien d'autre que de vous induire en erreur en vous disant que le tout est vraiment de la syntaxe et non un programme). En tout cas, ce n'est pas quelque chose que awk sait. C'est pourquoi vous avez besoin de la system()fonction pour y accéder en la référençant dans le contexte de bash où elle est comprise
Joe
6

Vous pouvez utiliser getline :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Steven Penny
la source
1
Ceci est sûr awk, mais ne fonctionne malheureusement pas pour les répertoires.
Tino
5

Si vous utilisez vraiment gawk(bien que vous puissiez utiliser nawk, ou mawk, dans ce cas, cela ne s'applique pas), vous pouvez le faire de manière native en utilisant l'une des extensions chargeables disponibles depuis la v4.0. J'utilise gawk-4.1.x(v4.0 avait une variation sur la syntaxe de chargement des extensions).

Le chargement de l' filefuncsextension ajoute (entre autres) une stat()fonction:

@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)
   }
}

Consultez la filefuncs(3am)page de manuel pour plus de détails sur cette extension.

Courez avec quelque chose comme:

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

Vous pouvez confirmer que votre gawkbinaire prend en charge les extensions avec:

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

À part: gawkest également livré avec une petite fonction de bibliothèque pour lire le passwdfichier, vous pouvez l'invoquer comme:

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

Je préfère utiliser getentsur les systèmes Linux / glibc car il prend en charge nsswitch.

Mr Spuratic
la source
1
Intéressant. Je n'étais pas au courant de la gawkv4 qui offre cela. Merci!
Tino
3

C'est presque génial ...

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

Voici une solution qui

  • utilise gawket/bin/sh
  • le répertoire vérifie-t-il avec une commande externe
  • mais le fait de manière sûre et sécurisée

Code:

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

expliqué:

  • IFS="" read -r dir; [ -d "$dir" ]; echo $?est un code shell qui lit un chemin depuis stdin et affiche 0s'il s'agit d'un répertoire, sinon1
  • print $6 |& cmdredirige le nom de fichier vers la commande. |&est une extension GNU-awk.
  • cmd |& getline x lit la sortie de la commande dans GNU-awk
  • close(cmd) termine la commande, afin que la ligne suivante puisse en exécuter une à nouveau
  • if (x)exécute le printseul if xn'est pas 0(donc le répertoire n'existe pas)

Je ne recommande pas de le faire de cette façon, car c'est très lent et maladroit. Mais cette recette est sûre , de sorte qu'une entrée irrégulière ne peut pas nuire. (Il est peu probable qu'il /etc/passwdcontienne des données nuisibles, mais peut-être que quelqu'un veut l'utiliser avec des données provenant d'une source non fiable).

Si vous ne pouvez pas l'utiliser gawk, c'est dommage. Dans ce cas, vous n'en avez pas |&. Normal awkne peut effectuer que l'une des trois actions suivantes:

  • print "data" | cmd; close(cmd): Canaliser des données dans une commande
  • getline data < cmd; close(cmd): Lire les données d'une commande
  • ret = system(cmd): Récupère le code retour de la commande

"Normal" awkne peut tout simplement pas canaliser des données dans un script et en extraire quelque chose en même temps (au moins, je n'ai pas trouvé de moyen), vous avez donc besoin d'un fichier intermédiaire (fichier temporaire) qui est encore plus maladroit.

L'observation intéressante est qu'une tâche aussi simple peut être effectuée avec le shell seul:

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

Vous n'avez pas besoin bash, chaque Bourne-shell normal peut exécuter le code ci-dessus.

Notez que le code shell ci-dessus est un peu plus élaboré que vraiment nécessaire, mais ce sera un pointeur sur la façon de vraiment travailler avec lui (pour les personnes qui ne sont pas pleinement expérimentées dans le fonctionnement du shell).

Tino
la source