Bash - проверить каталог для файлов со списком частичных имен файлов

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

uuid_datestring_other-data

Например:

d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
uuid является стандартным форматом uuid. datestring - выход из date +%Y%m%d. other-data имеет переменную длину, но никогда не будет содержать символ подчеркивания.

У меня есть файл формата:

#
d6f60016-0011-49c4-8fca-e2b3496ad5a7    client1
d5873483-5b98-4895-ab09-9891d80a13da    client2
be0ed6a6-e73a-4f33-b755-47226ff22401    another_client
...

Мне нужно проверить, что каждый uuid, указанный в файле, имеет соответствующий файл в каталоге, используя bash. [!d7 ]

У меня это так далеко, но чувствую, что я исхожу из неправильного направления, используя оператор if, и мне нужно прокрутить файлы в исходном каталоге.

source_directory и uuid_list ранее были назначены в скрипте:

# Check the entries in the file list

while read -r uuid name; do
# Ignore comment lines
   [[ $uuid = \#* ]] && continue
   if [[ -f "${source_directory}/${uuid}*" ]]
   then
      echo "File for ${name} has arrived"
   else
      echo "PANIC! - No File for ${name}"
   fi
done < "${uuid_list}"

Как проверить, что файлы в моем списке существуют в каталоге? Я хотел бы использовать функциональность bash, насколько это возможно, но не против использования команд, если это необходимо.

1
задан 4 February 2016 в 18:40

4 ответа

Вот более «краткий» и лаконичный подход:

#!/bin/bash

## Read the UUIDs into the array 'uuids'. Using awk
## lets us both skip comments and only keep the UUID
mapfile -t uuids < <(awk '!/^\s*#/{print $1}' uuids.txt)

## Iterate over each UUID
for uuid in ${uuids[@]}; do
        ## Set the special array $_ (the positional parameters: $1, $2 etc)
        ## to the glob matching the UUID. This will be all file/directory
        ## names that start with this UUID.
        set -- "${source_directory}"/"${uuid}"*
        ## If no files matched the glob, no file named $1 will exist
        [[ -e "$1" ]] && echo "YES : $1" || echo  "PANIC $uuid" 
done

Обратите внимание, что, хотя вышесказанное довольно красиво и отлично работает для нескольких файлов, его скорость зависит от количества UUID и будет очень медленно, если вам нужно обрабатывать многие. Если это так, используйте либо решение @ choroba или, для чего-то действительно быстрого, избегайте оболочки и вызывайте perl:

#!/bin/bash

source_directory="."
perl -lne 'BEGIN{
            opendir(D,"'"$source_directory"'"); 
            foreach(readdir(D)){ /((.+?)_.*)/; $f{$2}=$1; }
           } 
           s/\s.*//; $f{$_} ? print "YES: $f{$_}" : print "PANIC: $_"' uuids.txt

. Чтобы проиллюстрировать разницу во времени, я опробовал свой подход bash, choroba и my perl в файле с 20000 UUID, из которых 18001 имеет соответствующее имя файла. Обратите внимание, что каждый тест выполнялся путем перенаправления вывода скрипта на /dev/null.

My bash (~ 3.5 min)
real   3m39.775s
user   1m26.083s
sys    2m13.400s
Choroba's (bash, ~ 0.7 sec)
real   0m0.732s
user   0m0.697s
sys    0m0.037s
My perl (~ 0,1 с ):
real   0m0.100s
user   0m0.093s
sys    0m0.013s
5
ответ дан 23 May 2018 в 13:46
  • 1
    +1 для фантастически сжатого метода, это должно быть выполнено из каталога, содержащего файлы. Я знаю, что могу cd войти в каталог в скрипте, но есть ли способ, по которому путь файла может быть включен в поиск? – Arronical 4 February 2016 в 21:32
  • 2
    @ Арологический уверен, см. Обновленный ответ. Вы можете использовать ${source_directory} так же, как в своем скрипте. – terdon♦ 4 February 2016 в 21:44
  • 3
    Или используйте "$2" и передайте его сценарию в качестве второго аргумента. – alexis 5 February 2016 в 14:15
  • 4
    Убедитесь, что это выполняется достаточно быстро для ваших целей - быстрее было бы сделать это с помощью одного сканирования каталогов, а не столько поиска файлов, как это. – alexis 5 February 2016 в 14:21
  • 5
    @alexis да, вы совершенно правы. Я провел некоторое тестирование, и это становится очень медленным, если число UUID / файлов увеличивается. Я добавил подход perl (который можно запускать как один лайнер изнутри bash-скрипта, так что технически, все еще bash, если вы открыты для какого-либо творческого наименования), который намного быстрее. – terdon♦ 5 February 2016 в 17:12

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

Но производительность по-настоящему не намного лучше, чем у вас в настоящее время.

Он будет читать каждую строку из path/to/file; для каждой строки он сохранит первое поле в $uuid и распечатает сообщение, если файл, соответствующий шаблону path/to/directory/$uuid*, , но найден:

#! /bin/bash
[ -z "$2" ] && printf 'Not enough arguments.\n' && exit

while read uuid; do
    [ ! -f "$2/$uuid"* ] && printf '%s missing in %s\n' "$uuid" "$2"
done <"$1"

Позвонить с помощью path/to/script path/to/file path/to/directory.

Образец вывода с использованием образца входного файла в вопросе об иерархии тестового каталога, содержащего образец файла в вопросе:

% tree
.
├── path
│   └── to
│       ├── directory
│       │   └── d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
│       └── file
└── script.sh

3 directories, 3 files
% ./script.sh path/to/file path/to/directory
d5873483-5b98-4895-ab09-9891d80a13da* missing in path/to/directory
be0ed6a6-e73a-4f33-b755-47226ff22401* missing in path/to/directory
3
ответ дан 23 May 2018 в 13:46
unset IFS
set -f
set +f -- $(<uuid_file)
while  [ "${1+:}" ]
do     : < "$source_directory/$1"*  &&
       printf 'File for %s has arrived.\n' "$2"
       shift 2
done

Идея здесь не в том, чтобы беспокоиться о сообщениях об ошибках, которые оболочка сообщит вам. Если вы попытаетесь < открыть файл, который не существует, ваша оболочка будет жаловаться. Фактически, он добавит ваш скрипт $0 и номер строки, на которой произошла ошибка с выходом ошибки, когда он делает ... Это хорошая информация, которая предоставляется по умолчанию уже - так что не беспокойтесь. [!d0 ]

Вам также не нужно брать файл в строчном порядке, как это - он может быть ужасно медленным. Это расширяет все это за один выстрел до массива аргументов с пробелом, ограниченным пробелами, и обрабатывает два за раз. Если ваши данные соответствуют вашему примеру, тогда $1 всегда будет вашим uuid, а $2 будет вашим $name. Если bash может открыть совпадение с вашим uuid - и существует только один такой матч - тогда printf произойдет. В противном случае это не так, и оболочка записывает диагностику в stderr о том, почему.

3
ответ дан 23 May 2018 в 13:46
  • 1
    @kos - существует ли файл? если нет, то он ведет себя так, как предполагалось. unset IFS гарантирует, что $(cat <uuid_file) разделен на пробел. Оболочки поделены на $IFS по-разному, когда он состоит только из белого пространства или не установлен. Подобные раздельные расширения никогда не имеют никаких нулевых полей, потому что все последовательности белого пространства стоят как единственный разделитель поля. Думаю, пока на каждой строке есть только два поля, не содержащие белого пространства, они должны работать. в bash, во всяком случае. set -f гарантирует, что неопределенное расширение не интерпретируется для globs, а set + f гарантирует, что последующие шары. – mikeserv 5 February 2016 в 18:46
  • 2
    @kos - я только что исправил это. Я не должен был использовать <>, потому что это создает несуществующий файл. < сообщит, как я это сделал. возможная проблема с этим - и причина, по которой я неправильно использовал <>, в первую очередь, - это то, что если это файл с каналом без чтения или как строковый буфер, он будет зависать. что можно было бы избежать, если обработать вывод ошибки более явно и сделать [ -f "$dir/$1"* ]. мы говорим о uuids здесь, и поэтому он никогда не должен расширяться до более чем одного файла. это любопытно, хотя, как он сообщает о неудачных именах файлов для stderr, как это. – mikeserv 5 February 2016 в 19:14
  • 3
    @kos - на самом деле, я полагаю, я мог бы использовать ulimit, чтобы он не создавал никаких файлов вообще, и поэтому <> все равно будет использоваться таким образом ... <> лучше, если glob может расширяться до каталога, потому что на linux чтение / запись не удастся и сказать - это каталог. – mikeserv 5 February 2016 в 19:19
  • 4
    @kos - о! Извините - я просто тупой - у вас есть два матча, и поэтому он делает все правильно. я имею в виду для этого ошибку в этом случае, если могут быть два совпадения, они должны быть uuids - никогда не должно быть возможности для двух похожих имен, которые соответствуют одному и тому же глобусу. который полностью преднамерен, и является неоднозначным таким образом, которым он не должен быть. вы видите, что я имею в виду? Именование файла для glob не является проблемой, - здесь важны специальные символы, проблема в том, что bash будет принимать только глобальный шар перенаправления, если он соответствует только одному файлу. см. man bash в разделе «REDIRECTION». – mikeserv 5 February 2016 в 19:44

Как я подхожу к нему, сначала нужно получить uuids из файла, а затем использовать find

awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;done

Для чтения,

awk '{print $1}' listfile.txt  | \
    while read fileName;do \
    find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;
    done

Пример со списком файлов в /etc/, ища имена файлов passwd, group, fstab и THISDOESNTEXIST.

$ awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null; done
/etc/pam.d/passwd FOUND
/etc/cron.daily/passwd FOUND
/etc/passwd FOUND
/etc/group FOUND
/etc/iproute2/group FOUND
/etc/fstab FOUND

Поскольку вы упомянули, что каталог плоский, вы можете использовать параметр -printf "%f\n" для печати filename сам

Что это не значит, это перечислить недостающие файлы. Недостаток find заключается в том, что он не говорит вам, не находит ли он файл, только когда он что-то соответствует. Однако, что можно сделать, это проверить выход - если выход пуст, то у нас отсутствует файл

awk '{print $1}' listfile.txt  | while read fileName;do RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; [ -z "$RESULT"  ] && echo "$fileName not found" || echo "$fileName found"  ;done

. Более читаемый:

awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

И вот как это работает как маленький скрипт:

skolodya@ubuntu:$ ./listfiles.sh                                               
passwd found
group found
fstab found
THISDONTEXIST not found

skolodya@ubuntu:$ cat listfiles.sh                                             
#!/bin/bash
awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

В качестве альтернативы можно использовать stat, так как это плоский каталог, но приведенный ниже код не будет работать рекурсивно для подкаталогов, если вы когда-нибудь захотите добавить те:

$ awk '{print $1}' listfile.txt  | while read fileName;do  stat /etc/"$fileName"* 1> /dev/null ;done        
stat: cannot stat ‘/etc/THISDONTEXIST*’: No such file or directory

Если мы возьмем идею stat и запустим ее, мы можем использовать код выхода stat как указание на наличие файла или нет. Эффективно, мы хотим сделать это:

$ awk '{print $1}' listfile.txt  | while read fileName;do  if stat /etc/"$fileName"* &> /dev/null;then echo "$fileName found"; else echo "$fileName NOT found"; fi ;done

Пример прогона:

skolodya@ubuntu:$ awk '{print $1}' listfile.txt  | \                                                         
> while read FILE; do                                                                                        
> if stat /etc/"$FILE" &> /dev/null  ;then                                                                   
> echo "$FILE found"                                                                                         
> else echo "$FILE NOT found"                                                                                
> fi                                                                                                         
> done
passwd found
group found
fstab found
THISDONTEXIST NOT found
1
ответ дан 23 May 2018 в 13:46

Другие вопросы по тегам:

Похожие вопросы: