У меня есть сервер, который каждый день получает файл на каждого клиента в каталог. Имена файлов строятся следующим образом:
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, насколько это возможно, но не против использования команд, если это необходимо.
Вот более «краткий» и лаконичный подход:
#!/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
Это чистый 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
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 о том, почему.
Как я подхожу к нему, сначала нужно получить 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