Bagaimana cara menemukan file mana yang hilang dari daftar?

9

Saya memiliki daftar file yang ingin saya periksa apakah ada di sistem file saya. Saya berpikir untuk melakukan ini menggunakan findseperti pada:

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

(menggunakan zsh) tetapi itu tidak berfungsi karena findtampaknya keluar 0atau tidak menemukan file. Saya kira saya bisa melewatinya melalui beberapa tes lain yang menguji untuk melihat apakah findmenghasilkan output apa pun (mentah tapi efektif untuk menggantikan > /dev/nulldengan |grep '') tetapi ini terasa seperti menggunakan troll untuk menangkap seekor kambing (bangsa lain mungkin mengatakan sesuatu tentang palu godam dan kenari ).

Apakah ada cara untuk memaksa findsaya memberi saya nilai keluar yang bermanfaat? Atau setidaknya untuk mendapatkan daftar file-file yang tidak ditemukan? (Saya bisa membayangkan yang terakhir ini mungkin lebih mudah dengan beberapa pilihan penghubung logis yang licik, tetapi saya tampaknya selalu terikat dalam simpul ketika saya mencoba mengetahuinya.)

Latar Belakang / Motivasi: Saya memiliki cadangan "master" dan saya ingin memeriksa bahwa beberapa file di mesin lokal saya ada di cadangan master saya sebelum menghapusnya (untuk membuat sedikit ruang). Jadi saya membuat daftar file, sshmengeditnya ke mesin master, dan kemudian bingung mencari cara terbaik untuk menemukan file yang hilang.

Andrew Stacey
sumber
Saya memperbarui solusi saya untuk menggunakan yang jauh lebih cepat locate.
pengguna tidak diketahui
@userunknown locatetidak menunjukkan status filesystem saat ini, bisa jadi satu hari, atau bahkan berumur seminggu. Itu cocok sebagai basis untuk menguji cadangan.
Volker Siegel

Jawaban:

5

findmenganggap tidak menemukan kasus khusus keberhasilan (tidak ada kesalahan terjadi). Cara umum untuk menguji apakah file cocok dengan beberapa findkriteria adalah dengan menguji apakah output findkosong. Untuk efisiensi yang lebih baik ketika ada file yang cocok, gunakan -quitpada GNU temukan untuk membuatnya berhenti pada pertandingan pertama, atau head( head -c 1jika tersedia, jika tidak head -n 1yang merupakan standar) pada sistem lain untuk membuatnya mati karena pipa yang rusak daripada menghasilkan output yang panjang.

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

Di bash ≥4 atau zsh, Anda tidak perlu findperintah eksternal untuk pencocokan nama sederhana: Anda dapat menggunakan **/$name. Versi bash:

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

Versi Zsh dengan prinsip serupa:

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

Atau inilah cara yang lebih pendek namun lebih samar untuk menguji keberadaan file yang cocok dengan suatu pola. Kualifikasi glob Nmembuat output kosong jika tidak ada kecocokan, [1]hanya mempertahankan kecocokan pertama, dan e:REPLY=true:mengubah setiap kecocokan untuk diperluas menjadi 1bukan nama file yang cocok. Jadi **/"$name"(Ne:REPLY=true:[1]) falseperluas true falsejika ada kecocokan, atau hanya falsejika tidak ada kecocokan.

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

Akan lebih efisien untuk menggabungkan semua nama Anda menjadi satu pencarian. Jika jumlah pola tidak terlalu besar untuk batas panjang sistem Anda pada baris perintah, Anda dapat menggabungkan semua nama dengan -o, melakukan findpanggilan tunggal , dan memposting proses. Jika tidak ada nama yang mengandung karakter meta shell (sehingga namanya juga findpola), berikut adalah cara untuk pasca-proses dengan awk (belum diuji):

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

Pendekatan lain adalah dengan menggunakan Perl dan File::Find, yang membuatnya mudah untuk menjalankan kode Perl untuk semua file dalam direktori.

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

Pendekatan alternatif adalah membuat daftar nama file di kedua sisi dan bekerja pada perbandingan teks. Versi Zsh:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Saya menerima yang ini karena dua alasan. Saya suka zshsolusinya dengan **sintaks. Ini adalah solusi yang sangat sederhana dan sementara itu mungkin bukan yang paling efisien dalam hal mesin , itu mungkin yang paling efisien dalam hal saya benar-benar mengingatnya! Juga, solusi pertama di sini menjawab pertanyaan yang sebenarnya karena memutar findke sesuatu di mana kode keluar membedakan "Saya mendapat kecocokan" dari "Saya tidak mendapatkan kecocokan".
Andrew Stacey
9

Anda dapat menggunakan statuntuk menentukan apakah ada file di sistem file.

Anda harus menggunakan fungsi shell bawaan untuk menguji apakah file ada.

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

"Tes" bersifat opsional dan skrip akan benar-benar berfungsi tanpanya, tetapi saya membiarkannya agar dapat dibaca.

Sunting: Jika Anda benar-benar tidak memiliki pilihan selain bekerja untuk daftar nama file tanpa path, saya sarankan Anda membuat daftar file sekali dengan find, kemudian beralih dengan grep untuk mencari tahu file mana yang ada.

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

Perhatikan bahwa:

  • daftar file hanya menyertakan file bukan direktori,
  • garis miring dalam pola pertandingan grep jadi kami membandingkan nama file lengkap bukan parsial,
  • dan '$' terakhir dalam pola pencarian adalah untuk mencocokkan akhir baris sehingga Anda tidak mendapatkan kecocokan direktori, hanya tambalan nama file lengkap.
Caleb
sumber
stat membutuhkan lokasi yang tepat, bukan? Saya menggunakan find karena saya hanya punya daftar nama file dan mereka bisa di banyak direktori. Maaf kalau itu tidak jelas.
Andrew Stacey
Hmmm. Ya Anda tidak mengatakan Anda memiliki nama file tanpa jalur! Mungkin Anda bisa memperbaiki masalah ITU itu? Ini akan menjadi cara yang lebih efisien daripada menjalankan menemukan beberapa kali di dataset yang sama.
Caleb
Terima kasih atas hasil editnya, dan maaf lagi karena tidak spesifik. Nama file / path bukanlah sesuatu yang akan saya perbaiki - file mungkin ada di tempat yang berbeda pada kedua sistem jadi saya ingin solusi yang cukup kuat untuk mengatasi itu. Komputer harus bekerja sesuai spesifikasi saya , bukan sebaliknya! Serius, ini bukan sesuatu yang sering saya lakukan - saya sedang mencari beberapa file lama untuk dihapus untuk membuat ruang dan hanya ingin cara "cepat dan kotor" untuk memastikan bahwa mereka ada di cadangan saya.
Andrew Stacey
Pertama-tama Anda tidak perlu path lengkap, hanya path relatif ke struktur direktori apa pun yang Anda buat cadangannya. Izinkan saya menyarankan bahwa jika jalurnya tidak sama, ada kemungkinan besar file tersebut tidak sama dan Anda mungkin mendapatkan hasil positif palsu dari pengujian Anda. Sepertinya solusi Anda mungkin lebih kotor daripada cepat; Saya tidak ingin melihat Anda terbakar dengan berpikir Anda memiliki sesuatu yang tidak Anda miliki. Selain itu, jika file cukup berharga untuk dicadangkan, Anda tidak boleh menghapus pendahuluan, jika tidak, Anda harus membuat cadangan cadangan!
Caleb
Ak! Saya meninggalkan banyak detail untuk mencoba memfokuskan pertanyaan dan Anda mengisi mereka dengan banyak asumsi yang - saya harus katakan - sangat masuk akal tetapi kebetulan sepenuhnya salah! Cukuplah untuk mengatakan bahwa saya tahu bahwa jika file itu ada dan ada dalam direktori dengan jenis nama tertentu, maka saya tahu bahwa itu adalah file asli dan aman untuk menghapus salinan pada mesin saya.
Andrew Stacey
1

Pendekatan pertama dan sederhana, bisa berupa:

a) urutkan daftar file Anda:

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

untuk menemukan yang hilang, atau

comm sorted.lst found.lst

untuk menemukan kecocokan

  • Perangkap:
    • Baris baru dalam nama file sangat sulit untuk ditangani
    • kekosongan dan hal-hal serupa dalam nama file juga tidak bagus. Tetapi karena Anda memiliki kendali atas file dalam daftar file, mungkin solusi ini sudah cukup, namun ...
  • Kekurangan:

    • Ketika menemukan menemukan file, itu terus berjalan untuk menemukan yang lain, dan yang lainnya. Akan menyenangkan untuk melewati pencarian lebih lanjut.
    • find dapat mencari beberapa file sekaligus, dengan beberapa persiapan:

      temukan -nama a.file -atau -nama -b.file -atau -nama c.file ...

Bisakah lokasi menjadi pilihan? Sekali lagi, daftar file yang diasumsikan diasumsikan:

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

Pencarian untuk foo.bar tidak akan cocok dengan file a foo.ba, atau oo.bar dengan --regexp-construct (tidak akan di-confue oleh regex tanpa p).

Anda dapat menentukan basis data spesifik untuk mencari, dan Anda harus memperbaruinya sebelum mencari, jika Anda membutuhkan hasil terbaru.

Pengguna tidak diketahui
sumber
1

Saya pikir ini bisa bermanfaat juga.

Ini adalah solusi satu baris, jika Anda memilih "daftar" Anda menjadi file nyata yang ingin Anda sinkronkan dengan folder lain:

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

untuk membantu membaca:

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

contoh ini tidak termasuk file cadangan "* ~" dan batasan untuk tipe file biasa "-type f"

Aquarius Power
sumber
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}

Mungkin?

Halo71
sumber
0

Mengapa tidak membandingkan panjang daftar permintaan dengan panjang daftar hasil?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Holger Brandl
sumber