it-swarm.com.de

Entfernen von schleppenden/beginnenden Zeilenumbrüchen mit sed, awk, tr und friends

Ich möchte alle leeren Zeilen aus einer Datei entfernen, aber nur, wenn sie sich am Ende/Anfang einer Datei befinden (dh, wenn keine leeren Zeilen vor ihnen am Anfang stehen) und falls vorhanden keine leeren Zeilen nach ihnen am Ende.)

Ist dies außerhalb einer voll ausgestatteten Skriptsprache wie Perl oder Ruby möglich? Ich ziehe es vor, wenn möglich mit sed oder awk zu tun. Grundsätzlich wäre jedes leichtgewichtige und allgemein verfügbare UNIX-y-Tool in Ordnung, vor allem eines, über das ich schnell mehr erfahren kann (Perl, also nicht enthalten).

31
ELLIOTTCABLE

VonNützliche einzeilige Skripte für sed:

# Delete all leading blank lines at top of file (only).
sed '/./,$!d' file

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

Um sowohl führende als auch nachfolgende Leerzeilen aus einer Datei zu entfernen, können Sie die obigen Befehle folgendermaßen kombinieren:

sed -e :a -e '/./,$!d;/^\n*$/{$d;N;};/\n$/ba' file
44
dogbane

Also werde ich mir einen Teil der Antwort von @ dogbane dafür ausleihen, da diese sed-Zeile zum Entfernen der führenden Leerzeilen so kurz ist ...

tac ist Teil von coreutils und kehrt eine Datei um. Also mach es zweimal:

tac file | sed -e '/./,$!d' | tac | sed -e '/./,$!d'

Es ist sicherlich nicht das effizienteste, aber wenn Sie brauchen Effizienz, finde ich es lesbarer als alles andere bis jetzt.

10
Izkata

hier ist eine One-Pass-Lösung in awk: Der Druckvorgang beginnt erst, wenn eine nicht leere Zeile angezeigt wird. Wenn eine leere Zeile angezeigt wird, merkt es sich dies bis zur nächsten nicht leeren Zeile

awk '
    /[[:graph:]]/ {
        # a non-empty line
        # set the flag to begin printing lines
        p=1      
        # print the accumulated "interior" empty lines 
        for (i=1; i<=n; i++) print ""
        n=0
        # then print this line
        print
    }
    p && /^[[:space:]]*$/ {
        # a potentially "interior" empty line. remember it.
        n++
    }
' filename

Beachten Sie, dass aufgrund des Mechanismus, den ich verwende, um leere/nicht leere Zeilen (mit [[:graph:]] und /^[[:space:]]*$/) zu berücksichtigen, innere Zeilen mit nur Leerzeichen abgeschnitten werden, um wirklich leer zu werden.

3
glenn jackman

awk verwenden:

awk '{a[NR]=$0;if($0 && !s)s=NR;}
    END{e=NR;
        for(i=NR;i>1;i--) 
            if(a[i]){ e=i; break; } 
        for(i=s;i<=e;i++)
            print a[i];}' yourFile
2
Kent

Wie in erwähnt, ist eine andere Antwort , tac Teil von coreutils und kehrt eine Datei um. Kombinieren Sie die Idee, es zweimal zu machen, mit die Tatsache, dass die Befehlssubstitution neue Zeilen abreißt , erhalten wir

echo "$(echo "$(tac "$filename")" | tac)"

was nicht von sed abhängt. Sie können echo -n verwenden, um die verbleibende nachfolgende Zeile abzulösen.

2
Jason Gross

Hier ist eine angepasste sed-Version, die auch "leere" Zeilen mit Leerzeichen und Registerkarten berücksichtigt.

sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}'

Es ist im Grunde die akzeptierte Antwortversion (unter Berücksichtigung des BryanH-Kommentars), aber der Punkt . im ersten Befehl wurde in [^[:blank:]] (alles nicht leer) geändert und der \n in der zweiten Befehlsadresse wurde in [[:space:]] geändert, um Zeilenumbrüche und Tabulatoren zuzulassen.

Eine alternative Version, ohne die POSIX-Klassen zu verwenden, aber Ihr Sed muss das Einfügen von \t und \n in […] unterstützen. GNU sed tut, BSD sed nicht.

sed -e :a -e '/[^\t ]/,$!d; /^[\n\t ]*$/{ $d; N; ba' -e '}'

Testen:

Prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' 



foo

foo



Prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -n l
$
 \t $
$
foo$
$
foo$
$
 \t $
$
Prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}'
foo

foo
Prompt$
2
Aurelio Jargas

Für eine effiziente, nicht rekursive Version des nachgestellten Newline-Strips (einschließlich "weißer" Zeichen) habe ich dieses sed-Skript entwickelt.

sed -n '/^[[:space:]]*$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H'

Es verwendet den Haltepuffer, um alle leeren Zeilen zu speichern, und druckt sie nur, nachdem eine nicht leere Zeile gefunden wurde. Sollte jemand nur die Zeilenumbrüche wünschen, genügt es, die beiden [[:space:]]*-Teile loszuwerden:

sed -n '/^$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^$/H'

Ich habe einen einfachen Leistungsvergleich mit dem bekannten rekursiven Skript versucht

sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba'

in einer 3 MB-Datei mit 1 MB zufälligen leeren Zeilen um einen zufälligen base64-Text.

shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M > bigfile
base64 </dev/urandom | dd bs=1 count=1M >> bigfile
shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M >> bigfile

Das Streaming-Skript dauerte ungefähr 0,5 Sekunden, das Rekursiv endete nicht nach 15 Minuten. Sieg :)

Der Vollständigkeit halber ist die Antwort zu beachten, dass das führende Skript für das Strippen des Sed-Skripts bereits einwandfrei funktioniert. Verwenden Sie das am besten geeignete für Sie.

sed '/[^[:blank:]]/,$!d'
sed '/./,$!d'
1
tlwhitec

In Bash mit cat, wc, grep, sed, tail und head:

# number of first line that contains non-empty character
i=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | head -1`
# number of hte last one
j=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | tail -1`
# overall number of lines:
k=`cat <your_file> | wc -l`
# how much empty lines at the end of file we have?
m=$(($k-$j))
# let strip last m lines!
cat <your_file> | head -n-$m
# now we have to strip first i lines and we are done 8-)
cat <your_file> | tail -n+$i

Mann, es lohnt sich auf jeden Fall, "echte" Programmiersprache zu lernen, um diese Hässlichkeit zu vermeiden!

1

bash verwenden

$ filecontent=$(<file)
$ echo "${filecontent/$'\n'}"
1
bash-o-logist

Ich möchte eine andere Variante für gawk v4.1 + vorstellen

result=($(gawk '
    BEGIN {
        lines_count         = 0;
        empty_lines_in_head = 0;
        empty_lines_in_tail = 0;
    }
    /[^[:space:]]/ {
        found_not_empty_line = 1;
        empty_lines_in_tail  = 0;
    }
    /^[[:space:]]*?$/ {
        if ( found_not_empty_line ) {
            empty_lines_in_tail ++;
        } else {
            empty_lines_in_head ++;
        }
    }
    {
        lines_count ++;
    }
    END {
        print (empty_lines_in_head " " empty_lines_in_tail " " lines_count);
    }
' "$file"))

empty_lines_in_head=${result[0]}
empty_lines_in_tail=${result[1]}
lines_count=${result[2]}

if [ $empty_lines_in_head -gt 0 ] || [ $empty_lines_in_tail -gt 0 ]; then
    echo "Removing whitespace from \"$file\""
    eval "gawk -i inplace '
        {
            if ( NR > $empty_lines_in_head && NR <= $(($lines_count - $empty_lines_in_tail)) ) {
                print
            }
        }
    ' \"$file\""
fi
0
puchu
Perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
0
Jan Kyu Peblik

Dieses AWK-Skript wird den Trick ausführen:

BEGIN {
    ne=0;
}

/^[[:space:]]*$/ {
    ne++;
}

/[^[:space:]]+/ {
    for(i=0; i < ne; i++)
        print "";
    ne=0;
    print
}

Die Idee ist einfach: Leere Zeilen werden nicht sofort wiedergegeben. Stattdessen warten wir, bis wir eine nicht leere Zeile erhalten, und erst dann wiederholen wir so viele leere Zeilen wie zuvor, und erst dann die neue nicht leere Zeile.

0
Adi Degani

Abash Lösung .

Hinweis: Nur nützlich wenn die Datei klein genug ist , um sofort in den Speicher eingelesen zu werden.

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file) liest die gesamte Datei und schneidet nachlaufende newlines ab, da die Befehlsersetzung ($(....)) implizit dies tut.
  • =~ ist der _ {Regular-Expression-Matching-Operatorvon bash, und =~ ^$'\n'*(.*)$ entspricht optional allen führenden -Nachrichten (gierig) und erfasst, was danach kommt. Beachten Sie das möglicherweise verwirrende $'\n', durch das eine wörtliche Zeilenschaltung mit ANSI C quoting eingefügt wird, da die Escape-Sequenz \n nicht unterstützt wird.
  • Beachten Sie, dass dieser Regex immer übereinstimmt, so dass der Befehl nach &&immer ausgeführt wird.
  • Die spezielle Array-Variable BASH_REMATCH rematch enthält die Ergebnisse der neuesten Regex-Übereinstimmung. Das Array-Element [1] enthält den (ersten und einzigen) eingeklammerten Unterausdruck (Erfassungsgruppe), der die Eingabezeichenfolge enthält, wobei alle führenden Zeilenumbrüche entfernt werden. Der Nettoeffekt ist, dass ${BASH_REMATCH[1]} den Inhalt der Eingabedatei enthält, wobei sowohl führende als auch nachfolgende Zeilenumbrüche entfernt werden.
  • Beachten Sie, dass beim Drucken mit echo eine einzelne abschließende Zeile hinzugefügt wird. Wenn Sie dies vermeiden möchten, verwenden Sie stattdessen echo -n (oder verwenden Sie den portablen printf '%s').
0
mklement0

@dogbane hat eine einfache Antwort, um führende Leerzeilen zu entfernen. Hier ist ein einfacher awk-Befehl, der nur die nachfolgenden Zeilen entfernt. Verwenden Sie diesen Befehl zusammen mit dem Befehl @ dogbane, um führende und nachgestellte Leerzeichen zu entfernen.

awk '{ LINES=LINES $0 "\n"; } /./ { printf "%s", LINES; LINES=""; }'

Dies ist im Betrieb ziemlich einfach. 

  • Fügen Sie jede Zeile zu einem Puffer hinzu, während wir sie lesen. 
  • Drucken Sie für jede Zeile, die ein Zeichen enthält, den Inhalt des Puffers und löschen Sie ihn.

Die einzigen Dinge, die gepuffert werden und niemals angezeigt werden, sind nachfolgende Leerzeichen.

Ich habe printf anstelle von print verwendet, um das automatische Hinzufügen einer neuen Zeile zu vermeiden, da ich bereits Zeilen zwischen den Zeilen im Puffer trenne.

0
Andy Mortimer