Как проверить, существует ли файл в awk? [-d 'имя файла'] не удалось

10

Я пытаюсь создать список пользователей, чей домашний каталог не существует. Кажется, я должен быть в состоянии сделать это с помощью awk, но что-то не так с моим синтаксисом.

Он продолжает говорить мне «Неверный синтаксис» в]. Что я делаю неправильно?

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

Последний код, который я, вероятно, собираюсь использовать:

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

И у меня есть связанный вопрос здесь .

Doug
источник

Ответы:

14

Вы могли бы использовать

Система (команда) 
    Выполнить команду операционной системы команды , а затем
    вернитесь в программу awk. Возвращение команды «s статус выхода. 

например:

awk -F: '{if(system("[ ! -d " $6 " ]") == 0) {print $1 " " $3 " " $7}}' /etc/passwd
don_crissti
источник
Я не мог заставить это работать по некоторым причинам, поэтому мне пришлось сделать это: awk '{if (system ("stat" $ 0 "> / dev / null 2> / dev / null") == 1) print $ 0 } '
Александр Кьялл
1
@ AlexanderKjäll - это проверка на наличие каталога - если вы пытаетесь увидеть, существует ли обычный файл, то очевидно, что приведенный выше код завершится ошибкой ...
don_crissti
3
Это чрезвычайно опасно! В зависимости от ввода могут быть выполнены произвольные команды . Например, выполняется следующее /bin/ls: echo ';ls;' | awk '{ print system("[ ! -d " $1 " ]") }'
Тино
6

Я не думаю, что [ -d ]это awkвещь, это вещь оболочки. Я бы просто сделал это так:

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

Конечно, как очень правильно указал @Janis, вы можете сделать все это в оболочке:

while IFS=: read  name x uid x x x shell rest; do
     [ -d "/home/$name" ] || echo "$name $uid $shell" 
done < /etc/passwd
Тердон
источник
Хорошо, я думаю, что на самом деле хочу передать $ 6 как dir и сделать -d $ dir, но это очень помогает. Теперь мне просто нужно выяснить, как отобразить «нет результатов», если ничего не найдено. Спасибо!
Дуг
2
Обратите внимание, что на awkсамом деле это не нужно; так как вы в любом случае зацикливаетесь в оболочке, вы также можете сделать это while IFS=: read -r name x uid x x x shell rest ; do ... ; done </etc/passwd.
Янис
@ Даг Просто сделай awk -F: '{print $6}' /etc/passwd | while read dir; do [ -d "$dir" ] || echo "no results for $dir"; done.
Terdon
@Janis действительно, хорошая точка зрения, ответ отредактирован.
Terdon
Как отмечает @terdon, [ -d "$6"]на самом деле это синтаксис bash, а не awk. В [выглядит как нормальный синтаксис, но (в одном из Баша / лучше weirdnesses для Linux) это фактически синоним testисполняемой программы (или , возможно , Баш встроенного версии этого, и, просто чтобы быть страннее, Баш требует согласований , ]что Безразлично не делай ничего, о чем я знаю, кроме как вводить тебя в заблуждение и думать, что все дело в синтаксисе, а не в программе). В любом случае, это не то, о чем знает awk. Вот почему вам нужна system()функция для доступа к ней, ссылаясь на нее в контексте bash, где она понимается
Джо,
6

Вы можете использовать getline :

awk 'BEGIN {print getline < "file" < 0 ? "not exists" : "exists"}'
Стивен Пенни
источник
1
Это безопасно awk, но, к сожалению, не работает для каталогов.
Тино
5

Если вы действительно используете gawk(хотя вы можете использовать nawk, или mawk, в этом случае это не будет применяться), вы можете сделать это изначально, используя одно из загружаемых расширений, доступных начиная с версии 4.0. Я использую gawk-4.1.x(v4.0 имел вариацию синтаксиса для загрузки расширений).

Загрузка filefuncsрасширения добавляет (среди прочего) stat()функцию:

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

См. filefuncs(3am)Справочную страницу для деталей об этом расширении.

Запустите что-то вроде:

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

Вы можете подтвердить, что ваш gawkдвоичный файл поддерживает расширения:

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

Помимо: gawkтакже поставляется с небольшой библиотечной функцией для чтения passwdфайла, вы можете вызвать его следующим образом:

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

Я предпочитаю использовать getentв системах Linux / glibc, так как он поддерживает nsswitch.

mr.spuratic
источник
1
Интересно. Я не знал о gawkv4 предлагает это. Спасибо!
Тино
3

Это почти ужасно ...

perl -F: -ane 'if(!-d $F[5]){ print "$F[0] $F[2] $F[6]" }' /etc/passwd
JJoao
источник
0

Вот решение, которое

  • использует gawkи/bin/sh
  • проверяет каталог какой-нибудь внешней командой
  • но делает это безопасным и надежным способом

Код:

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

пояснил:

  • IFS="" read -r dir; [ -d "$dir" ]; echo $?это шелл-код, который читает путь из стандартного ввода и выводит, 0если это каталог, иначе1
  • print $6 |& cmdпередает имя файла в команду. |&является расширением GNU-awk.
  • cmd |& getline x читает вывод команды в GNU-awk
  • close(cmd) завершает команду, поэтому следующая строка может выполнить ее еще раз
  • if (x)выполняет printтолько если xнет 0(поэтому каталог не существует)

Я не рекомендую делать это таким образом, потому что это очень медленно и неуклюже. Но этот рецепт безопасен , так что неправильный ввод не может причинить вред. (Маловероятно, что он /etc/passwdсодержит вредоносные данные, но, возможно, кто-то захочет использовать его с данными из ненадежного источника).

Если вы не можете использовать gawk, это прискорбно. В этом случае у вас нет |&. awkТолько обычный может выполнить одно из следующих трех:

  • print "data" | cmd; close(cmd): Передать данные в команду
  • getline data < cmd; close(cmd): Чтение данных из команды
  • ret = system(cmd): Получить код возврата команды

«Обычный» awkпросто не может передать данные в скрипт и получить что-то из него одновременно (по крайней мере, я не нашел пути для этого), поэтому вам нужен какой-то промежуточный файл (временный файл), который еще более неуклюжий.

Интересно отметить, что такую ​​простую задачу можно выполнить и с помощью одной оболочки:

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

Вам не нужно bash, каждая нормальная оболочка Bourne может выполнить приведенный выше код.

Обратите внимание, что приведенный выше код оболочки немного сложнее, чем действительно необходимо, но это должен быть указатель на то, как на самом деле работать с ним (для людей, не совсем разбирающихся в том, как работает оболочка).

Tino
источник