Wie überprüfen Sie, ob eine Datei in awk vorhanden ist? [-d 'Dateiname'] schlägt fehl

10

Ich versuche, eine Liste von Benutzern zu erstellen, für die ein nicht vorhandenes Basisverzeichnis festgelegt ist. Es scheint, ich sollte dies mit awk tun können, aber etwas stimmt nicht mit meiner Syntax.

Es sagt mir immer wieder "Ungültige Syntax" an der]. Was mache ich falsch?

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

Der letzte Code, den ich wahrscheinlich verwenden werde, ist:

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

Und ich habe hier eine verwandte Frage .

Doug
quelle

Antworten:

14

Du könntest benutzen

System (Befehl) 
    Führen Sie den Befehl Befehl des Betriebssystems aus und dann
    kehre zum awk-Programm zurück. Beenden Sie den Beendigungsstatus des Befehls . 

z.B:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
quelle
Ich konnte dies aus irgendeinem Grund nicht zum Laufen bringen, also musste ich Folgendes tun: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) print $ 0 } '
Alexander Kjäll
1
@ AlexanderKjäll - dies testet auf die Existenz eines Verzeichnisses - wenn Sie versuchen zu sehen, ob eine reguläre Datei existiert, dann ist es offensichtlich, dass der obige Code fehlschlagen wird ...
don_crissti
2
Das ist extrem gefährlich! Abhängig von der Eingabe können beliebige Befehle ausgeführt werden . Zum Beispiel folgende Ausführungen /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Tino
6

Ich denke nicht, dass [ -d ]das eine awkSache ist, das ist eine Muschelsache. Ich würde es stattdessen einfach so machen:

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

Natürlich können Sie, wie von @Janis sehr richtig hervorgehoben, das Ganze in der Shell erledigen:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
terdon
quelle
Ok, ich denke, ich möchte tatsächlich $ 6 als dir übergeben und -d $ dir tun, aber das hilft sehr. Jetzt muss ich nur noch herausfinden, wie "keine Ergebnisse" wiedergegeben werden, wenn keine gefunden werden. Vielen Dank!
Doug
2
Beachten Sie, dass das awknicht wirklich benötigt wird; da du sowieso in der shell schleifst, kannst du das auch einfach machen while IFS=: read -r name x uid x x x shell rest ; do ... ; done </etc/passwd.
Janis
@ Doug Mach es einfach awk -F: '{print $6}' /etc/passwd | while read dir; do [ -d "$dir" ] || echo "no results for $dir"; done.
Terdon
@ Janis in der Tat, guter Punkt, Antwort bearbeitet.
Terdon
Wie @terdon bemerkt, [ -d "$6"]ist eigentlich Bash-Syntax, nicht awk. Das [sieht aus wie normale Syntax, aber (in einer der besseren Verrücktheiten von bash / Linux) ist es tatsächlich ein Synonym für das testausführbare Programm (oder vielleicht eine in bash integrierte Version davon, und um seltsamer zu sein, erfordert bash ein Matching ], das dies nicht tut Ich tue nichts, was mir bewusst ist, außer dich irrezuführen, um zu glauben, dass das Ganze wirklich Syntax und kein Programm ist. Auf jeden Fall weiß awk nichts darüber. Aus diesem Grund benötigen Sie die system()Funktion, um darauf zugreifen zu können, indem Sie sie im Kontext von Bash referenzieren, wo sie verstanden wird
Joe,
6

Sie können getline verwenden :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Steven Penny
quelle
1
Dies ist sicher in awk, funktioniert aber leider nicht für Verzeichnisse.
Tino
5

Wenn Sie wirklich verwenden gawk(obwohl Sie möglicherweise verwenden nawkoder mawkin diesem Fall nicht zutreffen), können Sie dies nativ mit einer der seit Version 4.0 verfügbaren ladbaren Erweiterungen tun. Ich verwende gawk-4.1.x(v4.0 hatte eine Variation der Syntax zum Laden von Erweiterungen).

Durch das Laden der filefuncsErweiterung wird (unter anderem) eine stat()Funktion hinzugefügt:

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

Weitere filefuncs(3am)Informationen zu dieser Erweiterung finden Sie in der Manpage.

Laufen Sie mit so etwas wie:

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

Sie können bestätigen, dass Ihre gawkBinärdatei Erweiterungen unterstützt, mit:

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

Nebenbei: gawkKommt auch mit einer kleinen Bibliotheksfunktion zum Lesen der passwdDatei, können Sie sie wie folgt aufrufen:

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

Ich bevorzuge die Verwendung getentauf Linux / glibc-Systemen, da es nsswitch unterstützt.

mr.spuratic
quelle
1
Interessant. Mir war nicht bekannt, dass gawkv4 dies anbietet. Vielen Dank!
Tino
3

Es ist fast awk ...

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

Hier ist eine Lösung, die

  • verwendet gawkund/bin/sh
  • führt die Verzeichnisprüfung mit einem externen Befehl durch
  • aber macht es einen sicheren Weg

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

erklärt:

  • IFS="" read -r dir; [ -d "$dir" ]; echo $?ist ein Shell-Code, der einen Pfad aus stdin liest und ausgibt, 0wenn es sich um ein Verzeichnis handelt1
  • print $6 |& cmdleitet den Dateinamen an den Befehl weiter. |&ist eine GNU-awk-Erweiterung.
  • cmd |& getline x liest die Ausgabe des Befehls in GNU-awk
  • close(cmd) Beendet den Befehl, damit die nächste Zeile einen erneut ausführen kann
  • if (x)führt das printnur aus wenn xnicht 0(also existiert das Verzeichnis nicht)

Ich empfehle jedoch nicht, dies so zu tun, da es sehr langsam und ungeschickt ist. Aber dieses Rezept ist sicher , so dass unregelmäßige Eingabe nicht schaden kann. (Es ist unwahrscheinlich, dass /etc/passwdschädliche Daten enthalten sind, aber vielleicht möchte jemand sie mit Daten aus einer nicht vertrauenswürdigen Quelle verwenden.)

Wenn Sie nicht verwenden können gawk, ist dies unglücklich. In diesem Fall haben Sie nicht |&. Normal awkkann nur eine der folgenden drei Aufgaben ausführen:

  • print "data" | cmd; close(cmd): Pipe Daten in einen Befehl
  • getline data < cmd; close(cmd): Daten aus einem Befehl lesen
  • ret = system(cmd): Holen Sie sich den Rückkehrcode des Befehls

"Normal" awkkann einfach keine Daten in ein Skript leiten und gleichzeitig wieder etwas daraus herausholen (zumindest habe ich keinen Weg dafür gefunden), daher benötigen Sie eine Zwischendatei (tempfile), die noch ungeschickter ist.

Die interessante Beobachtung ist, dass eine so einfache Aufgabe auch nur mit der Schale erledigt werden kann:

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

Sie brauchen nicht bash, jede normale Bourne-Shell kann den obigen Code ausführen.

Beachten Sie, dass der obige Shell-Code etwas ausgefeilter ist als wirklich benötigt, aber dies soll ein Hinweis darauf sein, wie man wirklich damit arbeitet (für Leute, die nicht vollständig mit der Funktionsweise von Shell vertraut sind).

Tino
quelle