Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'd like to sort a file based on the results of a regex expression. For example, if I have the following property declarations in Obj-C

@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

By default they will sort in order [4, 1, 2, 3], but I would like to sort them in order of the actual property names, [1, 3, 2, 4]. I can write a regular expression to tease out just the property name, is it possible for me to sort by the results of that expression?

Is there any built-in Unix tool that will do this for me? I'm working in Xcode, so VIM/emacs solutions won't help.

Also, the reason I'd like to do this using a regex is so that I can expand my sorting algorithm to work in other situations. Use it to sort method declarations, import statements, etc.

share|improve this question
1  
sort -k 5 doesn't work? – cjc Aug 13 '12 at 19:08
    
I'm not that familiar with sort and its flags, but I'm fairly sure that won't work. There's a large variation in the number of items between the beginning of the line and the name of the property. It's 5 in this example, but it could be 3 or more than 7. – kubi Aug 13 '12 at 19:12
    
So, what's the actual format? The field you're interested in sorting on is in the last column (except you may have a // comment)? – cjc Aug 13 '12 at 19:14
    
Also, see the last part of my question. Whatever solution I get, I'd like to be able to use it for much more complex sorting than the examples I've given here. Like sorting method declarations. In that case, the value I want to sort by would be in the middle of the first key. – kubi Aug 13 '12 at 19:14
up vote 9 down vote accepted

A general method to sort by an arbitrary function of the contents of the line is as follows:

  1. Get the key you want to sort by, and copy it to the beginning of the line
  2. Sort
  3. Delete the key from the beginning of the line

Here is a key you can use in this particular case: this sed program will output the the line from the last identifier to the end.

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

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

To put these keys and the original lines side by side:

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

To sort them ...

| sort

and to leave just the second field (the original line)

| cut -f 2-

All together (sorting in reverse order, so there's something to show):

% 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
share|improve this answer
2  
Just a note b/c the OP will probably want to adapt this approach. By using \1 & in the replace expression of the sed substitute command, you can skip the decls temporary file and the paste invocation. Also, regex would need to be tuned to match only the identifier (the example given would misfire if a comment at the end of the line contained any identifiers with no numbers). – jw013 Aug 13 '12 at 19:36
    
Thanks, @angus!. Taking into account @jw013's comment, I'm sure this will work beautifully for what I want. – kubi Aug 13 '12 at 19:38
3  
@jw013: Yes, I thought he might want to use another tool to get the key--maybe he knows perl or awk. In that case, using paste is the generic, "least denominator" solution. Btw, that should be a tab between \1 and & (impossible to distinguish from here). – angus Aug 13 '12 at 19:43
    
@kubi glad to be of help. :) – angus Aug 13 '12 at 19:47
    
I ended up not doing this exact thing, but instead put a ruby script in an Automator.app Service. jimkubicek.com/blog/2012/09/20/sort-methods-with-automator – kubi Sep 21 '12 at 4:29
PIPED-DATA | sed -r "s/(\*\w+)/\x01&\x01/" | sort -k2 -t$'\x01' |tr -d $'\x01'

The above script is enough for your situation. Actually it is basically enough for any single-key-field sort.. For the same script, expanded, read on.


The following script sets up the field to be sorted as 2, but the field layout is quite flexible. You can sort on multiple fields, if you need to, by specifying appropriate regex patterns, and changing the sort options accordingly.

Each field pattern should be wrapped in normal (brackets) and 'single-quoted'.

The patterns which you provide are delimited by any unique character you choose. sed also needs a unique delimiter. The script uses delimiters \x01 and \x02. These delimiter values were chosen because they do not normallay appear in text files.

Note that your setup must be considered as being based on field composiiton, not by field delimiters..

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"

Output:

@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
share|improve this answer
sort -k 5 ~/Temp/data

worked for me on Cygwin.

share|improve this answer
    
Updated my question with a more difficult (and realistic) example – kubi Aug 13 '12 at 19:17

This uses Python. Pythons syntax isn't good for one-liners except the bash shell will happily handle two lines and the code can use double quotes for its string constants :-)

Pythons sort routines allow you to use a lambda function to extract the key for the lines to be sorted on (the decorate, sort, un-dercorate of the other methods).

The regexp I use just extracts the non-space word after the first ' *' substring in the lines.

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$ 
share|improve this answer
    
If you like using Python this way, I recommend pythonpy. With it, your code can be written as py -l 'sorted(l, key=lambda x: re.search(r"\s[*](\S+)", x).group(1)) '< test_in2.txt, if I didn't messed up parenthesis. – GingerPlusPlus Feb 5 at 17:13
    
Thanks Ginger, but no thanks. I am quite happy to use awk/Perl/sed for extended one-liners if I must, but prefer to use an unadulterated Python. – Paddy3118 Feb 14 at 22:52

I created a perl script to do exactly this, you can input a regex to sort a file by the first capture. then you can set a flag to do either string or numerical comparison. just toss this code sample into a .pl file.

it's pretty simple and the logic really just sits on lines 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);
    }
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.