ฉันจะรันคำสั่ง 'find` นี้ได้อย่างไร แต่เฉพาะในไฟล์ที่ไม่ใช่ไบนารี


8

ฉันต้องการลบช่องว่างต่อท้ายออกจากไฟล์ทั้งหมดในลำดับชั้นไดเรกทอรีซ้ำ ฉันใช้สิ่งนี้:

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

วิธีนี้ใช้ได้ผล แต่จะลบ "ช่องว่าง" ต่อท้ายออกจากไฟล์ไบนารีที่พบซึ่งไม่พึงประสงค์

ฉันจะบอกfindให้หลีกเลี่ยงการรันคำสั่งนี้ในไฟล์ไบนารีได้อย่างไร?


ระบบไฟล์ Unix ไม่แยกความแตกต่างระหว่างไฟล์ "binary" และ "non-binary" ไม่มีวิธีบอกชนิดของข้อมูลในไฟล์โดยไม่ต้องดูจากภายใน
Wooble

@Wooble: ถูกต้อง แต่มีคำสั่งต่าง ๆ เช่นfileที่สามารถตรวจสอบข้อมูลได้
John Feminella

คำตอบ:


4

คุณสามารถลองใช้fileคำสั่งUnix เพื่อช่วยระบุไฟล์ที่คุณไม่ต้องการ แต่ฉันคิดว่ามันอาจจะดีกว่าถ้าคุณระบุไฟล์ที่คุณต้องการตีอย่างชัดเจนมากกว่าที่คุณไม่ต้องการ

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

เพื่อหลีกเลี่ยงการเข้าไปในไฟล์ควบคุมซอร์สที่คุณอาจต้องการ

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

คุณอาจหรืออาจไม่ต้องการแบ็กสแลชบางอย่างขึ้นอยู่กับเชลล์ของคุณ


2
ฉันไม่รู้เกี่ยวกับคุณ แต่ซอร์สไฟล์ Java ของเราทั้งหมดอยู่ในมาตรฐาน UTF-8 เสมอดังนั้นคำสั่งsedจะไม่ทำสิ่งที่ถูกต้องกับไฟล์เหล่านั้นเสมอไป ฉันยังมีระบบโดยไม่ต้องมี-iตัวเลือกในการsed มันยากที่จะเขียนคำสั่ง shell แบบพกพาใช่ไหม?
tchrist


3

คำตอบที่ง่ายที่สุดและพกพามากที่สุดคือเรียกใช้สิ่งนี้:

#!/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;

ฉันอธิบายว่าทำไมด้านล่างซึ่งฉันยังแสดงให้เห็นถึงวิธีการใช้เพียงบรรทัดคำสั่งเช่นเดียวกับวิธีจัดการกับไฟล์ข้อความ trans-ASCII เช่น ISO-8859-1 (ละติน -1) และ UTF-8 ซึ่งไม่ได้ผล -ASCII ช่องว่างในพวกเขา


ส่วนที่เหลือของเรื่อง

ปัญหาคือการหา (1) ไม่รองรับ-Tผู้ประกอบการทดสอบไฟล์และไม่รู้จักการเข้ารหัสถ้ามัน - ซึ่งคุณจำเป็นต้องตรวจสอบ UTF-8, การเข้ารหัส Unicode มาตรฐานโดยพฤตินัย

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

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

อย่างไรก็ตามตอนนี้คุณมีปัญหากับช่องว่างในชื่อไฟล์ของคุณดังนั้นคุณต้องมาช้ากว่านี้ด้วยการยกเลิกแบบ null:

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

สิ่งอื่นที่คุณสามารถทำได้คือไม่ใช้findแต่find2perlเนื่องจาก Perl เข้าใจ-Tแล้ว:

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

และถ้าคุณต้องการให้ Perl ถือว่าไฟล์อยู่ใน UTF-8 ให้ใช้

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

หรือคุณสามารถบันทึกสคริปต์ที่ได้ในไฟล์และแก้ไขมัน คุณไม่ควรเรียกใช้ไฟล์-Tทดสอบในไฟล์เก่า แต่ควรใช้เฉพาะไฟล์ที่เป็นไฟล์ธรรมดาตามที่พิจารณา-fก่อน มิฉะนั้นคุณจะเสี่ยงต่อการเปิดอุปกรณ์พิเศษปิดกั้นฟีด ฯลฯ

แต่ถ้าคุณจะทำทุกสิ่งที่คุณอาจรวมทั้งข้ามsed (1) โดยสิ้นเชิง สำหรับสิ่งหนึ่งมันพกพาได้มากกว่าเนื่องจากรุ่น POSIX ของsed (1) ไม่เข้าใจ-iในขณะที่ Perl ทุกรุ่นทำ รุ่นที่ผ่านมาของsed ได้รับการจัดสรรด้วยความรักเป็น-iตัวเลือกที่มีประโยชน์มากจาก Perl ซึ่งจะปรากฏขึ้นเป็นครั้งแรก

สิ่งนี้ยังให้โอกาสคุณในการแก้ไข regex ของคุณอีกด้วย คุณควรใช้รูปแบบที่ตรงกับช่องว่างแนวนอนหนึ่งช่องหรือมากกว่านั้นไม่ใช่แค่ศูนย์หรือคุณจะทำงานช้าลงจากการทำสำเนาที่ไม่จำเป็น นั่นคือสิ่งนี้:

 s/[ \t]*$//

ควรจะเป็น

 s/[ \t]+$//

อย่างไรก็ตามวิธีที่จะทำให้sed (1) เข้าใจว่าต้องใช้ส่วนขยายที่ไม่ใช่ POSIX โดยทั่วไป-Rสำหรับระบบ System Unices เช่น Solaris หรือ Linux หรือ-EBSD เช่น OpenBSD หรือ MacOS ฉันสงสัยว่ามันเป็นไปไม่ได้ภายใต้ AIX การเขียนเชลล์แบบพกพานั้นง่ายกว่าการใช้เชลล์สคริปต์แบบพกพา

เตือนเมื่อ 0xA0

แม้ว่าจะเป็นอักขระช่องว่างแนวนอนเท่านั้นใน ASCII ทั้ง ISO-8859-1 และดังนั้น Unicode จึงมี NO-BREAK SPACE ที่จุดโค้ด U + 00A0 นี่เป็นหนึ่งในสองอักขระที่ไม่ใช่ ASCII อันดับแรกที่พบใน Unicode corpora หลายแห่งและฉันได้เห็นการแตกรหัส regex ของผู้คนจำนวนมากเมื่อเร็ว ๆ นี้เพราะพวกเขาลืมมันไป

ดังนั้นทำไมคุณไม่ทำเช่นนี้:

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

ถ้าคุณอาจมี UTF-8 ไฟล์ที่จะจัดการกับการเพิ่ม-CSDและถ้าคุณกำลังเรียกใช้ Perl V5.10 หรือสูงกว่าคุณสามารถใช้\hสำหรับช่องว่างในแนวนอนและ\Rสำหรับ LINEBREAK ทั่วไปซึ่งรวมถึง\r, \n, \r\n, \f, \cK, \x{2028}และ\x{2029}:

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

ที่จะทำงานกับไฟล์ UTF-8 ทั้งหมดไม่ว่าจะมีการแพร่กระจายของพวกมันกำจัดช่องว่างในแนวนอน (คุณสมบัติอักขระ Unicode HorizSpace) รวมถึงช่องว่างNO-BREAK ที่น่ารำคาญที่เกิดขึ้นก่อน Unicode Linebreak (รวมคอมโบ CRLF) ในตอนท้ายของแต่ละบรรทัด

นอกจากนี้ยังพกพาได้มากกว่ารุ่นsed (1) มากเนื่องจากมีการติดตั้งPerlเพียงครั้งเดียว(1) แต่มีsed (1) จำนวนมาก

ปัญหาหลักที่ฉันเห็นมีอยู่คือfind (1) เนื่องจากในระบบ recalcitrant บางระบบ (คุณรู้ว่าคุณคือใคร AIX และ Solaris) มันจะไม่เข้าใจ-print0คำสั่งsupercritical หากนั่นคือสถานการณ์ของคุณคุณควรใช้File::Findโมดูลจาก Perl โดยตรงและไม่ต้องใช้ยูทิลิตี้ Unix อื่น ๆ นี่เป็นโค้ด Perl ของคุณบริสุทธิ์ที่ไม่ต้องพึ่งพาสิ่งอื่นใด:

#!/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;

หากคุณใช้ไฟล์ข้อความเพียง ASCII หรือ ISO-8859-1 ก็ถือว่าใช้ได้ แต่ถ้าคุณใช้ไฟล์ ASCII หรือ UTF-8 ให้เพิ่ม-CSDสวิตช์ในการโทรภายในไปยัง Perl

หากคุณมีการเข้ารหัสแบบผสมทั้งสามของ ASCII, ISO-8859-1 และ UTF-8 ฉันกลัวว่าคุณจะมีปัญหาอื่น :( คุณจะต้องคิดการเข้ารหัสแบบต่อไฟล์และไม่มีวิธีที่ดีที่จะเดาได้

ช่องว่าง Unicode

สำหรับเร็กคอร์ด Unicode มีอักขระช่องว่าง 26 ตัวที่แตกต่างกัน คุณสามารถใช้unicharsยูทิลิตี้เพื่อสูดอากาศออกเหล่านี้ เฉพาะช่องว่างแนวนอนสามช่องแรกเท่านั้นที่จะเห็น:

$ 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

0

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

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

ถ้าคุณอยู่ใน Solaris คุณต้องการแทนที่ด้วยgrep/opt/csw/bin/ggrep

grepธงทำต่อไปนี้: lชื่อไฟล์รายการเท่านั้นสำหรับไฟล์ที่ตรงกันRคือเวียนเกิดIตรงกับไฟล์ข้อความเท่านั้น (ละเว้นไฟล์ไบนารี) และPสำหรับไวยากรณ์นิพจน์ Perl เข้ากันได้ปกติ

ส่วนที่ perl แก้ไขไฟล์ในสถานที่การลบช่องว่าง / แท็บต่อท้ายทั้งหมด

สุดท้าย: หาก UTF8 เป็นปัญหาคำตอบของ tchrist และของฉันก็น่าจะเพียงพอหากงาน build ของgrepคุณถูกสร้างขึ้นด้วยการสนับสนุน UTF8 (โดยปกติผู้ดูแลแพคเกจจะพยายามใช้ฟังก์ชันประเภทนั้น)

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