Как мне найти, какие файлы отсутствуют в списке?

9

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

for f in $(cat file_list); do
find . -name $f > /dev/null || print $f
done

(используя zsh), но это не работает, так как findкажется, что он завершает работу , 0независимо от того, находит ли он файл. Я предполагаю , что я мог бы передать его через какой - то другой тест , который проверяет , чтобы увидеть , если findпроизводит какой - либо выход (нефть , но эффективным было бы заменить > /dev/nullс |grep '') , но это чувствует , как с помощью тролля поймать козу (другие национальности могли бы сказать что - то о кувалды и грецкие орехи ).

Есть ли способ заставить findменя дать полезное значение выхода? Или хотя бы получить список тех файлов, которые не были найдены? (Я могу себе представить, что последнее может быть легче благодаря некоторому хитрому выбору логических связок, но я, кажется, всегда завязываю в узлах, когда пытаюсь это выяснить.)

Предыстория / Мотивация: у меня есть «основная» резервная копия, и я хочу проверить, что некоторые файлы на моем локальном компьютере существуют на моей главной резервной копии, прежде чем удалять их (чтобы освободить место). Поэтому я составил список файлов, sshотредактировал их на главном компьютере, а потом не мог найти лучший способ найти пропавшие файлы.

Эндрю Стейси
источник
Я обновил свое решение, чтобы использовать гораздо быстрее locate.
пользователь неизвестен
@userunknown locateне показывает текущее состояние файловой системы, это может быть день или даже неделя. Это подходит в качестве базы для тестирования резервных копий.
Фолькер Сигел

Ответы:

5

findсчитает поиск ничего особенного случая успеха (ошибки не произошло). Общий способ проверить, соответствуют ли файлы некоторым findкритериям, состоит в том, чтобы проверить, является ли вывод findпустым. Для повышения эффективности, когда есть совпадающие файлы, используйте -quitв GNU find, чтобы он завершал работу при первом совпадении, или head( head -c 1если доступно, в противном случае, head -n 1что является стандартным) в других системах, чтобы заставить его умереть от разорванного канала, а не производить длинный вывод.

while IFS= read -r name; do
  [ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list

В bash ≥4 или zsh вам не нужна внешняя findкоманда для простого сопоставления имен: вы можете использовать **/$name. Bash версия:

shopt -s nullglob
while IFS= read -r name; do
  set -- **/"$name"
  [ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list

Zsh версия по схожему принципу:

while IFS= read -r name; do
  set -- **/"$name"(N)
  [ $# -ge 1 ] || print -- "$name"
done <file_list

Или вот более короткий, но более загадочный способ проверки существования файла, соответствующего шаблону. Спецификатор glob Nделает вывод пустым, если совпадений нет, [1]сохраняет только первое совпадение и e:REPLY=true:меняет каждое совпадение, чтобы расширять 1вместо сопоставленного имени файла. Так **/"$name"(Ne:REPLY=true:[1]) falseрасширяется, true falseесли есть совпадение, или просто, falseесли нет совпадения.

while IFS= read -r name; do
  **/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list

Было бы эффективнее объединить все ваши имена в один поиск. Если количество шаблонов не слишком велико для ограничения длины вашей системы в командной строке, вы можете объединить все имена с помощью -o, сделать один findвызов и постобработать вывод. Если ни одно из имен не содержит метасимволов оболочки (так что имена также являются findшаблонами), вот способ постобработки с использованием awk (не проверено):

set -o noglob; IFS='
'
set -- $(<file_list sed -e '2,$s/^/-o\
/')
set +o noglob; unset IFS
find . \( "$@" \) -print | awk -F/ '
    BEGIN {while (getline <"file_list") {found[$0]=0}}
    wanted[$0]==0 {found[$0]=1}
    END {for (f in found) {if (found[f]==0) {print f}}}
'

Другой подход заключается в использовании Perl и File::Find, что упрощает запуск кода Perl для всех файлов в каталоге.

perl -MFile::Find -l -e '
    %missing = map {chomp; $_, 1} <STDIN>;
    find(sub {delete $missing{$_}}, ".");
    print foreach sort keys %missing'

Альтернативный подход заключается в создании списка имен файлов с обеих сторон и работе над сравнением текста. Zsh версия:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
Жиль "ТАК - перестань быть злым"
источник
Я принимаю это по двум причинам. Мне нравится zshрешение с **синтаксисом. Это очень простое решение, и, хотя оно может быть не самым эффективным с точки зрения машины , оно, вероятно, самое эффективное с моей точки зрения, когда я его запоминаю! Кроме того, первое решение здесь отвечает на реальный вопрос в том, что оно findпревращается во что-то, где код выхода отличает «Я получил совпадение» от «Я не получил совпадение».
Эндрю Стейси
9

Вы можете использовать, statчтобы определить, существует ли файл в файловой системе.

Вы должны использовать встроенные функции оболочки, чтобы проверить, существуют ли файлы.

while read f; do
   test -f "$f" || echo $f
done < file_list

«Тест» является необязательным, и скрипт фактически будет работать без него, но я оставил его там для удобства чтения.

Редактировать: Если у вас действительно нет выбора, кроме как работать со списком имен файлов без путей, я предлагаю вам создать список файлов один раз с помощью команды find, а затем выполнить итерацию по нему с помощью grep, чтобы выяснить, какие файлы там есть.

find -type f /dst > $TMPFILE
while read f; do
    grep -q "/$f$" $TIMPFILE || echo $f
done < file_list

Обратите внимание, что:

  • список файлов включает только файлы, а не каталоги,
  • косая черта в шаблоне соответствия grep такова, что мы сравниваем полные имена файлов, а не частичные,
  • и последний «$» в шаблоне поиска должен совпадать с концом строки, чтобы вы не получали совпадения с каталогом, только патчи с полным именем файла.
Калеб
источник
Стату нужно точное местоположение, не так ли? Я использую find, потому что у меня просто есть список имен файлов, и они могут быть в многочисленных каталогах. Извините, если это не ясно.
Эндрю Стейси
Хммм. Вы не сказали, что у вас есть имена файлов без путей! Может быть, вы можете решить эту проблему вместо этого? Это было бы намного эффективнее, чем запускать поиск несколько раз по одному и тому же набору данных.
Калеб
Спасибо за редактирование, и еще раз извините за то, что не был конкретным. Я не собираюсь исправлять имя / путь к файлу - файлы могут находиться в разных местах в двух системах, поэтому я хочу найти достаточно надежное решение, чтобы обойти это. Компьютер должен работать в соответствии с моими спецификациями, а не наоборот! Серьезно, это не то, что я делаю часто - я искал несколько старых файлов, чтобы удалить их, чтобы освободить место, и просто хотел «быстрый и грязный» способ убедиться, что они были в моих резервных копиях.
Эндрю Стейси
Прежде всего вам не потребуется полный путь, просто относительный путь к любой структуре каталогов, для которой вы создавали резервные копии. Позвольте мне предположить, что если путь не совпадает, есть большая вероятность, что файл не совпадает, и вы можете получить ложные срабатывания из вашего теста. Похоже, ваше решение может быть скорее грязным, чем быстрым; Я не хотел бы видеть тебя обожженным, думая, что у тебя есть то, чего у тебя нет. Кроме того, если файлы достаточно ценны для резервного копирования, вам не следует удалять первичные файлы, в противном случае вам необходимо создавать резервные копии!
Калеб
Ак! Я упустил множество деталей, чтобы попытаться сфокусировать вопрос, а вы заполняете их множеством предположений, которые - я должен сказать - совершенно разумны, но оказываются совершенно неправильными! Достаточно сказать, что я знаю, что если файл существует и находится в каталоге с определенным типом имени, то я знаю, что это оригинальный файл, и удалить его на моем компьютере безопасно.
Эндрю Стейси
1

Первый, упрощенный подход, может быть:

а) сортировать свой список файлов:

sort file.lst > sorted.lst 
for f in $(< sortd.lst) ; do find -name $f -printf "%f\n"; done > found.lst
diff sorted.lst found.lst

чтобы найти пропуски, или

comm sorted.lst found.lst

найти совпадения

  • Ловушки:
    • Новые строки в именах файлов очень трудно обрабатывать
    • пробелы и тому подобные вещи в именах файлов тоже не хороши. Но так как у вас есть контроль над файлами в списке файлов, возможно, это решение уже достаточно, однако ...
  • Недостатки:

    • Когда find находит файл, он продолжает поиск другого и другого. Было бы неплохо пропустить дальнейший поиск.
    • find может искать несколько файлов одновременно с некоторой подготовкой:

      find -name a.file -или -name -b.file -или -name c.file ...

Может быть найти вариант? Опять же, предварительный список файлов предполагается:

 for f in $(< sorted.tmp) ; do locate --regexp "/"$f"$" > /dev/null || echo missing $f ; done

Поиск по foo.bar не будет соответствовать ни файлу foo.ba, ни oo.bar с конструкцией --regexp (не должен быть подтвержден регулярным выражением без p).

Вы можете указать конкретную базу данных для поиска, и вам необходимо обновить ее перед поиском, если вам нужны последние результаты.

Пользователь неизвестен
источник
1

Я думаю, что это тоже может быть полезно.

Это однострочное решение, в случае если вы выбираете в качестве «списка» реальные файлы, которые вы хотите синхронизировать с другой папкой:

function FUNCsync() { local fileCheck="$synchronizeTo/$1"; if [[ ! -f "$fileCheck" ]];then echo "$fileCheck";fi; };export -f FUNCsync;find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

чтобы помочь чтению:

function FUNCsync() {
  local fileCheck="$synchronizeTo/$1";
  if [[ ! -f "$fileCheck" ]];then 
    echo "$fileCheck";
  fi; 
};export -f FUNCsync;
find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

этот пример исключает резервные файлы "* ~" и ограничивает обычный тип файла "-type f"

Водолей Сила
источник
0
FIND_EXP=". -type f \( "
while read f; do
   FIND_EXP="${FIND_EXP} -iname $f -or"
done < file_list
FIND_EXP="${var%-or}"
FIND_EXP="${FIND_EXP} \)"
find ${FIND_EXP}

Может быть?

Hello71
источник
0

Почему бы просто не сравнить длину списка запросов с длиной списка результатов?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Хольгер Брандл
источник