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

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

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, перечисленный в файле, имеет соответствующий файл в каталоге, с помощью удара.

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

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

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

8
задан 4 February 2016 в 07:40

5 ответов

Техническая победа файлы, создайте ассоциативный массив по uuids, содержавшемуся в их именах (я использовал расширение параметра для извлечения uuid). Прочитайте список, проверьте ассоциативный массив на каждый uuid и сообщите, был ли файл зарегистрирован или нет.

#!/bin/bash
uuid_list=...

declare -A file_for
for file in *_*_* ; do
    uuid=${file%%_*}
    file_for[$uuid]=1
done

while read -r uuid name ; do
    [[ $uuid = \#* ]] && continue
    if [[ ${file_for[$uuid]} ]] ; then
        echo "File for $name has arrived."
    else
        echo "File for $name missing!"
    fi
done < "$uuid_list"
5
ответ дан 23 November 2019 в 05:25

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

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

Это считает каждую строку от 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 November 2019 в 05:25

Вот больше "bashy" и краткий подход:

#!/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

Только для иллюстрирования разницы во времени я протестировал свой подход удара, choroba's и свой жемчуг на файле, с 20 000 UUID которого 18001 имел соответствующее имя файла. Обратите внимание, что каждый тест был запущен путем перенаправления вывода сценария к /dev/null.

  1. Мой удар (~3.5 минуты)

    real   3m39.775s
    user   1m26.083s
    sys    2m13.400s
    
  2. Choroba (удар, ~0.7 секунды)

    real   0m0.732s
    user   0m0.697s
    sys    0m0.037s
    
  3. Мой жемчуг (~0.1 секунды):

    real   0m0.100s
    user   0m0.093s
    sys    0m0.013s
    
5
ответ дан 23 November 2019 в 05:25
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 и номер строки, на котором ошибка произошла с выводом ошибок, когда это делает... Это - хорошая информация, которая по умолчанию уже предоставляется - так не беспокойтесь.

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

3
ответ дан 23 November 2019 в 05:25

Путем я приблизился бы, это должно получить 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, группа, 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" опция просто распечатать само имя файла

То, что это не делает, должно перечислить недостающие файлы. 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 идея и выполнение с нею, мы могли использовать код выхода статистики как признак для того, существует ли файл или нет. Effectivelly, мы хотим сделать это:

$ 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 November 2019 в 05:25

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

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