Як знайти, які файли відсутні у списку?


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не відображає поточний стан файлової системи, це може бути день або навіть тиждень. Це підходить як основа для тестування резервних копій.
— Volker Siegel

Відповіді:


5

findвважає пошук нічого особливого випадку успіху (помилки не сталося). Загальний спосіб перевірити, чи відповідають файли деяким findкритеріям, це перевірити, чи вихід findпустим. Для кращої ефективності, коли є відповідні файли, використовуйте -quitв пошуку GNU, щоб змусити її виходити під час першого матчу, або 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

Або ось коротший, але більш виразний спосіб перевірити наявність файлу, що відповідає шаблону. Класифікатор глобу 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 -type f /dst > $TMPFILE
while read f; do
    grep -q "/$f$" $TIMPFILE || echo $f
done < file_list

Зауважте, що:

  • список файлів включає лише файли, а не каталоги,
  • косою рисою в схемі відповідності grep, тому ми порівнюємо повні назви файлів, а не часткові,
  • і останнє "$" в шаблоні пошуку - відповідати кінці рядка, щоб у вас не було збігів каталогів, а лише патчі повних імен файлів.

stat потребує точного розташування, чи не так? Я використовую find, тому що у мене просто список імен файлів, і вони можуть бути у численних каталогах. Вибачте, якщо це було не ясно.
— Ендрю Стейсі

Хммм. Так, ти не сказав, що маєш назви файлів без доріжок! Може ви можете замість цього виправити цю проблему? Це було б набагато ефективніше, ніж біг знайти купу разів через один і той же набір даних.
— Калеб

Дякуємо за редагування та ще раз вибачте, що не вказали конкретні. Ім'я / шлях файлу - це не те, що я збираюся виправляти - файли можуть бути в різних місцях на двох системах, тому я хочу, щоб рішення було достатньо надійним, щоб обійти це. Комп'ютер повинен працювати за моїми характеристиками, а не навпаки! Серйозно, це не те, що я часто роблю - я шукав деякі старі файли, які слід видалити, щоб звільнити простір, і просто хотів "швидкого" n "брудного" способу, щоб переконатися, що вони були в моїх резервних копіях.
— Ендрю Стейсі

Перш за все, вам не знадобиться повний шлях, а лише відносний шлях до тієї структури каталогу, яку ви створювали. Дозвольте мені припустити, що якщо шлях не той самий, є хороший шанс, що файл не такий, і ви можете отримати помилкові позитивні результати свого тесту. Здається, що ваше рішення може бути більш брудним, ніж швидким; Я не хотів би бачити вас спаленим, думаючи, що у вас є щось, чого ви цього не зробили. Крім того, якщо файли є досить цінними для резервного копіювання, ви не повинні видаляти праймери, інакше вам потрібно створити резервну копію!
— Калеб

Ак! Я залишив безліч деталей, щоб спробувати сфокусувати питання, і ви заповнюєте їх великою кількістю припущень, які - я повинен сказати - цілком розумні, але трапляються абсолютно неправильно! Досить сказати, що я знаю, що якщо файл є і знаходиться в каталозі з певним типом імені, то я знаю, що це оригінальний файл і безпечно видалити копію на моїй машині.
— Ендрю Стейсі

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 -or -name -b.file -or -name c.file ...

Можливо, місце знаходження є варіантом? Знову ж таки, перекручений список файлів передбачає:

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

Пошук foo.bar не буде відповідати файлу aa 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

цей приклад виключає резервні файли "* ~" та обмежує звичайний тип файлу "-типу 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}

Можливо?


0

Чому б просто не порівняти довжину списку запитів із довжиною списку результатів?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.