2013-09-04 2 views
5

Ich versuche, eine Reihe von Dateien mit dem Befehl ls numerisch zu sortieren, die entweder ABCDE1234A1789.RST.txt oder ABCDE12345A1789.RST.txt durch das Feld '789' entsprechen.Verwenden Sie den Befehl bash sort in Dateinamen mit variabler Länge

In den obigen Beispielmustern ist ABCDE für alle Dateien gleich, 1234 oder 12345 sind Ziffern, die variieren, aber immer 4 oder 5 Ziffern lang sind. A1 ist die gleiche Länge für alle Dateien, aber der Wert kann variieren, so dass es leider nicht als Trennzeichen verwendet werden kann. Alles nach dem ersten . ist für alle Dateien gleich. Etwas wie:

ls -l *.RST.txt | sort -k +9.13 | awk '{print $9} ' > file-list.txt 

werden die kürzeren Dateinamen übereinstimmen, aber nicht die längeren wegen der variablen Länge von Zeichen vor dem Feld Ich möchte sortiert werden soll.

Gibt es eine Möglichkeit, alle Dateien zu sortieren, ohne zuerst die Dateien mit kürzerer Länge aufzufüllen, um sie alle gleich lang zu machen?

+1

FYI - der 'sort' Befehl ist nicht Teil von bash, sondern ein Standard-UNIX-Dienstprogramm. Als solches ist es für jedes Programm (und jede Shell) verfügbar. –

Antwort

4

Perl zur Rettung!

perl -e 'print "$_\n" for sort { substr($a, -11, 3) cmp substr($b, -11, 3) } glob "*.RST.txt"' 

Wenn Ihr Perl neuere (5.10 oder höher) ist, können Sie es zu

perl -E 'say for sort { substr($a, -11, 3) cmp substr($b, -11, 3) } glob "*.RST.txt"' 
+0

Danke choroba (und danke, Perl). Mit einer Version vor 5.10 funktioniert also die oberste Zeile perfekt dafür. –

2

Die konventionelle Art und Weise verkürzen dies in bash zu tun ist, Ihr Sortierfeld zu extrahieren. Mit Ausnahme der Art Befehl wird die folgende in reinen bash allein implementiert:

sort_names_by_first_num() { 
    shopt -s extglob 
    for f; do 
    first_num="${f##+([^0-9])}"; 
    first_num=${first_num%[^0-9]*}; 
    [[ $first_num ]] && printf '%s\t%s\n' "$first_num" "$f" 
    done | sort -n | while IFS='' read -r name; do name=${name#*$'\t'}; printf '%s\n' "$name"; done 
} 

sort_names_by_first_num *.RST.txt 

Das heißt, Newline Begrenzungsdateinamen (wie diese Frage zu nennen scheint) ist eine schlechte Praxis: Die Dateinamen auf UNIX-Dateisysteme sind erlaubt Neue Zeilen in ihren Namen enthalten, so dass sie durch Zeilenumbrüche innerhalb einer Liste getrennt werden können, sodass Ihre Liste keine wesentliche Teilmenge des Bereichs gültiger Namen enthalten kann. Es ist viel besser, NULL-Abgrenzung Ihrer Listen. das tun würde aussehen wie so:

sort_names_by_first_num() { 
    shopt -s extglob 
    for f; do 
    first_num="${f##+([^0-9])}"; 
    first_num=${first_num%[^0-9]*}; 
    [[ $first_num ]] && printf '%s\t%s\0' "$first_num" "$f" 
    done | sort -n -z | while IFS='' read -r -d '' name; do name=${name#*$'\t'}; printf '%s\0' "$name"; done 
} 

sort_names_by_first_num *.RST.txt 
+0

Danke, Charles, für eine sehr umfassende Alternative. In diesem Anwendungsfall steht Perl den Benutzern zur Verfügung und es ist vielleicht geringfügig einfacher zu implementieren als die Bash-Funktion, aber ich schätze die Option sehr! –

3

Wegen der Teile des Dateinamens, die Sie als unveränderlich identifiziert haben, können Sie tatsächlich einen Schlüssel bauen, die Art verwenden:

$ echo ABCDE{99999,8765,9876,345,654,23,21,2,3}A1789.RST.txt \ 
    | fmt -w1 \ 
    | sort -tE -k2,2n --debug 
ABCDE2A1789.RST.txt 
    _ 
___________________ 
ABCDE3A1789.RST.txt 
    _ 
___________________ 
ABCDE21A1789.RST.txt 
    __ 
etc. 

Was dies bedeutet ist sell sort die Felder auf dem Zeichen E zu trennen, dann benutze das 2. Feld numerisch. --debug kam in coreutils 8.6 an und kann sehr hilfreich sein, wenn es darum geht, genau zu sehen, was für eine Art zu tun ist.