Làm cách nào để tìm tập tin nào bị thiếu trong danh sách?


9

Tôi có một danh sách các tệp mà tôi muốn kiểm tra xem chúng có tồn tại trên hệ thống tệp của tôi không. Tôi nghĩ làm việc này bằng cách sử dụng findnhư trong:

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

(sử dụng zsh) nhưng điều đó finddường như không hoạt động để thoát khỏi 0việc nó có tìm thấy tệp hay không. Tôi đoán tôi có thể vượt qua một số thử nghiệm khác để kiểm tra xem liệu có findtạo ra bất kỳ đầu ra nào không (thô nhưng hiệu quả sẽ thay thế > /dev/nullbằng |grep '') nhưng cảm giác này giống như sử dụng một con troll để bắt một con dê (các quốc tịch khác có thể nói gì đó về búa tạ và quả óc chó ).

Có cách nào để ép buộc findđể cho tôi một giá trị thoát hữu ích không? Hoặc ít nhất để có được danh sách những tập tin không tìm thấy? (Tôi có thể tưởng tượng cái sau có lẽ dễ dàng hơn bởi một số lựa chọn khôn ngoan của các kết nối logic, nhưng tôi dường như luôn bị trói buộc trong các nút thắt khi tôi cố gắng tìm ra nó.)

Bối cảnh / Động lực: Tôi có bản sao lưu "chính" và tôi muốn kiểm tra xem một số tệp trên máy cục bộ của tôi có tồn tại trên bản sao lưu chính của mình trước khi xóa chúng (để tạo một chút dung lượng). Vì vậy, tôi đã lập một danh sách các tệp, chỉnh sửa sshchúng cho máy chủ và sau đó không biết tìm ra cách tốt nhất để tìm các tệp bị thiếu.


Tôi cập nhật giải pháp của tôi để sử dụng nhanh hơn nhiều locate.
người dùng không xác định

@userunknown locatekhông hiển thị trạng thái hiện tại của hệ thống tệp, nó có thể là một ngày hoặc thậm chí là một tuần. Đó là phù hợp như là một cơ sở để thử nghiệm sao lưu.
Volker Siegel

Câu trả lời:


5

findxem xét không tìm thấy gì một trường hợp đặc biệt của thành công (không có lỗi xảy ra). Một cách chung để kiểm tra xem các tệp có khớp với một số findtiêu chí hay không là kiểm tra xem đầu ra findcó trống không. Để có hiệu quả tốt hơn khi có các tệp phù hợp, hãy sử dụng -quittrên GNU find để làm cho nó thoát ở lần khớp đầu tiên hoặc head( head -c 1nếu có, nếu không head -n 1, là tiêu chuẩn) trên các hệ thống khác để làm cho nó chết vì đường ống bị hỏng thay vì tạo ra đầu ra dài.

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

Trong bash 4 hoặc zsh, bạn không cần findlệnh bên ngoài cho khớp tên đơn giản: bạn có thể sử dụng **/$name. Phiên bản Bash:

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

Phiên bản Zsh theo nguyên tắc tương tự:

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

Hoặc đây là một cách ngắn hơn nhưng khó hiểu hơn để kiểm tra sự tồn tại của một tệp khớp với một mẫu. Vòng loại toàn cầu Nlàm cho đầu ra trống nếu không có kết quả khớp, [1]chỉ giữ lại kết quả khớp đầu tiên và e:REPLY=true:thay đổi từng kết quả để mở rộng thành 1thay vì tên tệp phù hợp. Vì vậy, **/"$name"(Ne:REPLY=true:[1]) falsemở rộng đến true falsenếu có một trận đấu, hoặc chỉ falsekhi không có trận đấu.

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

Sẽ hiệu quả hơn khi kết hợp tất cả tên của bạn vào một tìm kiếm. Nếu số lượng mẫu không quá lớn so với giới hạn độ dài hệ thống của bạn trên một dòng lệnh, bạn có thể nối tất cả các tên với -o, thực hiện một findcuộc gọi và xử lý hậu quả đầu ra. Nếu không có tên nào chứa ký tự đại diện hệ vỏ (để tên cũng là findmẫu), thì đây là một cách để xử lý hậu kỳ với awk (chưa được kiểm tra):

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

Một cách tiếp cận khác là sử dụng Perl và File::Find, giúp dễ dàng chạy mã Perl cho tất cả các tệp trong một thư mục.

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

Một cách tiếp cận khác là tạo ra một danh sách các tên tệp ở cả hai bên và làm việc trên một so sánh văn bản. Phiên bản Zsh:

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

Tôi chấp nhận điều này vì hai lý do. Tôi thích zshgiải pháp với **cú pháp. Đó là một giải pháp rất đơn giản và trong khi nó có thể không hiệu quả nhất về mặt máy móc , thì nó có lẽ là hiệu quả nhất về mặt tôi thực sự nhớ nó! Ngoài ra, giải pháp đầu tiên ở đây trả lời câu hỏi thực tế ở chỗ nó xoắn findvào một cái gì đó trong đó mã thoát phân biệt "Tôi có một trận đấu" với "Tôi không nhận được một trận đấu".
Andrew Stacey

9

Bạn có thể sử dụng statđể xác định xem một tệp có tồn tại trên hệ thống tệp không.

Bạn nên sử dụng các hàm shell tích hợp để kiểm tra nếu các tệp tồn tại.

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

"Thử nghiệm" là tùy chọn và tập lệnh sẽ thực sự hoạt động mà không có nó, nhưng tôi để nó ở đó để dễ đọc.

Chỉnh sửa: Nếu bạn thực sự không có tùy chọn nào ngoài việc làm việc với danh sách tên tệp không có đường dẫn, tôi khuyên bạn nên tạo danh sách các tệp một lần bằng tìm, sau đó lặp lại nó với grep để tìm ra tệp nào ở đó.

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

Lưu ý rằng:

  • danh sách tập tin chỉ bao gồm các tập tin không phải thư mục,
  • dấu gạch chéo trong mẫu khớp grep là vì vậy chúng tôi so sánh tên tệp đầy đủ không phải là partials,
  • và '$' cuối cùng trong mẫu tìm kiếm là khớp với cuối dòng để bạn không nhận được các kết quả trùng khớp thư mục, chỉ các bản vá tên tệp đầy đủ.

stat cần vị trí chính xác, phải không? Tôi đang sử dụng find vì tôi chỉ có một danh sách các tên tệp và chúng có thể nằm trong nhiều thư mục. Xin lỗi nếu điều đó không rõ ràng.
Andrew Stacey

Hừm. Bạn không nói rằng bạn có tên tập tin mà không có đường dẫn! Có lẽ bạn có thể khắc phục vấn đề THAT thay thế? Nó sẽ hiệu quả hơn nhiều so với việc chạy tìm một loạt các lần trên cùng một tập dữ liệu.
Caleb

Cảm ơn đã chỉnh sửa, và xin lỗi lần nữa vì đã không cụ thể. Tên / đường dẫn tệp không phải là thứ tôi sẽ sửa - các tệp có thể ở các vị trí khác nhau trên hai hệ thống vì vậy tôi muốn một giải pháp đủ mạnh để khắc phục điều đó. Máy tính nên hoạt động theo thông số kỹ thuật của tôi , không phải cách khác! Nghiêm túc mà nói, đây không phải là điều tôi thường làm - tôi đang tìm một số tệp cũ cần xóa để tạo khoảng trống và chỉ muốn một cách "nhanh chóng" bẩn để đảm bảo rằng chúng nằm trong bản sao lưu của tôi.
Andrew Stacey

Trước hết bạn sẽ không cần đường dẫn đầy đủ, chỉ cần một đường dẫn tương đối đến bất kỳ cấu trúc thư mục nào bạn đang sao lưu. Cho phép tôi đề xuất rằng nếu đường dẫn không giống nhau, rất có thể tệp không giống nhau và bạn có thể nhận được kết quả dương tính giả trong bài kiểm tra của mình. Có vẻ như giải pháp của bạn có thể bẩn hơn là nhanh chóng; Tôi sẽ không muốn nhìn thấy bạn bị đốt cháy khi nghĩ rằng bạn đã có một cái gì đó bạn không. Ngoài ra, nếu các tệp đủ giá trị để sao lưu ở vị trí đầu tiên, bạn không nên xóa các bản gốc, nếu không bạn cần sao lưu các bản sao lưu của mình!
Caleb

Ôi! Tôi đã bỏ qua vô số chi tiết để cố gắng tập trung vào câu hỏi và bạn đang điền vào đó những tải giả định - tôi nên nói - hoàn toàn hợp lý nhưng lại hoàn toàn sai! Chỉ cần nói rằng tôi biết rằng nếu tệp đó ở đó và nằm trong một thư mục có một loại tên cụ thể thì tôi biết rằng đó là tệp gốc và an toàn để xóa bản sao trên máy của tôi.
Andrew Stacey

1

Cách tiếp cận đầu tiên, đơn giản, có thể là:

a) sắp xếp filelist của bạ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

để tìm thấy sự bỏ lỡ, hoặc

comm sorted.lst found.lst

để tìm trận đấu

  • Cạm bẫy:
    • Dòng mới trong tên tệp rất khó xử lý
    • khoảng trống và những thứ tương tự trong tên tập tin cũng không đẹp. Nhưng vì bạn có quyền kiểm soát các tệp trong danh sách các tệp, có thể giải pháp này đã đủ, tuy nhiên ...
  • Hạn chế:

    • Khi tìm thấy một tập tin, nó tiếp tục chạy để tìm một tập tin khác và một tập tin khác. Nó sẽ được tốt đẹp để bỏ qua tìm kiếm thêm.
    • tìm có thể tìm kiếm nhiều tập tin cùng một lúc, với một số chuẩn bị:

      tìm -name a.file -or -name -b.file -or -name c.file ...

Có thể xác định vị trí là một lựa chọn? Một lần nữa, một danh sách các tập tin giả định:

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

Tìm kiếm cho foo.bar sẽ không khớp với tệp aa foo.ba hoặc oo.bar với --regrec-construc (không bị giới hạn bởi regex mà không có p).

Bạn có thể chỉ định một cơ sở dữ liệu cụ thể để định vị và bạn phải cập nhật nó trước khi tìm kiếm, nếu bạn cần kết quả gần đây nhất.


1

Tôi nghĩ rằng điều này cũng có thể hữu ích.

Đây là giải pháp một dòng, trong trường hợp bạn chọn "danh sách" của mình là các tệp thực mà bạn muốn đồng bộ hóa với thư mục khác:

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

để giúp đọc:

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

ví dụ này không bao gồm các tệp "* ~" sao lưu và giới hạn đối với loại tệp thông thường "-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}

Có lẽ?


0

Tại sao không chỉ đơn giản là so sánh độ dài của danh sách truy vấn với độ dài của danh sách kết quả?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.