จะค้นหาไฟล์ที่มีอักขระ 100% NUL ในเนื้อหาได้อย่างไร


16

คำสั่งบรรทัดคำสั่ง Linux ที่สามารถระบุไฟล์ดังกล่าวคืออะไร?

AFAIK findคำสั่ง (หรือgrep) สามารถจับคู่สตริงเฉพาะภายในไฟล์ข้อความเท่านั้น แต่ผมอยากให้ตรงกับเนื้อหาทั้งหมดคือผมอยากจะเห็นไฟล์ที่ตรงกับการแสดงออกปกติ\0+, ไม่สนใจตัวอักษรท้ายบรรทัด (s) บางทีfind . cat | grepสำนวนอาจใช้งานได้ แต่ฉันไม่ทราบวิธีการทำ grep โดยไม่สนใจบรรทัด (และถือว่าไฟล์เป็นไบนารี)

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


คุณหมายถึงไฟล์ที่มีเลขศูนย์อยู่หรือไม่
ราหุลปาติล

2
ฉันคิดว่ามันเกี่ยวกับตัวละคร NULL แทนที่จะเป็นเลขศูนย์
gertvdijk

10
ลองย้อนกลับไปที่นี่ ทุกสองสามวันเมื่อแล็ปท็อปของคุณค้าง ทำไมเราไม่พยายามที่จะแก้ไขว่าปัญหาที่เกิดขึ้นจริงที่นี่?
D_Bye

2
@D_Bye เป็นความคิดที่ดี แต่จนถึงตอนนี้มันก็ยังไม่มาไกล: [ unix.stackexchange.com/questions/57894/…
Adam Ryczkowski

1
คุณได้พิจารณา-vตัวเลือกในการ grep: กรองไฟล์ทั้งหมดที่มีไบต์ 1 ถึง 255 หรือไม่
ctrl-alt-delor

คำตอบ:


10

คุณสามารถ grepใช้อักขระ using โดยใช้โหมด Perl regex:

$ echo -ne "\0\0" > nul.bin
$ echo -ne "\0x\0" > non-nul.bin
$ grep -P "[^\0]" *.bin
Binary file non-nul.bin matches

ดังนั้นคุณสามารถใช้สิ่งนี้:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done

GNU grep 2.5.4ฉันได้รับผลลัพธ์ที่ไม่คาดคิดโดยใช้ ไม่ว่าฉันจะใช้--binary-files=textหรือ--binary-files=binaryไม่ก็ให้trueผลลัพธ์สำหรับค่าข้อมูลที่ไม่ว่างทั้งหมดเช่น "\0\0", "\0x\0", "abcd"... รหัสที่แน่นอนผมใช้คือ: for typ in binary text ;do for dat in '\0\0' '\0x\0' 'abcd' '' ;do printf "$dat" >f; grep --binary-files=$typ -P '[^\0]' f >/dev/null && echo true || echo false; done; done
Peter.O

1
GNU grep) 2.10ฉันมีตอนนี้พยายามต่อไป รุ่นที่ใหม่กว่านี้จะให้ผลลัพธ์ที่คาดหวัง ... ดังนั้น +1 ที่ล่าช้า
Peter.O

1
ล้มเหลวในไฟล์ที่สร้างขึ้นด้วยprintf '\0\n\0\0\n\n' > fileหรือprintf '\n' > fileสำหรับเรื่องที่
Stéphane Chazelas

2
@ StéphaneChazelas OP พูดว่า "ไม่สนใจตัวอักษรท้ายบรรทัด" ดังนั้นไฟล์ใด ๆ ที่ประกอบด้วยเฉพาะ\0และ\nตัวอักษร (แม้กระทั่งศูนย์ทั้งสอง) จะเป็นการแข่งขัน
l0b0

6

ฉันเห็นด้วยกับสิ่งที่ D_Bye พูดเกี่ยวกับการค้นหาสาเหตุของปัญหา

อย่างไรก็ตามเพื่อตรวจสอบว่าไฟล์มีเพียง\0และ / หรือ\nคุณสามารถใช้tr:

<file tr -d '\0\n' | wc -c

ซึ่งส่งคืน 0 สำหรับไฟล์ null / newline และว่างเปล่า


2
tr -d '\0\n'แก้ปัญหาการขึ้นบรรทัดใหม่ซึ่งจะทำให้เกิดปัญหา (?) ของไฟล์เปล่าที่แสดงอยู่ในผลลัพธ์ ... มันจะประมวลผลทุกไบต์ของทุกไฟล์แม้ว่า (ซึ่งอาจหรืออาจจะไม่เป็นปัญหา) +1
Peter.O

@ Peter.O: ฉันพลาดการขึ้นบรรทัดใหม่ขอบคุณ โซลูชันนี้ไม่ได้รับการปรับปรุงให้ดีที่สุดและหากต้องเรียกใช้ข้อมูลจำนวนมากจะดีกว่าหากมีโซลูชันที่ดำเนินการค้นหาไบต์ที่ไม่ตรงกัน
ธ อร์

มันใช้งานได้ดีมาก ฉันในกรณีของฉันฉันต้องแน่ใจว่าได้ยกเว้นไฟล์ที่มีความยาวเป็นศูนย์ ขอขอบคุณ.
Adam Ryczkowski

1
อย่างไรก็ตามการทำเช่นนี้จะนับจำนวนไฟล์ที่มีบรรทัดใหม่ว่า "ว่าง"
Chris Down

1
@ChrisDown: ฉันทำให้ข้อความคำตอบชัดเจนว่ามันทำอะไร ยังไม่ชัดเจนว่า OP ต้องการทำอย่างไรกับไฟล์ newline-only
ธ อร์

5

ฉันสงสัยว่าไฟล์เหล่านั้นกระจัดกระจายนั่นคือพวกเขาไม่มีพื้นที่ดิสก์ที่จัดสรรให้พวกเขาพวกเขาเพียงระบุขนาดไฟล์ (duจะรายงาน 0 สำหรับพวกเขา)

ในกรณีนี้ด้วย GNU find คุณสามารถทำได้ (สมมติว่าไม่มีไฟล์พา ธ ที่มีอักขระขึ้นบรรทัดใหม่):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-

จุดดี. ฉันไม่เคยคิดเกี่ยวกับมัน ฉันจะพยายาม. การใช้duจะป้องกันการเกาเนื้อหาของไฟล์ทุกไฟล์ในระบบไฟล์ดังนั้นขั้นตอนทั้งหมดจะใช้เวลาไม่เกิน 30 นาที
Adam Ryczkowski

(และprintf %bเหนือรายงานสิ่งที่duจะรายงาน)
Stéphane Chazelas

ฉันจะเปลี่ยน-size +0ไป-size +1เพื่อให้เป็นศูนย์ไฟล์ยาวจะถูกแยกออกจากผลการค้นหา ไฟล์ที่มี\nอยู่ในพา ธ จะทำให้เกิดปัญหาสำหรับคำสั่งนี้
Tyson

@Tyson -size +0สำหรับขนาดที่มากกว่า 0 -size +1อย่างเคร่งครัดสำหรับขนาดที่มากกว่า 512 อย่างเคร่งครัดข้อ จำกัด ของบรรทัดใหม่ถูกกล่าวถึงแล้ว
Stéphane Chazelas

@ StéphaneChazelasขอบคุณที่ให้ความกระจ่างแก่ฉันเกี่ยวกับ-size +1คุณถูกต้องแน่นอน ฉันแก้ไขคำตอบของฉันแล้ว :-)
Tyson

4

นี่คือโปรแกรมหลามขนาดเล็กที่สามารถทำได้:

import sys

def only_contains_nulls(fobj, chunk_size=1024):
    first = True
    while True:
        data = fobj.read(chunk_size)
        if not data:
            if first:
                return 1  # No data
            else:
                return 0
        if data.strip("\0"):
            return 1
        first = False

if __name__ == '__main__':
    with open(sys.argv[1]) as f:
        sys.exit(only_contains_nulls(f))

และในการดำเนินการ:

$ printf '\0\0\0' > file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Only nulls
$ printf a >> file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Non-null characters

คุณสามารถตรวจสอบหลายไฟล์โดยใช้หาของ-exec, xargsแอฟริกาparallelและโปรแกรมที่คล้ายกัน อีกวิธีนี้จะพิมพ์ชื่อไฟล์ที่ต้องจัดการ:

files=( file1 file2 )
for file in "${files[@]}"; do
    ./onlynulls "$file" || printf '%s\n' "$file"
done

โปรดจำไว้ว่าหากคุณกำลังจะส่งผ่านผลลัพธ์นี้ไปยังโปรแกรมอื่นชื่อไฟล์อาจมีการขึ้นบรรทัดใหม่ดังนั้นคุณควรกำหนดขอบเขตให้แตกต่างกัน (เหมาะสมกับ \0 )

หากคุณมีไฟล์จำนวนมากจะเป็นการดีกว่าถ้าใช้ตัวเลือกสำหรับการประมวลผลแบบขนานเนื่องจากจะอ่านได้ครั้งละหนึ่งไฟล์เท่านั้น


2
ระวังศูนย์ไฟล์ความยาว (เช่น: /etc/nologin, ~/.hushlogin, .nomedia, ... ) จะ misidentified โดยคำตอบนี้
Tyson

@ ไทสันขอบคุณสำหรับการชี้ให้เห็น! ฉันเพิ่งแก้ไขมัน
Chris Down

3

ค้นหาไฟล์ที่มีเฉพาะ null-chars '\ 0' และ newline chars '\ n' ในsedสาเหตุค้นหาแต่ละไฟล์จะเลิกทันทีที่หาใด ๆ อักขระที่ไม่โมฆะในบรรทัด
q

find -type f -name 'file-*' |
  while IFS= read -r file ;do 
      out=$(sed -n '1=; /^\x00\+$/d; i non-null
                      ; q' "$file")
      [[ $out == "1" ]] &&  echo "$file"
  done

ทำไฟล์ทดสอบ

> file-empty
printf '%s\n' 'line1' 'line2' 'line3'      > file-with-text           
printf '%4s\n' '' '' xx | sed 's/ /\x00/g' > file-with-text-and-nulls
printf '%4s\n' '' '' '' | sed 's/ /\x00/g' > file-with-nulls-and-newlines
printf '%4s'   '' '' '' | sed 's/ /\x00/g' > file-with-nulls-only

เอาท์พุต

./file-with-nulls-and-newlines
./file-with-nulls-only

ไม่ว่าจะเป็น-print0ข้อโต้แย้งที่ดูเหมือนว่าจะหายไปfindหรือIFS=บางส่วนเกิดความสับสน ตัวคั่นที่ต้องการคืออะไร
Tyson

3

นี้หนึ่งซับเป็นวิธีที่มีประสิทธิภาพมากที่สุดในการค้นหาไฟล์ NUL 100% โดยใช้ GNU find, xargsและgrep(สมมติว่าหลังถูกสร้างขึ้นด้วยการสนับสนุน PCRE):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00]" --

ข้อดีของวิธีนี้เหนือคำตอบอื่น ๆ ที่ให้ไว้คือ:

  • ไฟล์ที่ไม่กระจายอยู่ในการค้นหา
  • ไฟล์ที่อ่านไม่ได้จะไม่ถูกส่งไปยัง grep หลีกเลี่ยงการPermission deniedเตือน
  • grepจะหยุดอ่านข้อมูลจากไฟล์หลังจากค้นหาไบต์ที่ไม่ใช่ nul ใด ๆ ( LC_ALL=Cใช้เพื่อให้แน่ใจว่าแต่ละไบต์ถูกตีความว่าเป็นอักขระ )
  • ไฟล์ที่ว่างเปล่า (ศูนย์ไบต์) จะไม่รวมอยู่ในผลลัพธ์
  • น้อยลง grepกระบวนการตรวจสอบหลายไฟล์อย่างมีประสิทธิภาพ
  • เส้นทางที่มีการขึ้นบรรทัดใหม่หรือเริ่มต้นด้วย -การจัดการอย่างถูกต้อง
  • ทำงานบนระบบฝังตัวส่วนใหญ่ที่ไม่มี Python / Perl

การส่งผ่าน-Zตัวเลือกไปยังgrepและการใช้งานxargs -r0 ...จะช่วยให้สามารถดำเนินการเพิ่มเติมกับไฟล์ nul 100% (เช่น: ล้างข้อมูล):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00]" -- |
  xargs -r0 rm --

ฉันขอแนะนำให้ใช้findตัวเลือกต่าง ๆ-Pเพื่อหลีกเลี่ยงการเชื่อมโยงไปถึงและ-xdevเพื่อหลีกเลี่ยงการข้ามระบบไฟล์ (เช่น: การเมาท์ระยะไกล, โครงสร้างอุปกรณ์, การเชื่อมต่อเมา ฯลฯ )

สำหรับการเพิกเฉยอักขระบรรทัดปลายตัวแปรต่อไปนี้ควรใช้งานได้ (แม้ว่าฉันไม่คิดว่านี่เป็นความคิดที่ดี):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00\r\n]" --

รวมทั้งหมดเข้าด้วยกันรวมถึงการลบไฟล์ที่ไม่ต้องการ (100% nul / อักขระขึ้นบรรทัดใหม่) เพื่อป้องกันไม่ให้ทำการสำรองข้อมูล:

find -P . -xdev -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00\r\n]" -- |
  xargs -0 rm --

ผมไม่แนะนำให้รวมถึงไฟล์ที่ว่างเปล่า (ศูนย์ bytes) พวกเขามักจะมีอยู่สำหรับมาก เฉพาะ วัตถุประสงค์


การเป็นทางเลือกที่เร็วที่สุดในหลาย ๆ ทางเป็นข้ออ้างที่กล้าหาญ ฉันจะทำเครื่องหมายคำตอบของคุณว่ายอมรับถ้าคุณเพิ่มเกณฑ์มาตรฐาน :-)
Adam Ryczkowski

มาตรฐานดังกล่าวจะขึ้นอยู่กับปัจจัยหลายประการรวมถึงประสิทธิภาพของระบบย่อยดิสก์ต่างๆ
Tyson

แน่นอน แต่ไม่มีอะไรดีไปกว่าสิ่งใด วิธีการต่างๆนั้นปรับการใช้ CPU ให้เหมาะสมแตกต่างกันดังนั้นจึงเหมาะสมที่จะทำการทดสอบบน SSD หรือแม้แต่ในไฟล์แคช ใช้เครื่องที่คุณทำงานอยู่เขียนประโยคหนึ่งว่ามันคืออะไร (ชนิดของ CPU, ไม่มีแกน, RAM, ชนิดของฮาร์ดไดรฟ์), อธิบายชุดไฟล์ (เช่นเคอร์เนลต้นฉบับโคลน + ไฟล์ 1GB เต็มไป\0ด้วยรู 900MB) และ ช่วงเวลาปัจจุบันของผลลัพธ์ หากคุณทำในลักษณะที่เป็นมาตรฐานที่น่าเชื่อถือสำหรับคุณก็น่าจะเป็นสิ่งที่น่าเชื่อถือสำหรับเราทุกคน
Adam Ryczkowski

"ระบบฝังตัวส่วนใหญ่" ไม่มียูทิลิตี้ GNU คนที่มีโอกาสยุ่งมากกว่า
Stéphane Chazelas

-Pfindเป็นค่าเริ่มต้นใน หากคุณต้องการที่จะปฏิบัติตาม symlinks ก็/-L -followคุณจะพบว่า POSIX ไม่ได้ระบุตัวเลือกนั้นสำหรับfind(แม้ว่า POSIX เป็นผู้ที่แนะนำ -P / -H / -L สำหรับคำสั่งสองสามข้อ)
Stéphane Chazelas

0

สำหรับการใช้ GNU sed คุณสามารถใช้-zตัวเลือกซึ่งกำหนดบรรทัดเป็นสตริงที่สิ้นสุดด้วยศูนย์และจับคู่และลบบรรทัดว่างดังนี้:

if [ "$( sed -z '/^$/d' "$file" | head -c 1 | wc -c )" -eq 0 ]; then
    echo "$file contains only NULL!"
fi

คำสั่ง head ในระหว่างนั้นเป็นเพียงการเพิ่มประสิทธิภาพ


-1

หลาม

ไฟล์เดียว

กำหนดนามแฝง:

alias is_binary="python -c 'import sys; sys.exit(not b\"\x00\" in open(sys.argv[1], \"rb\").read())'"

ทดสอบมัน

$ is_binary /etc/hosts; echo $?
1
$ is_binary `which which`; echo $?
0

หลายไฟล์

ค้นหาไฟล์ไบนารีทั้งหมดแบบเรียกซ้ำ:

IS_BINARY='import sys; sys.exit(not b"\x00" in open(sys.argv[1], "rb").read())'
find . -type f -exec bash -c "python -c '$IS_BINARY' {} && echo {}" \;

เพื่อหาไฟล์ไบนารีไม่ใช่ทุกการเปลี่ยนแปลงด้วย&&||


1
คำถามที่ถามเพื่อระบุไฟล์ที่มีอักขระ nul เท่านั้น (ไม่สนใจบรรทัดใหม่) รหัส Python ที่ระบุที่นี่ระบุไฟล์ที่มีอักขระ nul ใด ๆ
Tyson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.