Wie führe ich diesen Befehl `find` aus, aber nur für nicht-binäre Dateien?

8

Ich möchte abschließende Leerzeichen aus allen Dateien in einer rekursiven Verzeichnishierarchie entfernen. Ich benutze das:

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

Dies funktioniert, entfernt jedoch auch abschließende "Leerzeichen" aus den gefundenen Binärdateien, was unerwünscht ist.

Wie findvermeide ich, diesen Befehl für Binärdateien auszuführen?

John Feminella
quelle
Unix-Dateisysteme unterscheiden nicht zwischen "binären" und "nicht-binären" Dateien. Es gibt keine Möglichkeit festzustellen, welche Art von Daten in der Datei enthalten ist, ohne sie zu durchsuchen.
Wooble
@Wooble: Das ist richtig, aber es gibt Befehle, mit filedenen die Daten überprüft werden können.
John Feminella

Antworten:

4

Sie könnten versuchen, den Unix- fileBefehl zu verwenden, um die Dateien zu identifizieren, die Sie nicht möchten, aber ich denke, es ist besser, wenn Sie explizit angeben, welche Dateien Sie treffen möchten, als diejenigen, die Sie nicht möchten.

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

um zu vermeiden, dass Sie in Quellcodeverwaltungsdateien gelangen, möchten Sie vielleicht etwas Ähnliches

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

Abhängig von Ihrer Shell benötigen Sie möglicherweise einige der Backslashes oder auch nicht.

Bert F
quelle
2
Ich kenne Sie nicht, aber alle unsere Java-Quelldateien sind immer in Standard-UTF-8 gespeichert, sodass der Befehl sed mit all diesen nicht immer das Richtige tut. Ich habe auch Systeme ohne Sed--i Option . Es ist schwer, einen portablen Shell-Befehl zu schreiben, oder?
Donnerstag,
4

Dies kann über die Befehlszeile erfolgen.

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

Die einfachste und portabelste Antwort lautet:

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

Ich erkläre unten, warum, wo ich auch zeige, wie man es nur mit der Befehlszeile macht, und wie man mit trans-ASCII-Textdateien wie ISO-8859-1 (Latin-1) und UTF-8 umgeht, die oft keine haben -ASCII Leerzeichen in ihnen.


Der Rest der Geschichte

Das Problem ist, dass find (1) weder den -TDateitest-Operator unterstützt, noch Codierungen erkennt, wenn dies der Fall ist - was unbedingt erforderlich ist, um die UTF-8-Standard-Unicode-Codierung zu erkennen.

Sie könnten die Dateinamenliste durch eine Ebene führen, die Binärdateien ausgibt. Zum Beispiel

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

Jetzt haben Sie jedoch Probleme mit Leerzeichen in Ihren Dateinamen. Daher müssen Sie dies mit Nullterminierung verspäten:

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

Eine andere Sache, die Sie tun könnten, ist nicht zu verwenden, findaber find2perl, da Perl -Tbereits versteht :

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

Und wenn Sie möchten, dass Perl davon ausgeht, dass sich die Dateien in UTF-8 befinden, verwenden Sie

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

Sie können das resultierende Skript auch in einer Datei speichern und bearbeiten. Sie sollten wirklich wirklich nicht nur den -TFiletest auf einer alten Datei ausführen, sondern nur auf Dateien, die reine Dateien sind, wie zuerst von bestimmt -f. Andernfalls riskieren Sie das Öffnen von Gerätespecials, das Blockieren von Fifos usw.

Wenn Sie dies jedoch alles tun, können Sie sed (1) genauso gut überspringen . Zum einen ist es portabler, da die POSIX-Version von sed (1) dies nicht versteht -i, wohingegen alle Versionen von Perl dies tun. Die neueren Versionen von sed haben die sehr nützliche -iOption von Perl liebevoll übernommen, bei der ti zum ersten Mal auftaucht.

Dies gibt Ihnen auch die Möglichkeit, Ihren regulären Ausdruck zu korrigieren. Sie sollten wirklich ein Muster verwenden, das mit einem oder mehreren horizontalen Leerzeichen übereinstimmt, und nicht mit Null, da sonst unnötige Kopiervorgänge langsamer ablaufen. Dies ist das:

 s/[ \t]*$//

sollte sein

 s/[ \t]+$//

Um sed (1) zu verstehen, ist jedoch eine Nicht-POSIX-Erweiterung erforderlich, normalerweise entweder -Rfür System Ⅴ Unices wie Solaris oder Linux oder -Efür BSD wie OpenBSD oder MacOS. Ich vermute, dass es unter AIX unmöglich ist. Es ist leider einfacher, eine tragbare Shell zu schreiben als ein Skript für eine tragbare Shell, wissen Sie.

Warnung an 0xA0

Obwohl dies die einzigen horizontalen Leerzeichen in ASCII sind, haben sowohl ISO-8859-1 als auch Unicode das Leerzeichen NO-BREAK am Codepunkt U + 00A0. Dies ist eines der beiden wichtigsten Nicht-ASCII-Zeichen, die in vielen Unicode-Corpora vorkommen, und ich habe in letzter Zeit viele Leute gesehen, die den regulären Code gebrochen haben, weil sie ihn vergessen haben.

Warum machst du das nicht einfach so:

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

Wenn Sie könnten UTF-8 - Dateien zu behandeln, hinzufügen -CSD, und wenn Sie Perl v5.10 oder höher ausgeführt wird , können Sie \hfür die horizontalen Leerzeichen und \Rfür einen generischen Zeilenumbrüche, die beinhalten \r, \n, \r\n, \f, \cK, \x{2028}, und \x{2029}:

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

Dies funktioniert für alle UTF-8-Dateien, unabhängig von den Zeilenumbrüchen, und beseitigt nachfolgende horizontale Leerzeichen (Unicode-Zeicheneigenschaft HorizSpace), einschließlich des lästigen Leerzeichens NO-BREAK, das vor einem Unicode-Zeilenumbruch (einschließlich CRLF-Combos) am Ende jeder Zeile auftritt.

Es ist auch viel portabler als die sed (1) -Version, da es nur eine Perl (1) -Implementierung gibt, aber viele sed (1).

Das Hauptproblem, von dem ich sehe, dass es dort verbleibt, ist find (1), da es auf einigen wirklich widersprüchlichen Systemen (Sie wissen, wer Sie sind, AIX und Solaris) die überkritische -print0Direktive nicht versteht . Wenn das Ihre Situation ist, sollten Sie einfach das File::FindModul von Perl direkt verwenden und keine anderen Unix-Dienstprogramme verwenden. Hier ist eine reine Perl-Version Ihres Codes, die sich auf nichts anderes stützt:

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

Wenn Sie nur ASCII- oder ISO-8859-1-Textdateien verwenden, ist dies in Ordnung. Wenn Sie jedoch ASCII- oder UTF-8-Dateien verwenden, fügen Sie die Optionen im internen -CSDAufruf von Perl hinzu.

Wenn Sie alle drei Codierungen von ASCII, ISO-8859-1 und UTF-8 gemischt haben, dann befürchte ich, dass Sie ein anderes Problem haben. :( Sie müssen die Kodierung auf Dateibasis herausfinden, und es gibt nie einen guten Weg, das zu erraten.

Unicode-Leerzeichen

Für den Datensatz hat Unicode 26 verschiedene Whitespace-Zeichen. Sie können das Dienstprogramm unichars verwenden, um diese auszuspionieren . Es werden fast immer nur die ersten drei horizontalen Whitespace-Zeichen angezeigt:

$ 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
quelle
0

GNU grep kann ziemlich gut erkennen, ob eine Datei binär ist oder nicht. Außer Solaris gibt es sicher noch andere Plattformen, auf denen GNU grep nicht standardmäßig installiert ist, aber wie bei Solaris können Sie es sicher installieren.

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

Wenn Sie in Solaris sind, dann würden Sie ersetzen grepmit /opt/csw/bin/ggrep.

Die grepFlags führen Folgendes aus: lListet nur Dateinamen für übereinstimmende Dateien auf, Rist rekursiv, Istimmt nur mit Textdateien überein (ignoriert Binärdateien) und Pist für die Syntax von Perl-kompatiblen regulären Ausdrücken vorgesehen.

Der Perl-Teil ändert die Datei direkt und löscht alle nachfolgenden Leerzeichen / Tabulatoren.

Zum Schluss: Wenn UTF8 ein Problem ist, sollte die Antwort von tchrist in Verbindung mit meiner ausreichend sein, vorausgesetzt, der von grepIhnen erstellte Build wurde mit UTF8-Unterstützung erstellt (normalerweise versuchen Paketbetreuer jedoch, diese Art von Funktionalität bereitzustellen).

Brian Vandenberg
quelle