Sortieren Sie die Eingabedatei nach den Ergebnissen eines regulären Ausdrucks

7

Ich möchte eine Datei basierend auf den Ergebnissen eines regulären Ausdrucks sortieren. Zum Beispiel, wenn ich die folgenden Eigenschaftsdeklarationen in Obj-C habe

@property (nonatomic, strong) id <AlbumArtDelegate, UITextFieldDelegate> *albumArtView; // 1
@property (nonatomic, strong, readonly) UIImageView *profileView;  // 2
@property (nonatomic, strong, readwrite) UIButton *postFB;          // 3
@property (nonatomic, assign) UIButton *saveButton;      // 4

Standardmäßig werden sie in der Reihenfolge [4, 1, 2, 3] sortiert, aber ich möchte sie in der Reihenfolge der tatsächlichen Eigenschaftsnamen [1, 3, 2, 4] sortieren. Ich kann einen regulären Ausdruck schreiben, um nur den Eigenschaftsnamen herauszufiltern. Kann ich nach den Ergebnissen dieses Ausdrucks sortieren?

Gibt es ein eingebautes Unix-Tool, das dies für mich erledigt? Ich arbeite in Xcode, daher helfen VIM / Emacs-Lösungen nicht weiter.

Der Grund, warum ich dies mit einem regulären Ausdruck tun möchte, ist, dass ich meinen Sortieralgorithmus erweitern kann, um in anderen Situationen zu arbeiten. Verwenden Sie diese Option, um Methodendeklarationen, Importanweisungen usw. zu sortieren.

kubi
quelle
1
sort -k 5funktioniert nicht?
cjc
Ich bin nicht so vertraut mit Sort und seinen Flaggen, aber ich bin mir ziemlich sicher, dass das nicht funktionieren wird. Es gibt große Unterschiede in der Anzahl der Elemente zwischen dem Zeilenanfang und dem Namen der Eigenschaft. Es ist 5 in diesem Beispiel, aber es könnte 3 oder mehr als 7 sein.
Kubi
Also, was ist das eigentliche Format? Das Feld, nach dem Sie sortieren möchten, befindet sich in der letzten Spalte (außer Sie haben möglicherweise einen // Kommentar)?
cjc
Siehe auch den letzten Teil meiner Frage. Welche Lösung ich auch bekomme, ich möchte sie für eine viel komplexere Sortierung verwenden können als die Beispiele, die ich hier gegeben habe. Wie Sortiermethodendeklarationen. In diesem Fall befindet sich der Wert, nach dem ich sortieren möchte, in der Mitte des ersten Schlüssels.
Kubi

Antworten:

11

Eine allgemeine Methode zum Sortieren des Zeileninhalts nach einer beliebigen Funktion lautet wie folgt:

  1. Holen Sie sich den Schlüssel, nach dem Sie sortieren möchten, und kopieren Sie ihn an den Zeilenanfang
  2. Sortieren
  3. Löschen Sie den Schlüssel am Zeilenanfang

Hier ist ein Schlüssel, den Sie in diesem speziellen Fall verwenden können: Dieses sedProgramm gibt die Zeile vom letzten Bezeichner bis zum Ende aus.

% sed -e 's/^.*[^[:alnum:]_]\([[:alpha:]][[:alnum:]_]*\)/\1/' < decls

albumArtView; // 1
profileView;  // 2
postFB;          // 3
saveButton;      // 4

So legen Sie diese Tasten und die ursprünglichen Zeilen nebeneinander:

% paste <(sed -e 's/^.*[^[:alnum:]_]\([[:alpha:]][[:alnum:]_]*\)/\1/' < decls) decls

Um sie zu sortieren ...

| sort

und nur das zweite Feld (die ursprüngliche Zeile) zu verlassen

| cut -f 2-

Alles zusammen (Sortieren in umgekehrter Reihenfolge, es gibt also etwas zu zeigen):

% paste <(sed -e 's/^.*[^[:alnum:]_]\([[:alpha:]][[:alnum:]_]*\)/\1/' < decls) decls \
  | sort -r \
  | cut -f 2-

@property (nonatomic, assign) UIButton *saveButton;      // 4
@property (nonatomic, strong, readonly) UIImageView *profileView;  // 2
@property (nonatomic, strong, readwrite) UIButton *postFB;          // 3
@property (nonatomic, strong) id <AlbumArtDelegate, UITextFieldDelegate> *albumArtView; // 1
Angus
quelle
2
Nur eine Anmerkung b / c das OP wird wahrscheinlich diesen Ansatz anpassen wollen. Durch Verwenden \1 &des Ersetzungsausdrucks des Befehls sed replace können Sie die declstemporäre Datei und den pasteAufruf überspringen . Außerdem müsste der reguläre Ausdruck so eingestellt werden, dass er nur mit dem Bezeichner übereinstimmt (das angegebene Beispiel würde fehlschlagen, wenn ein Kommentar am Ende der Zeile Bezeichner ohne Zahlen enthält).
jw013
Danke, @angus!. Unter Berücksichtigung des Kommentars von @ jw013 bin ich sicher, dass dies für das, was ich will, wunderbar funktionieren wird.
Kubi
3
@ jw013: Ja, ich dachte, er möchte vielleicht ein anderes Tool verwenden, um den Schlüssel zu bekommen - vielleicht kennt er Perl oder Awk. In diesem Fall ist using pastedie generische Lösung mit dem kleinsten Nenner. Übrigens sollte das eine Registerkarte zwischen \1und sein &(von hier nicht zu unterscheiden).
Angus
@ Kubi froh, Ihnen zu helfen. :)
Angus
Am Ende habe ich nicht genau das getan, sondern stattdessen ein Ruby-Skript in einen Automator.app-Dienst eingefügt. jimkubicek.com/blog/2012/09/20/sort-methods-with-automator
kubi
2
PIPED-DATA | sed -r "s/(\*\w+)/\x01&\x01/" | sort -k2 -t$'\x01' |tr -d $'\x01'

Das obige Skript reicht für Ihre Situation aus. Eigentlich ist es genug für jede Sortierung mit einem Schlüsselfeld. Für dasselbe Skript, erweitert, lesen Sie weiter.


Das folgende Skript richtet das zu sortierende Feld als 2 ein , das Feldlayout ist jedoch recht flexibel. Sie können bei Bedarf nach mehreren Feldern sortieren, indem Sie geeignete Regex-Muster angeben und die Sortieroptionen entsprechend ändern.

Jedes Feldmuster sollte in normale (Klammern )und eingeschlossen werden 'single-quoted'.

Die von Ihnen bereitgestellten Muster werden durch jedes von Ihnen ausgewählte eindeutige Zeichen begrenzt. sedbenötigt auch ein eindeutiges Trennzeichen. Das Skript verwendet Trennzeichen \x01und \x02. Diese Begrenzerwerte wurden ausgewählt, da sie normalerweise nicht in Textdateien angezeigt werden.

Beachten Sie, dass Ihr Setup auf der Feldzusammensetzung und nicht auf Feldtrennzeichen basieren muss.

n=2                                  # field number to sort on
p=( '(.*)'  '(\*\w+)'  '(.*)' )      # Set up regex field patterns

f=; r=; d=$'\x01';  x=$'\x02'        # Build patterns and delimiters
for (( i=0; i<${#p[@]}; i++ )) ;do 
   f+="${p[i]}"; r+="\\$((i+1))$x"
done

sed -r "s$d$f$d$r$d" file |sort -k$n -t"$x" |tr -d  "$x"

Ausgabe:

@property (nonatomic, strong) id <AlbumArtDelegate, UITextFieldDelegate> *albumArtView; // 1
@property (nonatomic, strong, readwrite) UIButton *postFB;          // 3
@property (nonatomic, strong, readonly) UIImageView *profileView;  // 2
@property (nonatomic, assign) UIButton *saveButton;      // 4
Peter.O
quelle
1
sort -k 5 ~/Temp/data

arbeitete für mich an Cygwin.

gt6989b
quelle
Aktualisierte meine Frage mit einem schwierigeren (und realistischeren) Beispiel
Kubi
1
sort -k 5n ~/Temp/datafür numerische Sortierung nach Feld 5.
Petr Javorik
0

Dies verwendet Python. Die Pythons-Syntax ist nicht gut für Einzeiler, außer dass die Bash-Shell zwei Zeilen problemlos verarbeitet und der Code doppelte Anführungszeichen für seine Zeichenfolgenkonstanten verwenden kann :-)

Mit Pythons-Sortierroutinen können Sie eine Lambda-Funktion verwenden, um den Schlüssel für die zu sortierenden Zeilen zu extrahieren (Dekorieren, Sortieren, Nicht-Dekorieren der anderen Methoden).

Der von mir verwendete reguläre Ausdruck extrahiert nur das Nicht-Leerzeichen nach dem ersten '*' - Teilstring in den Zeilen.

paddy$ python -c 'import sys, re
print ("\n".join(sorted((line.rstrip() for line in sys.stdin), key=lambda x: re.search(r"\s[*](\S+)", x).group(1))))' < test_in2.txt 
(nonatomic, strong) id <AlbumArtDelegate, UITextFieldDelegate> *albumArtView; // 1
@property (nonatomic, strong, readwrite) UIButton *postFB;          // 3
@property (nonatomic, strong, readonly) UIImageView *profileView;  // 2
@property (nonatomic, assign) UIButton *saveButton;      // 4
paddy$ 
Paddy3118
quelle
Wenn Sie Python auf diese Weise verwenden möchten, empfehle ich pythonpy. Damit kann Ihr Code so geschrieben werden, als py -l 'sorted(l, key=lambda x: re.search(r"\s[*](\S+)", x).group(1)) '< test_in2.txthätte ich keine Klammern durcheinander gebracht.
GingerPlusPlus
Danke Ingwer, aber nein danke. Ich bin ziemlich glücklich, awk / Perl / sed für erweiterte Einzeiler zu verwenden, wenn ich muss, bevorzuge aber die Verwendung eines unverfälschten Python.
Paddy3118
0

Ich habe ein Perl-Skript erstellt, um genau dies zu tun. Sie können einen regulären Ausdruck eingeben, um eine Datei nach dem ersten Capture zu sortieren. Dann können Sie ein Flag setzen, um entweder einen String- oder einen numerischen Vergleich durchzuführen. Wirf dieses Codebeispiel einfach in eine .pl-Datei.

Es ist ziemlich einfach und die Logik sitzt wirklich nur in den Zeilen 20-37.

#! /usr/bin/perl
# Created by pete Nixon

use Getopt::Long;
use strict;
use Cwd qw(abs_path);

my $exec_path = abs_path($0);
   $exec_path =~ s/(.*\x2f)[^\x2f]+$/$1/g;
my $path = abs_path($1);

&getCommandLineArguments;

my $file_flag;
my $regex;
my $type_flag;
my @lines;
my @sortedLines;

open (FILE, $file_flag) || die "Couldn't open rule file, $!";
while (<FILE>) {
    chomp $_;
    if ($_ =~ /^\s*\n/) {
        next;
    }
    push (@lines, $_);
}

if ($type_flag eq 1) {
    @sortedLines = sort { ($a =~ m/$regex/)[0] <=> ($b =~ m/$regex/)[0]} @lines; # where the magic happens
} else {
    @sortedLines = sort { ($a =~ m/$regex/)[0] cmp ($b =~ m/$regex/)[0]} @lines; # where the magic happens
}

foreach (@sortedLines) {
    print "$_\n";
}

sub getCommandLineArguments() {
    my $help;
    my $clear = "[0m";
    my $black = "[0;30m";
    my $blue = "[0;34m";
    my $green = "[0;32m";
    my $cyan = "[0;36m";
    my $red = "[0;31m";
    my $purple = "[0;35m";
    my $brown = "[0;33m";
    my $gray = "[0;37m";
    my $darkGray = "[1;30m";
    my $lightBlue = "[1;34m";
    my $lightGreen = "[1;32m";
    my $lightCyan = "[1;36m";
    my $lightRed = "[1;31m";
    my $lightPurple = "[1;35m";
    my $yellow = "[1;33m";
    my $white = "[1;37m";
    GetOptions (
        'file|f=s' =>   \$file_flag,
        'regex|r=s' => \$regex,
        'type|t=s' => \$type_flag,
        'help|h|?' => \$help
        ) or die ("Error in command line arguments$clear\n");
    if ($help || $file_flag eq undef && $regex eq undef) {
        print "$green================================================================================$clear\n";
        print "$red WHAT DOES THIS SCRIPT DO?\n$clear";
        print "$cyan    - This program a regex and sorts a line based on it.\n$clear";
        print "$red HOW DO I USE THIS SCRIPT?\n$clear";
        print "$cyan    - Type the name of this script, space, options (see the next section)\n$clear";
        print "$green   SAMPLE: '$clear" . "sortbyregex.pl -f file -r \"regex\" -t (1|2)$green'\n$clear";
        print "$red WHAT OPTIONS ARE AVAILABLE?\n$clear";
        print "$yellow  -f, --file\n$clear";
        print "$cyan    - Use to specify a regex\n$clear";
        print "$yellow  -r, --regex\n$clear";
        print "$cyan    - Use to specify the regex used for sorting, must include one capture\n$clear";
        print "$yellow  -t, --type\n$clear";
        print "$cyan    - Use to specify the type of sorting 1 = numeric 2 = string\n$clear";
        print "$yellow  -h, --help, -?\n$clear";
        print "$cyan    - Use to see this help... so... yeah...\n$clear";
        print "$green================================================================================$clear\n";
        exit(0);
    }
}
Pete Nixon
quelle