Jak sprawdzić, czy plik istnieje w awk? Błąd [-d 'nazwa_pliku]

10

Próbuję wygenerować listę użytkowników, którzy mają zestaw katalogów domowych, który nie istnieje. Wydaje się, że powinienem być w stanie to zrobić za pomocą awk, ale coś jest nie tak z moją składnią.

Ciągle mówi mi „nieprawidłowa składnia” w]. Co ja robię źle?

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

Ostateczny kod, którego prawdopodobnie skończę, to:

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

Mam tutaj podobne pytanie .

Doug
źródło

Odpowiedzi:

14

Możesz użyć

Układ (poleceń) 
    wykonać polecenie systemu operacyjnego polecenia i
    powróć do programu awk. Zwraca status wyjścia polecenia . 

na przykład:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
źródło
Z jakiegoś powodu nie mogłem tego uruchomić, więc musiałem to zrobić: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) print 0 $ } '
Alexander Kjäll,
1
@ AlexanderKjäll - ten test na istnienie katalogu - jeśli próbujesz sprawdzić, czy istnieje zwykły plik , oczywiste jest, że powyższy kod nie powiedzie się ...
don_crissti
3
To jest bardzo niebezpieczne! W zależności od danych wejściowych mogą być wykonywane dowolne polecenia . Na przykład wykonuje następujące /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Tino
6

Nie sądzę, że [ -d ]to jest awkcoś, to jest sprawa skorupy. Zamiast tego zrobiłbym to w ten sposób:

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

Oczywiście, jak bardzo słusznie zauważył @Janis, możesz zrobić wszystko w powłoce:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
terdon
źródło
Ok, myślę, że tak naprawdę chcę przekazać 6 $ jako reż i zrobić -d $ reż, ale to bardzo pomaga. Teraz muszę tylko wymyślić, jak wykonać echo „bez wyników”, jeśli nie zostaną znalezione. Dziękuję Ci!
Doug
2
Zauważ, że awktak naprawdę nie jest potrzebne; skoro i tak zapętlasz powłokę, możesz po prostu to zrobić while IFS=: read -r name x uid x x x shell rest ; do ... ; done </etc/passwd.
Janis,
@Doug Po prostu zrób awk -F: '{print $6}' /etc/passwd | while read dir; do [ -d "$dir" ] || echo "no results for $dir"; done.
terdon
@Janis rzeczywiście, dobra uwaga, odpowiedź została zredagowana.
terdon
Jak zauważa @terdon, w [ -d "$6"]rzeczywistości jest to składnia bash, a nie awk. Że [wygląda jak normalny składni, ale (w jednym z bash / lepszych weirdnesses Linuksa) to właściwie synonim dla testprogramu wykonywalnego (a może bash wbudowanych w wersji niej, a po prostu być dziwniejsze, bash wymaga dopasowania ]tego nie robi nie rób niczego, o czym jestem świadomy, poza wprowadzaniem cię w błąd, aby myśleć, że całość jest naprawdę składnią, a nie programem). W każdym razie nie jest to coś, o czym wie awk. Właśnie dlatego potrzebujesz system()funkcji, aby uzyskać do niej dostęp, odwołując się do niej w kontekście bash, w którym jest to zrozumiałe
Joe
6

Możesz użyć getline :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Steven Penny
źródło
1
Jest to bezpieczne awk, ale niestety nie działa w przypadku katalogów.
Tino
5

Jeśli naprawdę używasz gawk(chociaż możesz nawk, lub mawkw takim przypadku nie będzie to miało zastosowania), możesz to zrobić natywnie, korzystając z jednego z ładowalnych rozszerzeń dostępnych od wersji 4.0. Używam gawk-4.1.x(wersja 4.0 miała zmienną składnię do ładowania rozszerzeń).

Ładowanie filefuncsrozszerzenia dodaje (między innymi) stat()funkcję:

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

Zobacz filefuncs(3am)stronę podręcznika, aby uzyskać szczegółowe informacje na temat tego rozszerzenia.

Uruchom z czymś takim jak:

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

Możesz potwierdzić, że Twój gawkplik binarny obsługuje rozszerzenia za pomocą:

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

Poza tym: posiada gawkrównież małą funkcję biblioteki do odczytu passwdpliku, możesz go wywołać w następujący sposób:

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

Wolę używać getentw systemach Linux / glibc, ponieważ obsługuje nsswitch.

pan. spuratic
źródło
1
Ciekawy. Nie wiedziałem, że gawkoferuje to wersja v4. Dzięki!
Tino
3

To prawie awk ...

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

Oto rozwiązanie, które

  • wykorzystuje gawki/bin/sh
  • sprawdza katalog za pomocą polecenia zewnętrznego
  • ale czy jest to bezpieczny sposób

Kod:

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

wyjaśnione:

  • IFS="" read -r dir; [ -d "$dir" ]; echo $?to kod powłoki, który odczytuje ścieżkę ze standardowego wejścia i wyświetla, 0jeśli jest to katalog, w przeciwnym razie1
  • print $6 |& cmdpotokuje nazwę pliku do polecenia. |&jest rozszerzeniem GNU-awk.
  • cmd |& getline x wczytuje dane wyjściowe polecenia do GNU-awk
  • close(cmd) kończy polecenie, aby następny wiersz mógł wykonać ponownie
  • if (x)wykonuje printtylko jeśli xnie jest 0(więc katalog nie istnieje)

Nie polecam jednak robić tego w ten sposób, ponieważ jest on bardzo powolny i niezdarny. Ale ten przepis jest bezpieczny , tak że nieregularny wkład nie może zaszkodzić. (Jest mało prawdopodobne, że /etc/passwdzawiera szkodliwe dane, ale być może ktoś chce użyć go z danymi z niezaufanego źródła).

Jeśli nie możesz użyć gawk, jest to niefortunne. W takim przypadku nie masz |&. Normalna awktylko można wykonać jedną z następujących trzech kryteriów:

  • print "data" | cmd; close(cmd): Potokuj dane do polecenia
  • getline data < cmd; close(cmd): Odczytywanie danych z polecenia
  • ret = system(cmd): Uzyskaj kod powrotu polecenia

„Normalny” awkpo prostu nie może potokować danych do skryptu i jednocześnie wyciągnąć z niego coś (przynajmniej nie znalazłem sposobu na to), więc potrzebujesz jakiegoś pliku pośredniego (pliku tymczasowego), który jest jeszcze bardziej niezręczny.

Ciekawym spostrzeżeniem jest to, że tak łatwe zadanie można wykonać również przy użyciu samej powłoki:

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

Nie potrzebujesz bash, każda normalna powłoka Bourne'a może wykonać powyższy kod.

Zauważ, że powyższy kod powłoki jest nieco bardziej skomplikowany niż naprawdę potrzebny, ale powinien być wskazówką, jak naprawdę z nim pracować (dla osób, które nie mają pełnego doświadczenia w działaniu powłoki).

Tino
źródło