ฉันจะค้นหาไฟล์ที่หายไปจากรายการได้อย่างไร


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

ในทุบตี≥4หรือ zsh คุณไม่จำเป็นต้องภายนอกคำสั่งสำหรับการแข่งขันชื่อง่ายๆที่คุณสามารถใช้find **/$nameรุ่นทุบตี:

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เป็นสิ่งที่รหัสทางออกแตกต่าง "ฉันได้ตรงกัน" จาก "ฉันไม่ได้ตรงกัน"
Andrew Stacey

9

คุณสามารถใช้statเพื่อตรวจสอบว่ามีไฟล์อยู่ในระบบไฟล์หรือไม่

คุณควรใช้ฟังก์ชั่นเชลล์ในตัวเพื่อทดสอบว่ามีไฟล์อยู่หรือไม่

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

"การทดสอบ" เป็นตัวเลือกและสคริปต์จะใช้งานได้จริง แต่ฉันก็ปล่อยให้มันอ่านได้

แก้ไข:หากคุณไม่มีตัวเลือกจริงๆ แต่ทำงานกับรายชื่อไฟล์ที่ไม่มีพา ธ ฉันขอแนะนำให้คุณสร้างรายการไฟล์หนึ่งครั้งด้วยการค้นหาจากนั้นวนซ้ำด้วย grep เพื่อหาไฟล์ที่มี

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

โปรดทราบว่า:

  • รายการไฟล์จะมีเฉพาะไฟล์ที่ไม่ใช่ไดเรกทอรี
  • เครื่องหมายทับในรูปแบบการจับคู่ grep คือดังนั้นเราจึงเปรียบเทียบชื่อไฟล์แบบเต็มไม่ใช่ส่วนหนึ่ง
  • และ '$' สุดท้ายในรูปแบบการค้นหาคือจับคู่จุดสิ้นสุดของบรรทัดเพื่อให้คุณไม่ได้รับการจับคู่ไดเรกทอรีเฉพาะแพทช์ชื่อไฟล์แบบเต็ม

สถิติต้องการตำแหน่งที่แน่นอนใช่ไหม ฉันใช้ค้นหาเพราะฉันเพิ่งมีรายชื่อไฟล์และอาจอยู่ในไดเรกทอรีจำนวนมาก ขออภัยหากไม่ชัดเจน
Andrew Stacey

อืมม ใช่คุณไม่ได้บอกว่าคุณมีชื่อไฟล์โดยไม่มีเส้นทาง! บางทีคุณสามารถแก้ไขปัญหานั้นแทนได้ มันจะมีประสิทธิภาพมากกว่าการใช้ค้นหาหลาย ๆ ครั้งในชุดข้อมูลเดียวกัน
Caleb

ขอบคุณสำหรับการแก้ไขและขออภัยอีกครั้งสำหรับการไม่เจาะจง ชื่อ / พา ธ ของไฟล์ไม่ใช่สิ่งที่ฉันกำลังจะแก้ไข - ไฟล์อาจอยู่ในสถานที่ที่แตกต่างกันในทั้งสองระบบดังนั้นฉันจึงต้องการโซลูชันที่แข็งแกร่งพอที่จะแก้ไขได้ คอมพิวเตอร์ควรทำงานตามข้อกำหนดของฉันไม่ใช่อย่างอื่น! อย่างจริงจังนี่ไม่ใช่สิ่งที่ฉันทำบ่อยๆ - ฉันกำลังมองหาไฟล์เก่า ๆ ที่จะลบเพื่อให้มีที่ว่างและเพียงแค่ต้องการวิธี "รวดเร็ว 'สกปรก" เพื่อให้แน่ใจว่าพวกเขาอยู่ในการสำรองข้อมูลของฉัน
Andrew Stacey

ก่อนอื่นคุณไม่จำเป็นต้องใช้พา ธ เต็มเพียงแค่พา ธ สัมพัทธ์กับโครงสร้างไดเรกทอรีที่คุณสำรองข้อมูล อนุญาตให้ฉันแนะนำว่าหากเส้นทางไม่เหมือนกันมีโอกาสที่ไฟล์จะไม่เหมือนกันและคุณอาจได้รับผลบวกปลอมจากการทดสอบของคุณ ดูเหมือนว่าโซลูชันของคุณอาจสกปรกกว่ารวดเร็ว ฉันไม่ต้องการเห็นคุณถูกเผาไหม้โดยคิดว่าคุณมีบางสิ่งที่คุณไม่ต้องการ นอกจากนี้หากไฟล์มีค่าเพียงพอสำหรับการสำรองข้อมูลตั้งแต่แรกคุณไม่ควรลบรายการหลักไม่เช่นนั้นคุณจะต้องสำรองข้อมูลสำรอง!
คาเลบ

Ak! ฉันออกจากรายละเอียดจำนวนมากเพื่อพยายามที่จะมุ่งเน้นคำถามและคุณกำลังกรอกข้อมูลด้วยสมมติฐานมากมายซึ่ง - ฉันควรจะบอกว่า - มีเหตุผลอย่างสมบูรณ์ แต่เกิดขึ้นผิดอย่างสมบูรณ์! เพียงพอที่จะบอกว่าฉันรู้ว่าถ้าไฟล์อยู่ในนั้นและอยู่ในไดเรกทอรีที่มีชื่อเฉพาะฉันรู้ว่ามันเป็นไฟล์ต้นฉบับและปลอดภัยที่จะลบสำเนาในเครื่องของฉัน
Andrew Stacey

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

เพื่อค้นหาการแข่งขัน

  • ผิดพลาด:
    • บรรทัดใหม่ในชื่อไฟล์นั้นจัดการได้ยากมาก
    • ช่องว่างและสิ่งที่คล้ายกันในชื่อไฟล์ก็ไม่ได้ดีเช่นกัน แต่เนื่องจากคุณสามารถควบคุมไฟล์ในรายการไฟล์ได้โซลูชันนี้อาจเพียงพอแล้ว ...
  • ข้อเสีย:

    • เมื่อค้นหาพบไฟล์มันจะยังคงทำงานเพื่อค้นหาไฟล์อื่นและอีกไฟล์หนึ่ง มันเป็นการดีที่จะข้ามการค้นหาเพิ่มเติม
    • พบสามารถค้นหาไฟล์หลายไฟล์พร้อมกันด้วยการเตรียม:

      ค้นหา -name a.file - หรือ - ชื่อ -b.file - หรือ - ชื่อ 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-construct (เพื่อไม่ให้สับสนโดย regex โดยไม่มี 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

ตัวอย่างนี้ไม่รวมไฟล์ "* ~" สำรองและ จำกัด ประเภทไฟล์ปกติ "-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}

อาจจะ?


0

ทำไมไม่เพียงเปรียบเทียบความยาวของรายการแบบสอบถามกับความยาวของรายการผลลัพธ์

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.