Bagaimana saya menjalankan perintah `find` ini, tetapi hanya pada file non-biner?

8

Saya ingin menghapus spasi kosong dari semua file dalam hierarki direktori rekursif. Saya menggunakan ini:

find * -type f -exec sed 's/[ \t]*$//' -i {} \;

Ini berfungsi, tetapi juga akan menghapus jejak "spasi putih" dari file biner yang ditemukan, yang tidak diinginkan.

Bagaimana saya tahu finduntuk menghindari menjalankan perintah ini pada file biner?

John Feminella
sumber
Sistem file Unix tidak membedakan antara file "biner" dan "non-biner"; tidak ada cara untuk mengetahui tipe data apa yang ada di file tanpa melihat ke dalamnya.
Wooble
@ Wooble: Itu benar, tetapi ada perintah seperti fileyang dapat memeriksa data.
John Feminella

Jawaban:

4

Anda dapat mencoba menggunakan fileperintah Unix untuk membantu mengidentifikasi file yang tidak Anda inginkan, tetapi saya pikir mungkin lebih baik jika Anda secara eksplisit menentukan file apa yang ingin Anda tekan daripada yang tidak Anda inginkan.

find * -type f \( -name \*.java -o -name \*.c -o -name \*.sql \) -exec sed 's/[ \t]*$//' -i {} \;

untuk menghindari melintasi ke file kontrol sumber Anda mungkin ingin sesuatu seperti

find * \! \( -name .svn -prune \) -type f \( -name \*.java -o -name \*.c -o -name \*.sql \) -exec sed 's/[ \t]*$//' -i {} \;

Anda mungkin perlu atau tidak membutuhkan beberapa garis miring terbalik tergantung pada cangkang Anda.

Bert F
sumber
2
Saya tidak tahu tentang Anda, tetapi semua file sumber Java kami selalu dalam standar UTF-8, sehingga perintah sed tidak akan selalu melakukan hal yang benar dengan semua itu. Saya juga punya sistem tanpa -iopsi untuk sed . Sulit untuk menulis perintah shell portabel, bukan?
tchrist
4

Itu bisa dilakukan di baris perintah.

$ find . -type f -print|xargs file|grep ASCII|cut -d: -f1|xargs sed 's/[ \t]*$//' -i
Vijay
sumber
3

Jawaban paling sederhana dan paling portabel adalah menjalankan ini:

#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
my @dirs = (@ARGV == 0) ? <*> : @ARGV;
find sub {
    next unless -f && -T;
    system('perl', '-i', '-pe', 's/[\t\xA0 ]+$//', $File::Find::name);
} => @dirs;

Saya menjelaskan mengapa di bawah ini, di mana saya juga menunjukkan bagaimana melakukannya hanya dengan menggunakan baris perintah, serta bagaimana menangani file teks trans-ASCII seperti ISO-8859-1 (Latin-1) dan UTF-8, yang sebelumnya tidak memiliki Ruang putih-ASCII di dalamnya.


Sisa dari Kisah

Masalahnya adalah bahwa find (1) tidak mendukung -Toperator filetest, juga tidak mengenali pengkodean jika itu - yang Anda benar-benar perlu mendeteksi UTF-8, pengkodean Unicode standar de facto standar.

Yang bisa Anda lakukan adalah menjalankan daftar nama file melalui lapisan yang membuang file biner. Sebagai contoh

$ find . -type f | perl -nle 'print if -T' | xargs sed -i 's/[ \t]*$//'

Namun sekarang Anda memiliki masalah dengan spasi putih di nama file Anda, jadi Anda harus mengakhiri ini dengan penghentian nol:

$ find . -type f -print0 | perl -0 -nle 'print if -T' | xargs -0 sed -i 's/[ \t]*$//'

Hal lain yang bisa Anda lakukan adalah menggunakan findtetapi find2perl, karena Perl -Tsudah mengerti :

$ find2perl * -type T -exec sed 's/[ \t]*$//' -i {} \; | perl

Dan jika Anda ingin Perl menganggap file-nya berada di UTF-8, gunakan

$ find2perl * -type T -exec sed 's/[ \t]*$//' -i {} \; | perl -CSD

Atau Anda dapat menyimpan skrip yang dihasilkan dalam file dan mengeditnya. Anda benar-benar benar-benar tidak boleh hanya menjalankan -Tfiletest pada file lama apa pun, tetapi hanya pada mereka yang file biasa seperti yang pertama kali ditentukan oleh -f. Kalau tidak, Anda berisiko membuka spesial perangkat, memblokir fifos, dll.

Namun, jika Anda akan melakukan semua itu, Anda mungkin melewatkan sed (1) sama sekali. Untuk satu hal, ini lebih portabel, karena versi POSIX sed (1) tidak mengerti -i, sedangkan semua versi Perl lakukan. Versi terakhir dari sed dengan penuh kasih sayang menggunakan opsi yang sangat berguna -idari Perl tempat saya pertama kali muncul.

Ini juga memberi Anda kesempatan untuk memperbaiki regex Anda juga. Anda harus benar-benar menggunakan pola yang cocok dengan satu atau lebih spasi spasi horizontal, tidak hanya nol, atau Anda akan berjalan lebih lambat dari penyalinan yang tidak perlu. Yaitu, ini:

 s/[ \t]*$//

seharusnya

 s/[ \t]+$//

Namun, bagaimana untuk sed (1) untuk memahami yang membutuhkan ekstensi-POSIX non, biasanya baik -Runtuk Sistem Ⅴ beragam Unix seperti Solaris atau Linux, atau -Euntuk yang BSD seperti OpenBSD atau MacOS. Saya menduga itu tidak mungkin di bawah AIX. Sayangnya, lebih mudah menulis shell portabel daripada skrip shell portabel.

Peringatan pada 0xA0

Walaupun itu adalah satu-satunya karakter spasi putih horizontal di ASCII, ISO-8859-1 dan juga Unicode memiliki ruang NO-BREAK pada titik kode U + 00A0. Ini adalah salah satu dari dua karakter non-ASCII teratas yang ditemukan di banyak Unicode corpora, dan akhir-akhir ini saya melihat banyak kode regex orang rusak karena mereka lupa.

Jadi kenapa tidak Anda lakukan saja ini:

$ find * -print0 | perl -0 -nle 'print if -f && -T' | xargs -0 perl -i -pe 's/[\t\xA0 ]+$//'

Jika Anda mungkin memiliki UTF-8 file untuk menangani, add -CSD, dan jika Anda menjalankan Perl v5.10 atau lebih, Anda dapat menggunakan \huntuk spasi horizontal dan \Runtuk linebreak generik, yang meliputi \r, \n, \r\n, \f, \cK, \x{2028}, dan \x{2029}:

$ find * -print0 | perl -0 -nle 'print if -f && -T' | xargs -0 perl -CSD -i -pe 's/\h+(?=\R*$)//'

Itu akan bekerja pada semua file UTF-8 tidak peduli linebreak mereka, menghilangkan spasi spasi horizontal (properti karakter Unicode HorizSpace) termasuk spasi NO-BREAK SPEAK yang terjadi sebelum linebreak Unicode (termasuk CRLF combo) di akhir setiap baris.

Ini juga jauh lebih portabel daripada versi sed (1), karena hanya ada satu perl (1) implementasi, tetapi banyak sed (1).

Masalah utama yang saya lihat masih ada di sana adalah dengan find (1), karena pada beberapa sistem yang benar-benar bandel (Anda tahu siapa Anda, AIX dan Solaris), ia tidak akan memahami -print0arahan superkritis . Jika itu situasi Anda, maka Anda harus menggunakan File::Findmodul dari Perl secara langsung, dan tidak menggunakan utilitas Unix lainnya. Ini adalah versi Perl murni dari kode Anda yang tidak bergantung pada hal lain:

#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
my @dirs = (@ARGV == 0) ? <*> : @ARGV;
find sub {
     next unless -f && -T;
     system('perl', '-i', '-pe', 's/[\t\xA0 ]+$//', $File::Find::name);  
} => @dirs;

Jika Anda menjalankan hanya pada file teks ASCII atau ISO-8859-1, itu bagus, tetapi jika Anda menjalankan dengan file ASCII atau UTF-8, tambahkan -CSDke switch di panggilan interior ke Perl.

Jika Anda memiliki penyandian campuran dari ketiga ASCII, ISO-8859-1, dan UTF-8, maka saya khawatir Anda memiliki masalah lain. :( Anda harus mencari tahu penyandian berdasarkan per-file, dan tidak pernah ada cara yang baik untuk menebaknya.

Ruang Putih Unicode

Sebagai catatan, Unicode memiliki 26 karakter spasi yang berbeda. Anda dapat menggunakan yang unichars utilitas untuk mengendus keluar ini. Hanya tiga karakter spasi horisontal pertama yang hampir pernah terlihat:

$ unichars '\h'
 ---- U+0009 CHARACTER TABULATION
 ---- U+0020 SPACE
 ---- U+00A0 NO-BREAK SPACE
 ---- U+1680 OGHAM SPACE MARK
 ---- U+180E MONGOLIAN VOWEL SEPARATOR
 ---- U+2000 EN QUAD
 ---- U+2001 EM QUAD
 ---- U+2002 EN SPACE
 ---- U+2003 EM SPACE
 ---- U+2004 THREE-PER-EM SPACE
 ---- U+2005 FOUR-PER-EM SPACE
 ---- U+2006 SIX-PER-EM SPACE
 ---- U+2007 FIGURE SPACE
 ---- U+2008 PUNCTUATION SPACE
 ---- U+2009 THIN SPACE
 ---- U+200A HAIR SPACE
 ---- U+202F NARROW NO-BREAK SPACE
 ---- U+205F MEDIUM MATHEMATICAL SPACE
 ---- U+3000 IDEOGRAPHIC SPACE

$ unichars '\v'
 ---- U+000A LINE FEED (LF)
 ---- U+000B LINE TABULATION
 ---- U+000C FORM FEED (FF)
 ---- U+000D CARRIAGE RETURN (CR)
 ---- U+0085 NEXT LINE (NEL)
 ---- U+2028 LINE SEPARATOR
 ---- U+2029 PARAGRAPH SEPARATOR
tchrist
sumber
0

GNU grep cukup bagus dalam mengidentifikasi apakah suatu file biner atau tidak. Selain Solaris, saya yakin ada platform lain yang tidak datang dengan GNU grep terinstal secara default, tetapi seperti Solaris, saya yakin Anda dapat menginstalnya.

perl -pi -e 's{[ \t]+$}{}g' `grep -lRIP '[ \t]+$' .`

Jika Anda berada di Solaris, Anda akan menggantinya grepdengan /opt/csw/bin/ggrep.

The grepbendera melakukan hal berikut: lhanya daftar nama file untuk file yang cocok, Radalah rekursif, Icocok hanya file text (mengabaikan file biner), dan Pbagi sintaks ekspresi reguler perl-kompatibel.

Bagian perl memodifikasi file di tempat, menghapus semua spasi tambahan / tab.

Terakhir: jika UTF8 merupakan suatu masalah, jawaban tchrist yang digabungkan dengan milik saya harus mencukupi, asalkan build yang grepAnda miliki dibangun dengan dukungan UTF8 (biasanya pengelola paket mencoba menyediakan fungsionalitas semacam itu).

Brian Vandenberg
sumber