Gibt es eine Möglichkeit, Kopfzeilen in einer UNIX-Sortierung zu ignorieren?

102

Ich habe eine Felddatei mit fester Breite, die ich mit dem UNIX-Sortierdienstprogramm (in meinem Fall Cygwin) sortieren möchte.

Das Problem ist, dass sich am oberen Rand der Datei ein zweizeiliger Header befindet, der am unteren Rand der Datei sortiert wird (da jede Headerzeile mit einem Doppelpunkt beginnt).

Gibt es eine Möglichkeit, die Sortierung anzugeben, entweder "die ersten beiden Zeilen über unsortiert zu übergeben" oder eine Reihenfolge anzugeben, in der die Doppelpunktzeilen nach oben sortiert werden - die verbleibenden Zeilen beginnen immer mit einer 6-stelligen Ziffer (was eigentlich der Schlüssel I ist) sortiere weiter) wenn das hilft.

Beispiel:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
500123TSTMY_RADAR00
222334NOTALINEOUT01
477821USASHUTTLES21
325611LVEANOTHERS00

sollte sortieren nach:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
222334NOTALINEOUT01
325611LVEANOTHERS00
477821USASHUTTLES21
500123TSTMY_RADAR00
Rob Gilliam
quelle
Für den Datensatz: Die Befehlszeile, die ich bisher verwende, lautet "sort -t \\ -k1.1,1.6 <Datei>" [die Daten können Leerzeichen enthalten, enthalten jedoch niemals einen Backslash]
Rob Gilliam

Antworten:

124
(head -n 2 <file> && tail -n +3 <file> | sort) > newfile

Die Klammern erstellen eine Unterschale, die das Standardout so umschließt, dass Sie es weiterleiten oder umleiten können, als ob es von einem einzelnen Befehl stammt.

BobS
quelle
Vielen Dank; Ich akzeptiere diese Antwort, da sie am vollständigsten und prägnantesten erscheint (und ich verstehe, was sie tut!) - sie sollte jedoch "head -n 2" sein :-)
Rob Gilliam
1
Danke, habe den 'Kopf'-Teil behoben.
BobS
4
Gibt es eine Möglichkeit, diese Version für eingespeiste Daten zu verwenden? Ich habe es mit versucht tee >(head -n $header_size) | tail -n +$header_size | sort, aber der Kopf scheint hinter der tail|sortPipe herzulaufen, sodass der Header am Ende gedruckt wird. Ist das deterministisch oder eine Rassenbedingung?
Damien Pollet
Sie könnten wahrscheinlich etwas zusammensetzen, mit dem Sie catdas stdin in eine temporäre Datei umleiten und dann den obigen Befehl für diese neue Datei ausführen, aber es wird langsam hässlich genug, dass es wahrscheinlich besser ist, eine der in awk basierten Lösungen zu verwenden die anderen Antworten.
BobS
@DamienPollet: Siehe Dave ‚s Antwort .
Jonathan Leffler
63

Wenn es Ihnen nichts ausmacht awk, können Sie die awkintegrierten Pipe-Fähigkeiten nutzen

z.B.

extract_data | awk 'NR<3{print $0;next}{print $0| "sort -r"}' 

Dies druckt die ersten beiden Zeilen wörtlich und leitet den Rest durch sort.

Beachten Sie, dass dies den ganz besonderen Vorteil hat, dass Teile eines Piping-Eingangs selektiv sortiert werden können. Alle anderen vorgeschlagenen Methoden sortieren nur einfache Dateien, die mehrfach gelesen werden können. Das funktioniert bei allem.

Dave
quelle
2
Sehr schön, und es funktioniert mit beliebigen Pipes, nicht nur Dateien!
Lapo
4
Schön, awk hört nie auf, mich zu überraschen. Auch das brauchst du nicht $0, printist genug.
Nachocab
1
Die Antwort von @SamWatkins freeseek ist weniger hässlich.
Fess.
Was macht die Option -r zum Sortieren? Soll das umgekehrt sein?
Gvrocha
32

Hier ist eine Version, die mit weitergeleiteten Daten arbeitet:

(read -r; printf "%s\n" "$REPLY"; sort)

Wenn Ihre Kopfzeile mehrere Zeilen enthält:

(for i in $(seq $HEADER_ROWS); do read -r; printf "%s\n" "$REPLY"; done; sort)

Diese Lösung ist von hier

freeseek
quelle
9
nett. Für den Fall mit einem einzelnen Header ist extract_data | (read h; echo "$h"; sort) er kurz genug, um sich zu erinnern. Ihr Beispiel deckt mehr Randfälle ab. :) Das ist die beste Antwort. arbeitet an Rohren. nein awk.
Fess.
1
Ok, ich habe das gespannt und es scheint, dass Bash besondere Anstrengungen unternimmt, um diese Arbeit zu machen. Wenn Sie dies in C oder einer anderen Sprache codieren, funktioniert dies im Allgemeinen nicht, da stdio mehr als nur die erste Kopfzeile liest. Wenn Sie es in einer durchsuchbaren Datei ausführen, liest bash einen größeren Block (128 Bytes in meinem Test) und sucht dann nach dem Ende der ersten Zeile zurück. Wenn Sie es auf einer Pipe ausführen, liest bash jeweils ein Zeichen, bis es das Ende der Zeile passiert.
Sam Watkins
Nett! Wenn Sie nur den Kopfball essen möchten, ist es noch einfacher, sich zu erinnern:extract_data | (read; sort)
Jason Suárez
Dieser ist fast perfekt, aber Sie müssen "IFS = read" anstelle von "read" verwenden, um führende und nachfolgende Leerzeichen beizubehalten.
Stanislav German-Evtushenko
6
Dies sollte meiner Meinung nach die akzeptierte Antwort sein. Einfach, präzise und flexibler, da es auch mit weitergeleiteten Daten funktioniert.
Paul I.
12

In einfachen Fällen sedkann die Arbeit elegant erledigt werden:

    your_script | (sed -u 1q; sort)

oder gleichwertig,

    cat your_data | (sed -u 1q; sort)

Der Schlüssel befindet sich in der 1qersten Zeile (Kopfzeile) und wird beendet (der Rest der Eingabe bleibt erhalten sort).

Für das gegebene Beispiel 2qwird der Trick tun.

Der -uSchalter (ungepuffert) ist für diejenigen seds (insbesondere GNUs ) erforderlich , die ansonsten die Eingabe in Blöcken lesen würden, wodurch Daten verbraucht würden, die Sie sortstattdessen durchlaufen möchten .

Andrea
quelle
1
Hallo, @Andrea; Willkommen bei Stack Overflow. Ich fürchte, Ihre Antwort funktioniert nicht, zumindest nicht, wenn ich sie in Git Bash unter Windows teste (ich bin von Cygwin, der Shell, die ich vor 6 Jahren verwendet habe, weitergegangen). Der Befehl sed zieht alle Daten aus dem Standard, sodass keine Daten zum Sortieren übergeben werden müssen. Versuchen Sie, den Befehl in cat your_data | zu ändern (sed 1q; wc -l) um zu sehen was ich meine.
Rob Gilliam
1
Dies könnte funktionieren, wenn Sie die Eingabe ein zweites Mal an den Befehl sed übergeben, wie folgt: cat sortMe.csv | (sed 1q sortMe.csv; sort -t, -k3 -rn)> sortiert.csv
Harry Cramer
8

Sie können verwenden tail -n +3 <file> | sort ...(tail gibt den Dateiinhalt aus der 3. Zeile aus).

Anton Kovalenko
quelle
4
head -2 <your_file> && nawk 'NR>2' <your_file> | sort

Beispiel:

> cat temp
10
8
1
2
3
4
5
> head -2 temp && nawk 'NR>2' temp | sort -r
10
8
5
4
3
2
1
Vijay
quelle
3

Es werden nur 2 Codezeilen benötigt ...

head -1 test.txt > a.tmp; 
tail -n+2 test.txt | sort -n >> a.tmp;

Für numerische Daten ist -n erforderlich. Für die Alpha-Sortierung ist -n nicht erforderlich.

Beispieldatei:
$ cat test.txt

Header
8
5
100
1
-1

Ergebnis:
$ cat a.tmp

Header
-1
1
5
8
100

Ian Sherbin
quelle
1
Ist das nicht im Grunde die gleiche Antwort wie die akzeptierte Antwort? (Außer bei BobS 'Ansatz wird das Ergebnis auf stdout gesetzt, sodass Sie das Ergebnis gegebenenfalls durch andere Filter senden können, bevor es in die Datei geschrieben wird.)
Rob Gilliam
1

Hier ist also eine Bash-Funktion, bei der Argumente genau wie Sortieren sind. Unterstützende Dateien und Pipes.

function skip_header_sort() {
    if [[ $# -gt 0 ]] && [[ -f ${@: -1} ]]; then
        local file=${@: -1}
        set -- "${@:1:$(($#-1))}"
    fi
    awk -vsargs="$*" 'NR<2{print; next}{print | "sort "sargs}' $file
}

Wie es funktioniert. Diese Zeile prüft, ob mindestens ein Argument vorhanden ist und ob das letzte Argument eine Datei ist.

    if [[ $# -gt 0 ]] && [[ -f ${@: -1} ]]; then

Dadurch wird die Datei in einem separaten Argument gespeichert. Da sind wir dabei, das letzte Argument zu löschen.

        local file=${@: -1}

Hier entfernen wir das letzte Argument. Da wollen wir es nicht als Sortierargument übergeben.

        set -- "${@:1:$(($#-1))}"

Schließlich machen wir den awk-Teil und übergeben die Argumente (abzüglich des letzten Arguments, wenn es sich um die Datei handelt), um in awk zu sortieren. Dies wurde ursprünglich von Dave vorgeschlagen und geändert, um Sortierargumente aufzunehmen. Wir verlassen uns auf die Tatsache, dass $filediese leer sind, wenn wir Rohrleitungen verwenden, und daher ignoriert werden.

    awk -vsargs="$*" 'NR<2{print; next}{print | "sort "sargs}' $file

Beispiel für die Verwendung mit einer durch Kommas getrennten Datei.

$ cat /tmp/test
A,B,C
0,1,2
1,2,0
2,0,1

# SORT NUMERICALLY SECOND COLUMN
$ skip_header_sort -t, -nk2 /tmp/test
A,B,C
2,0,1
0,1,2
1,2,0

# SORT REVERSE NUMERICALLY THIRD COLUMN
$ cat /tmp/test | skip_header_sort -t, -nrk3
A,B,C
0,1,2
2,0,1
1,2,0
Grippe
quelle
0

Mit Python:

import sys
HEADER_ROWS=2

for _ in range(HEADER_ROWS):
    sys.stdout.write(next(sys.stdin))
for row in sorted(sys.stdin):
    sys.stdout.write(row)
Kreuzfahrer
quelle
setzt voraus, dass auf dem System Python installiert ist (meins nicht)
Rob Gilliam
0

Hier ist eine Bash-Shell-Funktion, die aus den anderen Antworten abgeleitet wurde. Es behandelt sowohl Dateien als auch Pipes. Das erste Argument ist der Dateiname oder '-' für stdin. Verbleibende Argumente werden an sort übergeben. Ein paar Beispiele:

$ hsort myfile.txt
$ head -n 100 myfile.txt | hsort -
$ hsort myfile.txt -k 2,2 | head -n 20 | hsort - -r

Die Shell-Funktion:

hsort ()
{
   if [ "$1" == "-h" ]; then
       echo "Sort a file or standard input, treating the first line as a header.";
       echo "The first argument is the file or '-' for standard input. Additional";
       echo "arguments to sort follow the first argument, including other files.";
       echo "File syntax : $ hsort file [sort-options] [file...]";
       echo "STDIN syntax: $ hsort - [sort-options] [file...]";
       return 0;
   elif [ -f "$1" ]; then
       local file=$1;
       shift;
       (head -n 1 $file && tail -n +2 $file | sort $*);
   elif [ "$1" == "-" ]; then
       shift;
       (read -r; printf "%s\n" "$REPLY"; sort $*);
   else
       >&2 echo "Error. File not found: $1";
       >&2 echo "Use either 'hsort <file> [sort-options]' or 'hsort - [sort-options]'";
       return 1 ;
   fi
}
JonDeg
quelle
0

Dies ist die gleiche Antwort wie Ian Sherbin, aber meine Implementierung lautet:

cut -d'|' -f3,4,7 $arg1 | uniq > filetmp.tc
head -1 filetmp.tc > file.tc;
tail -n+2 filetmp.tc | sort -t"|" -k2,2 >> file.tc;
Bik
quelle
-4
cat file_name.txt | sed 1d | sort 

Dies wird tun, was Sie wollen.

Sathish G.
quelle
1) Dadurch wird nur die Kopfzeile entfernt und der Rest sortiert. Es wird nicht alles unterhalb der Kopfzeile sortiert, wobei die Kopfzeile intakt bleibt. 2) Es wird nur die erste Zeile entfernt, wenn der Header tatsächlich aus zwei Zeilen besteht (lesen Sie die Frage). 3) Warum verwenden Sie "cat file_name.txt | sed 1d", wenn "sed 1d <file_name.txt" oder auch nur "sed 1d file_name.txt" den gleichen Effekt hat?
Rob Gilliam