Bagaimana Anda memeriksa apakah ada file dalam awk? [-d 'filename'] gagal

10

Saya mencoba membuat daftar pengguna yang memiliki set direktori home yang tidak ada. Sepertinya saya harus dapat melakukan ini dengan awk, tetapi ada yang salah dengan sintaks saya.

Itu terus memberi tahu saya "Sintaks Tidak Valid" di]. Apa yang saya lakukan salah?

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

Kode terakhir yang mungkin akan saya gunakan adalah:

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

Dan saya punya pertanyaan terkait di sini .

Doug
sumber

Jawaban:

14

Anda bisa menggunakannya

system (command) 
    Jalankan perintah perintah sistem operasi lalu
    kembali ke program awk. Status keluar perintah kembali . 

misalnya:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
sumber
Saya tidak bisa menjalankan ini karena suatu alasan, jadi saya harus melakukan ini: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) cetak $ 0 } '
Alexander Kjäll
1
@ AlexanderKjäll - tes ini untuk keberadaan direktori - jika Anda mencoba untuk melihat apakah ada file biasa maka jelas bahwa kode di atas akan gagal ...
don_crissti
3
Ini sangat berbahaya! Tergantung pada input, perintah sewenang-wenang dapat dijalankan . Misalnya dijalankan berikut /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Tino
6

Saya tidak berpikir itu [ -d ]adalah awksesuatu, itu masalah shell. Saya hanya akan melakukannya dengan cara ini sebagai gantinya:

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

Tentu saja, sebagaimana ditunjukkan dengan sangat tepat oleh @Janis, Anda dapat melakukan semuanya di shell:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
terdon
sumber
Ok, saya pikir saya benar-benar ingin melewatkan $ 6 sebagai dir, dan melakukan -d $ dir, tetapi ini sangat membantu. Sekarang saya hanya perlu mencari cara untuk menggema "tidak ada hasil" jika tidak ada yang ditemukan. Terima kasih!
Doug
2
Perhatikan bahwa awksebenarnya tidak diperlukan; karena Anda tetap menggunakan shell, Anda juga bisa melakukannya 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 memang, poin bagus, jawaban diedit.
terdon
Sebagai catatan @terdon, [ -d "$6"]sebenarnya sintaks bash, bukan awk. The [terlihat seperti sintaks normal, tapi (di salah satu bash / weirdnesses Linux lebih baik) itu sebenarnya sinonim untuk testprogram executable (atau mungkin bash built-in versi itu, dan, hanya untuk menjadi aneh, bash memerlukan pencocokan ]yang doesn dapat melakukan apa pun yang saya ketahui selain untuk menyesatkan Anda untuk berpikir bahwa semuanya benar-benar sintaks dan bukan program). Bagaimanapun, itu bukan sesuatu yang diketahui oleh orang awk. Itu sebabnya Anda memerlukan system()fungsi untuk mengaksesnya dengan merujuknya dalam konteks bash di mana ia dipahami
Joe
6

Anda dapat menggunakan getline :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Steven Penny
sumber
1
Ini aman awk, tetapi sayangnya tidak berfungsi untuk direktori.
Tino
5

Jika Anda benar-benar menggunakan gawk(meskipun Anda mungkin menggunakan nawk, atau mawk, dalam hal ini tidak akan berlaku), Anda dapat melakukan ini native menggunakan salah satu ekstensi loadable tersedia sejak v4.0. Saya menggunakan gawk-4.1.x(v4.0 memiliki variasi pada sintaks untuk memuat ekstensi).

Memuat filefuncsekstensi menambahkan (antara lain) stat()fungsi:

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

Lihat filefuncs(3am)halaman manual untuk detail tentang ekstensi ini.

Jalankan dengan sesuatu seperti:

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

Anda dapat mengonfirmasi bahwa gawkbiner Anda mendukung ekstensi dengan:

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

Selain: gawkjuga dilengkapi dengan fungsi perpustakaan kecil untuk membaca passwdfile, Anda dapat memanggilnya seperti:

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

Saya lebih suka menggunakan getentsistem Linux / glibc karena mendukung nsswitch.

mr.spuratic
sumber
1
Menarik. Saya tidak mengetahui gawkpenawaran v4 ini. Terima kasih!
Tino
3

Ini hampir ...

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

Berikut ini solusinya

  • menggunakan gawkdan/bin/sh
  • direktori memeriksa dengan beberapa perintah eksternal
  • tetapi apakah itu cara yang aman dan aman

Kode:

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

menjelaskan:

  • IFS="" read -r dir; [ -d "$dir" ]; echo $?adalah kode shell yang membaca path dari stdin dan menghasilkan 0jika itu adalah direktori, yang lain1
  • print $6 |& cmdpipa nama file ke perintah. |&adalah ekstensi GNU-awk.
  • cmd |& getline x membaca output dari perintah ke dalam GNU-awk
  • close(cmd) menyelesaikan perintah, sehingga baris selanjutnya dapat menjalankan satu lagi
  • if (x)mengeksekusi printhanya jika xtidak 0(jadi direktori tidak ada)

Saya tidak menyarankan untuk melakukannya dengan cara ini, karena sangat lambat dan kikuk. Tetapi resep ini aman , sehingga input tidak teratur tidak membahayakan. (Sepertinya tidak /etc/passwdberisi data berbahaya, tetapi mungkin seseorang ingin menggunakannya dengan data dari sumber yang tidak tepercaya).

Jika Anda tidak dapat menggunakan gawk, ini sangat disayangkan. Dalam hal ini Anda tidak punya |&. Normal awkhanya dapat melakukan salah satu dari tiga berikut:

  • print "data" | cmd; close(cmd): Pipa data menjadi perintah
  • getline data < cmd; close(cmd): Baca data dari sebuah perintah
  • ret = system(cmd): Dapatkan kode pengembalian perintah

"Normal" awktidak bisa menyalurkan data ke dalam skrip dan mendapatkan sesuatu dari itu pada saat yang sama (setidaknya saya tidak menemukan cara untuk itu), jadi Anda memerlukan beberapa file perantara (tempfile) yang bahkan lebih canggung.

Pengamatan yang menarik adalah, bahwa tugas yang mudah dapat dilakukan dengan shell sendiri, juga:

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

Anda tidak perlu bash, setiap shell Bourne-normal dapat mengeksekusi kode di atas.

Perhatikan bahwa kode shell di atas sedikit lebih rumit daripada yang benar-benar dibutuhkan, tetapi ini akan menjadi petunjuk tentang cara untuk benar-benar bekerja dengannya (untuk orang-orang yang tidak sepenuhnya berpengalaman dalam cara shell bekerja).

Tino
sumber