Bagaimana menemukan file dengan 100% NUL karakter dalam isinya?

16

Apa perintah baris perintah Linux yang dapat mengidentifikasi file seperti itu?

AFAIK findperintah (atau grep) hanya bisa cocok dengan string tertentu di dalam file teks. Tapi saya ingin mencocokkan seluruh konten, yaitu saya ingin melihat file mana yang cocok dengan ekspresi reguler \0+, mengabaikan karakter garis akhir . Mungkin find . cat | grepidiom bisa bekerja, tapi saya tidak tahu bagaimana membuat garis mengabaikan grep (dan memperlakukan file sebagai biner).

Latar Belakang: Setiap beberapa hari, ketika laptop saya macet, partisi btrf saya kehilangan informasi: file yang dibuka untuk penulisan mendapat isinya diganti dengan nol (ukuran file tetap lebih atau kurang utuh). Saya menggunakan sinkronisasi dan saya tidak ingin menyebarkan file palsu ini: Saya perlu cara untuk mengidentifikasi mereka sehingga saya dapat mengambilnya dari cadangan.

Adam Ryczkowski
sumber
Maksud Anda file memiliki angka nol di dalamnya?
Rahul Patil
2
Saya pikir ini tentang karakter NULL daripada angka nol.
gertvdijk
10
Mari kita mundur ke sini. Setiap beberapa hari, kapan laptop Anda macet? Mengapa kita tidak mencoba untuk memperbaiki itu , masalah yang sebenarnya di sini?
D_Bye
2
@D_Bye itu ide bagus, tapi sejauh ini tidak terlalu jauh: [ unix.stackexchange.com/questions/57894/…
Adam Ryczkowski
1
sudahkah Anda mempertimbangkan -vopsi untuk melakukan grep: memfilter semua file yang memiliki byte 1 hingga 255.
ctrl-alt-delor

Jawaban:

10

Anda dapat grepuntuk karakter ␀ menggunakan mode 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

Jadi Anda bisa menggunakan ini:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done
l0b0
sumber
Saya mendapatkan hasil yang tidak terduga, menggunakan GNU grep 2.5.4. Terlepas dari apakah saya menggunakan --binary-files=textatau --binary-files=binary, itu memberikan truehasil untuk semua nilai data yang tidak kosong, misalnya. "\0\0", "\0x\0", "abcd"... Kode tepat saya digunakan adalah: 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
Saya sekarang telah mencoba lebih jauh GNU grep) 2.10. Versi yang lebih baru ini memang memberikan hasil yang diharapkan ... jadi, +1 terlambat
Peter.O
1
Gagal pada file yang dibuat dengan printf '\0\n\0\0\n\n' > fileatau printf '\n' > fileuntuk hal itu.
Stéphane Chazelas
2
@ StéphaneChazelas OP memang mengatakan "mengabaikan karakter garis akhir." Jadi file apa pun yang hanya terdiri dari karakter \0dan \n(bahkan nol dari keduanya) akan cocok.
l0b0
6

Saya setuju dengan apa yang dikatakan D_Bye tentang menemukan akar masalah.

Pokoknya untuk memeriksa apakah suatu file hanya berisi \0dan / atau \nAnda dapat menggunakan tr:

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

Yang mengembalikan 0 untuk file null / baris baru dan kosong.

Thor
sumber
2
tr -d '\0\n'memecahkan masalah baris baru, yang kemudian hanya meninggalkan masalah (?) file kosong yang tercantum dalam output ... Itu memproses setiap byte setiap file meskipun (yang mungkin atau mungkin tidak menjadi masalah) +1
Peter.O
@ Peter.O: Saya melewatkan persyaratan baris baru, terima kasih. Solusi ini tidak terlalu dioptimalkan dan jika dijalankan pada banyak data, akan lebih baik dengan solusi yang bergerak setelah menemukan byte yang tidak cocok.
Thor
Ini bekerja dengan sangat baik. Saya kasus saya, saya hanya harus memastikan untuk mengecualikan file panjang nol. Terima kasih.
Adam Ryczkowski
1
Namun, ini juga akan menghitung file dengan baris baru sebagai "kosong".
Chris Down
1
@ ChrisDown: Saya membuat teks jawaban jelas apa fungsinya. Tidak jelas apa yang OP ingin lakukan dengan file baris-baru.
Thor
5

Saya menduga file-file itu jarang, yaitu mereka tidak memiliki ruang disk yang dialokasikan untuk mereka, mereka hanya menentukan ukuran file ( duakan melaporkan 0 untuk mereka).

Dalam hal ini, dengan GNU find, Anda dapat melakukannya (dengan asumsi tidak ada jalur file yang mengandung karakter baris baru):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-
Stéphane Chazelas
sumber
Poin yang bagus. Saya tidak pernah berpikir tentang hal itu. Saya akan mencoba. Menggunakan duakan mencegah dari menggaruk konten setiap file dalam sistem file, sehingga seluruh prosedur tidak akan memakan waktu 30 menit untuk menyelesaikan.
Adam Ryczkowski
(dan di printf %batas melaporkan apa yang duakan dilaporkan)
Stéphane Chazelas
Saya akan berubah -size +0menjadi -size +1file dengan panjang nol tidak termasuk dalam hasil. File-file yang berisi \npath mereka akan menyebabkan masalah untuk perintah ini.
Tyson
@Tyson -size +0adalah untuk ukuran yang benar-benar lebih besar dari 0. -size +1akan untuk ukuran yang benar-benar lebih besar dari 512. Batasan baris baru telah disebutkan.
Stéphane Chazelas
@ StéphaneChazelas Terima kasih telah memberi tahu saya -size +1, Anda memang benar. Saya sudah memperbaiki jawaban saya . :-)
Tyson
4

Berikut ini adalah program python kecil yang dapat melakukannya:

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))

Dan beraksi:

$ 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

Anda dapat memeriksa beberapa file dengan menggunakan find ini -exec, xargs, GNU parallel, dan program-program serupa. Atau, ini akan mencetak nama file yang perlu ditangani:

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

Ingatlah bahwa jika Anda akan meneruskan output ini ke program lain, nama file dapat berisi baris baru, jadi Anda harus membatasi itu secara berbeda (sesuai, dengan \0).

Jika Anda memiliki banyak file, akan lebih baik menggunakan opsi untuk pemrosesan paralel, karena ini hanya membaca satu file pada satu waktu.

Chris Down
sumber
2
Hati-hati, panjang nol file (misalnya: /etc/nologin, ~/.hushlogin, .nomedia, ...) yang salah diidentifikasi oleh jawaban ini.
Tyson
@Tyson Terima kasih telah menunjukkan itu! Saya baru saja memperbaikinya.
Chris Down
3

Temukan file yang hanya berisi karakter null '\ 0' dan karakter baris baru '\ n'.
The qdi sed penyebab setiap file mencari untuk segera berhenti setelah menemukan karakter non-null dalam garis.

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

Buat file uji

> 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

keluaran

./file-with-nulls-and-newlines
./file-with-nulls-only
Peter.O
sumber
Entah -print0argumen itu tampaknya hilang findatau IFS=bagian itu kacau. Apa pembatas yang dimaksud?
Tyson
3

Ini satu-kapal adalah cara yang paling efisien untuk menemukan 100% file nul menggunakan GNU find, xargsdan grep(dengan asumsi yang terakhir dibangun dengan dukungan PCRE):

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

Keuntungan dari metode ini dibandingkan jawaban lain yang disediakan adalah:

  • file non-jarang termasuk dalam pencarian.
  • file yang tidak dapat dibaca tidak diteruskan ke grep, menghindari Permission deniedperingatan.
  • grepakan berhenti membaca data dari file setelah menemukan byte non-nul ( LC_ALL=Cdigunakan untuk memastikan setiap byte diartikan sebagai karakter ).
  • file kosong (nol byte) tidak termasuk dalam hasil.
  • lebih sedikit grepproses secara efisien memeriksa banyak file.
  • jalur yang berisi baris baru atau mulai dengan -ditangani dengan benar.
  • bekerja pada sebagian besar sistem embedded yang tidak memiliki Python / Perl.

Melewati -Zopsi ke grepdan menggunakan xargs -r0 ...memungkinkan tindakan lebih lanjut untuk dilakukan pada file nul 100% (misalnya: pembersihan):

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

Saya juga merekomendasikan menggunakan findopsi -Puntuk menghindari symlink berikut, dan -xdevuntuk menghindari melintasi sistem file (misalnya: mount jarak jauh, bagan perangkat, bind mounts, dll).

Untuk mengabaikan karakter garis akhir , varian berikut harus berfungsi (walaupun saya pikir ini bukan ide yang bagus):

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

Menyatukan semuanya, termasuk menghapus file yang tidak diinginkan (100% nul / karakter baris baru) untuk mencegahnya dicadangkan:

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

Saya tidak menyarankan untuk memasukkan file kosong (nol byte), mereka sering ada untuk tujuan yang sangat spesifik .

Tyson
sumber
Menjadi yang tercepat dari begitu banyak alternatif adalah klaim yang berani. Saya akan menandai jawaban Anda sebagai diterima jika Anda menambahkan tolok ukur :-)
Adam Ryczkowski
Benchmark seperti itu akan tergantung pada banyak faktor, termasuk kinerja berbagai subsistem disk.
Tyson
Tentu saja, tetapi segalanya lebih baik daripada tidak sama sekali. Berbagai pendekatan mengoptimalkan penggunaan CPU secara berbeda, jadi masuk akal untuk membandingkannya pada SSD atau bahkan pada file yang di-cache. Ambil mesin yang sedang Anda kerjakan, tulis satu kalimat apa itu (tipe CPU, no core, RAM, tipe hard drive), jelaskan set file (mis. Klon sumber kernel + 1GB file penuh \0dengan lubang 900MB di dalamnya) dan menyajikan waktu dari hasil. Jika Anda melakukannya dengan cara patokan meyakinkan untuk Anda, kemungkinan besar itu akan meyakinkan kita semua
Adam Ryczkowski
"kebanyakan sistem tertanam" tidak memiliki utilitas GNU. Kemungkinan kotak sibuk.
Stéphane Chazelas
-Padalah default dalam find. Jika Anda ingin mengikuti symlink, ini -L/ -follow. Anda akan menemukan POSIX bahkan tidak menentukan opsi untuk find(meskipun POSIX adalah orang yang memperkenalkan -P / -H / -L untuk beberapa perintah).
Stéphane Chazelas
0

Untuk menggunakan sed GNU Anda dapat menggunakan -zopsi, yang mendefinisikan sebuah baris sebagai string yang diakhiri tanpa nol dan cocok dengan dan menghapus baris kosong seperti:

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

Peralihan perintah kepala hanyalah sebuah optimasi.

mxmlnkn
sumber
-1

Python

File tunggal

Tentukan alias:

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

Menguji:

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

Banyak file

Temukan semua file biner secara rekursif:

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 {}" \;

Untuk menemukan semua file non-biner, ubah &&dengan ||.

kenorb
sumber
1
Pertanyaan yang diajukan untuk mengidentifikasi file yang hanya berisi karakter nul (mengabaikan baris baru), kode Python yang diberikan di sini mengidentifikasi file yang mengandung karakter apa saja .
Tyson