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.

  • 1
    sort -k 5 doesn't work?
    – cjc
    Aug 13, 2012 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, 2012 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, 2012 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, 2012 at 19:14

5 Answers 5


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
  • 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, 2012 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, 2012 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, 2012 at 19:43
  • @kubi glad to be of help. :)
    – angus
    Aug 13, 2012 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, 2012 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"

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


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

worked for me on Cygwin.

  • Updated my question with a more difficult (and realistic) example
    – kubi
    Aug 13, 2012 at 19:17
  • 1
    sort -k 5n ~/Temp/data for numeric sort according to field 5. Oct 17, 2019 at 9:45

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
  • 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. Feb 5, 2016 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, 2016 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);


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/) {
    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";

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .