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


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.