Listede hangi dosyaların eksik olduğunu nasıl bulabilirim?


9

Dosya sistemimde var olup olmadığını kontrol etmek istediğim dosyaların bir listesi var. Bunu şu şekilde kullanarak yapmayı düşündüm find:

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

(kullanarak zsh), ancak dosyayı bulsun veya bulmasın çıkmış gibi findgörünmüyor 0. Ben olmadığını görmek için test başka testte içinden geçebileceği tahmin findherhangi çıkışı (ham ama etkili değiştirmek şeklinde olur üretir > /dev/nullile |grep '') ama bir keçi yakalamak için bir trol kullanmak gibi bu hissediyor (diğer milletlerden balyoz ve ceviz hakkında bir şey yazabilir ).

findBana yararlı bir çıkış değeri vermeye zorlamanın bir yolu var mı ? Ya da en azından bu dosyaların bir listesini almak için değil bulduk? (Mantıksal bağların kurnaz bir seçimiyle ikincisinin belki de daha kolay olduğunu hayal edebilirim, ancak anlamaya çalıştığımda her zaman düğümlerde bağlandığım görünüyor.)

Arkaplan / Motivasyon: "Ana" bir yedeğim var ve bunları silmeden önce yerel makinemdeki bazı dosyaların ana yedeklememde var olup olmadığını kontrol etmek istiyorum (biraz alan yaratmak için). Bu yüzden dosyaların bir listesini hazırladım ssh, ana makineye düzenledim ve daha sonra eksik dosyaları bulmanın en iyi yolunu bulmaktan zarar gördüm .


Çok daha hızlı kullanmak için çözümümü güncelledim locate.
kullanıcı bilinmiyor

@userunknown locatedosya sisteminin mevcut durumunu göstermiyor, bir gün hatta bir haftalık olabilir. Bu, yedekleri test etmek için bir temel olarak uygundur.
Volker Siegel

Yanıtlar:


5

findhiçbir şeyi özel bir başarı örneği bulmayı düşünür (hata oluşmaz). Dosyaların bazı findölçütlerle eşleşip eşleşmediğini test etmenin genel bir yolu , çıktısının findboş olup olmadığını test etmektir. Eşleşen dosyalar, kullanım vardır daha iyi verim için -quitGNU ilk maçında çıkın veya yapmak bulmak head( head -c 1aksi varsa head -n 1diğer sistemlerde standart olan) o kırık bir boru yerine üretmek uzun çıkışının ölmek yapmak.

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

Bash ≥4 veya zsh'de, findbasit bir ad eşleşmesi için harici komuta ihtiyacınız yoktur : kullanabilirsiniz **/$name. Bash versiyonu:

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

Benzer bir prensipte Zsh versiyonu:

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

Ya da bir desenle eşleşen bir dosyanın varlığını test etmenin daha kısa ama daha şifreli bir yolu. Glob niteleyicisi N, eşleşme yoksa çıktıyı boş yapar, [1]yalnızca ilk eşleşmeyi korur ve e:REPLY=true:eşleşen her 1dosya adı yerine genişletilecek her eşleşmeyi değiştirir . Yani bir maç varsa veya sadece maç yoksa **/"$name"(Ne:REPLY=true:[1]) falsegenişler .true falsefalse

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

Tüm adlarınızı tek bir aramada birleştirmek daha verimli olur. Desen sayısı, bir komut satırındaki sisteminizin uzunluk sınırı için çok büyük değilse, tüm adları birleştirebilir -o, tek bir findçağrı yapabilir ve çıktıyı sonradan işleyebilirsiniz. İsimlerin hiçbiri kabuk metakarakterleri içermiyorsa (isimler de finddesenler olacaksa ), awk (denenmemiş) ile post-process yapmanın bir yolu:

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

Başka bir yaklaşım Perl kullanmaktır ve File::Findbu da bir dizindeki tüm dosyalar için Perl kodunu çalıştırmayı kolaylaştırır.

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

Alternatif bir yaklaşım, her iki tarafta dosya adlarının bir listesini oluşturmak ve bir metin karşılaştırması üzerinde çalışmaktır. Zsh sürümü:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)

Bunu iki nedenden dolayı kabul ediyorum. Sözdizimi zshile çözümü seviyorum **. Bu çok basit bir çözüm ve makine açısından en verimli olmasa da, aslında hatırlamakta muhtemelen en verimli olanı! Ayrıca, buradaki ilk çözüm , çıkış kodunun "Bir eşleşme aldım" ifadesini "Bir eşleşme alamadım" dan ayırdığı bir şeye çarptığı için asıl soruyu cevaplıyor find.
Andrew Stacey

9

statDosya sisteminde bir dosya olup olmadığını belirlemek için kullanabilirsiniz .

Dosyaların olup olmadığını test etmek için yerleşik kabuk işlevlerini kullanmalısınız.

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

"Test" isteğe bağlıdır ve script aslında onsuz çalışacaktır, ancak okunabilirlik için orada bıraktım.

Düzenleme: Gerçekten yolu olmayan dosya adları listesi için çalışmak için başka bir seçeneğiniz yoksa, bir kez find ile bir dosya listesi oluşturmanızı öneririz, sonra grep ile hangi dosyaları olduğunu anlamak için üzerinde tekrarlayın.

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

Bunu not et:

  • dosya listesi sadece dizinleri değil,
  • grep eşleme modelindeki eğik çizgi, kısmi olmayan tam dosya adlarını karşılaştırıyoruz,
  • ve arama desenindeki son '$' satırın sonuyla eşleşmelidir, böylece dizin eşleşmeleri almazsınız, yalnızca tam dosya adı düzeltme ekleri alırsınız.

stat tam konuma ihtiyaç duyar, değil mi? Ben sadece kullanıyorum çünkü dosya isimlerinin bir listesi var ve onlar çok sayıda dizin olabilir. Anlaşılmadıysa özür dilerim.
Andrew Stacey

Hmmm. Ya yolsuz dosya isimleriniz olduğunu söylemediniz! Belki bunun yerine BU sorunu çözebilirsiniz? Aynı veri kümesinde birkaç kez bulmaktan çok daha verimli olurdu.
Caleb

Düzenleme için teşekkürler ve spesifik olmadığın için tekrar özür dilerim. Dosya adı / yolu düzeltmek için bir şey değil - dosyaları iki sistem farklı yerlerde olabilir, bu yüzden bu geçici çözüm için yeterince sağlam bir çözüm istiyorum. Bilgisayar benim özellikleriyle çalışmalı, tersi değil! Cidden, bu sık sık yaptığım bir şey değil - yer açmak için silmek için bazı eski dosyalar arıyordum ve sadece yedeklerimde olduklarından emin olmak için "hızlı 'n' kirli" bir yol istedim.
Andrew Stacey

Her şeyden önce, tam yol yapmanız gerekmeyecekti, sadece yedeklediğiniz dizin yapısına göreli bir yol. Beni önermek için izin ver orada dosya aynı değildir iyi bir fırsat, yol aynı değilse ve size testin dışında yanlış pozitif alabilirsiniz. Çözümünüz hızlı olmaktan daha kirli olabilir; Yapmadığın bir şey olduğunu düşünerek yandığını görmek istemem. Ayrıca, dosyalar ilk etapta yedeklenecek kadar değerliyse, primerleri silmemelisiniz, aksi takdirde yedeklerinizi yedeklemeniz gerekir!
Caleb

Ak! Soruyu odaklamaya çalışmak için bir sürü ayrıntı bıraktım ve bunları bir dizi varsayımla dolduruyorsun - söylemeliyim - mükemmel makul ama tamamen yanlış oluyor! Bunu söylemek yeterli biliyorum dosya varsa ve isim belli türüne sahip bir dizinde ise o zaman orijinal dosya olduğunu biliyoruz ve benim makinede kopyayı silmek güvenlidir.
Andrew Stacey

1

İlk, basit bir yaklaşım şöyle olabilir:

a) dosya listenizi sıralayın:

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

eksikleri bulmak veya

comm sorted.lst found.lst

eşleşme bulmak

  • tuzaklar:
    • Dosya adlarındaki yeni satırların kullanımı çok zordur
    • dosya adlarındaki boşluklar ve benzeri şeyler de hoş değil. Ancak dosyalar listesindeki dosyalar üzerinde kontrol sahibi olduğunuz için, belki de bu çözüm zaten yeterlidir ...
  • Dezavantajları:

    • Find bir dosya bulduğunda, diğerini ve diğerini bulmak için çalışmaya devam eder. Daha fazla aramayı atlamak güzel olurdu.
    • find, bazı hazırlıklarla aynı anda birden fazla dosya arayabilir:

      -adı-dosya -veya-adı -b.file -veya-adı c.file bul ...

Yer bulmak bir seçenek olabilir mi? Yine, kabul edilen dosyaların bir listesi varsayılmıştır:

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

Foo.bar araması, are foo.ba veya oo.bar dosyasıyla --regexp-construct (p olmadan regex ile karıştırılmamalıdır) ile eşleşmez.

Bulmak için belirli bir veritabanı belirtebilirsiniz ve en son sonuçlara ihtiyacınız varsa aramadan önce güncellemeniz gerekir.


1

Bunun da faydalı olabileceğini düşünüyorum.

Bu, "listeniz" in başka bir klasörle senkronize etmek istediğiniz gerçek dosyalar olmasını seçmeniz durumunda tek satırlık bir çözümdür:

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

okumaya yardımcı olmak için:

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

bu örnekte yedek "* ~" dosyaları hariç tutulur ve "-type f" normal dosya türüyle sınırlandırılır


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}

Olabilir?


0

Neden sorgu listesinin uzunluğunu sonuç listesinin uzunluğuyla karşılaştırmıyorsunuz?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.