Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Golf: reverse sort /etc/passwd by UID

by Tommy (Chaplain)
on Feb 05, 2013 at 16:16 UTC ( [id://1017212]=perlmeditation: print w/replies, xml ) Need Help??

First some context: This is the format of an /etc/passwd file (rough example)...

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
rpc:x:32:32:Portmapper RPC user:/:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
haldaemon:x:68:68:HAL daemon:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin

Now then, can you sort that (numerically, descending) by UID (3rd field) and do it with less code? Show me how! I bet there's a way to optimize-out that split below...

cat /etc/passwd | perl -e 'print join ":", @$_ for sort { $b->[2] <=> +$a->[2] } map { [ split /:/, $_ ] } <>'

Granted, you can use the code below also, but it doesn't port to a certain flavor of Unix I have to work with more than I'd like... The Perl code works on all the platforms without changes...

cat /etc/passwd | sort -t : -k3 -nr # the above method works great with GNU /bin/sort!

Tommy
A mistake can be valuable or costly, depending on how faithfully you pursue correction

Replies are listed 'Best First'.
Re: Golf: reverse sort /etc/passwd by UID
by tobyink (Canon) on Feb 05, 2013 at 16:56 UTC

    How about this...

    perl -e'print$_->[0]for sort{$b->[3]-$a->[3]}map[$_,split/:/],<>' /etc +/passwd

    With additional whitespace, that's...

    perl -e'print $_->[0] for sort { $b->[3] - $a->[3] } map [$_, split /: +/], <>' /etc/passwd
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

      Hey that's clever! ...Preserving the original line as a member (1st member) in the arrayref so you don't have call join() on the arrayref just to get the line back. *golf clap*

      Tommy
      A mistake can be valuable or costly, depending on how faithfully you pursue correction
Re: Golf: reverse sort /etc/passwd by UID
by moritz (Cardinal) on Feb 05, 2013 at 18:17 UTC
    Here's a Perl 6 solution, works with current Rakudo. Not really golfed, just the way you'd write it in a terse way:
    $ cat /etc/passwd | perl6 -e '.say for lines.sort: +*.split(":")[2]'

    The nice part is that Perl 6's sort method automatically does the Schwartzian transform for you if the comparison code object takes only one argument.

Re: Golf: reverse sort /etc/passwd by UID
by BrowserUk (Patriarch) on Feb 05, 2013 at 17:11 UTC

    Does this work?

    ## typos (see below) corrected perl -E"say reverse sort{/(:\d+:)/<=>/(:\d+:)/}<>" /etc/passwd

    (Maybe not if usernames can be all digits?)


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Does this work?

      perl -E"say reverse sort{/(:\d+:)/<=>/(:\d+:)/}<>" /etc/passwd

      No, it does not work.

      In the sort compare function the current records are in $a and $b but you are not accessing the current record in your example.

      The comparison operator <=> puts its operands into scalar context and the match operator in scalar context returns TRUE or FALSE so you are not comparing the contents of the capturing parentheses.

        It works, using the exact input I specified (as well as my /etc/passwd file...

        UPDATE: see my post about me being wrong...

        Tommy
        A mistake can be valuable or costly, depending on how faithfully you pursue correction
      perl -E"say reverse sort{/(:\d+:)/<=>/(:\d+:)/}<>" /etc/passwd

      It's quite possible that I'm being exceedingly dense, but how is this any different from just:

      perl -E"say reverse<>" /etc/passwd

      ? Perhaps you meant

      perl -E"say sort{($b=~/:(\d+)/)[0]<=>($a=~/:(\d+)/)[0]}<>" /etc/passwd

        D'oh! I did omit a couple of bits didn't I (It was (obviously) untested -- I don't have an /etc/passwd).

        I tried to remove the $b=~ & $a=~ bits when I noticed that reverse was one character less -- ignoring/forgetting that $_ cannot be both at the same time :(.

        I don't think the (...)[0] bits are necessary, without /g only the regex will only return one (the first) match.

        This should work? (still untested):

        perl -E"say sort{$b=~/:(\d+):/<=>$a=~/:(\d+):/}<>" /etc/passwd

        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        It's quite possible that I'm being exceedingly dense, but how is this any different from just:
        perl -E"say reverse<>" /etc/passwd

        UPDATE- we only wanted it to work as described below-- with implicit reassignment of $_ to $a and also to $b.

        It's not the same. BrowserUK's solution uses two regexes, each with a capture group. The capture groups implicitly assign to what would be the equivalents of "$a" and "$b". Since the point of this excercise was to save typing (potentially at the loss of clarity), BrowserUK didn't bother wasting precious keystrokes in explicitly assigning the result of the regex capture groups to an "$a" or "$b". Instead, he/she (I don't know BrowserUK personally) just let Perl handle those details. BrowserUK definitely meant what was posted ;~)

        Tommy
        A mistake can be valuable or costly, depending on how faithfully you pursue correction

      UPDATE- as thundergnat correctly points out, the code doesn't work as first intended, at least not without some modification.

      Technically it's *possible* that usernames can be completely numeric, but that's outright abusive--borderline illegal, and definitely prone to all kinds of problems.

      If I we were dealing with "abusive" uid's, it wouldn't be too hard to add a neg look-behind assertion.

      Now, for the meat and potatoes... I did run into some small typos in your code, but after correcting them, it worked. *slow and dramatic golf clap*. I had a hunch it could be done with regexes!! I've said it before, and I'll say it again... you have a dark gift my friend (sarcasm).

      [user@host]$ perl -e 'print reverse sort{/(:\d+:))/<=>/(:\d+:/)}<>' /e +tc/passwd Unmatched ) in regex; marked by <-- HERE in m/(:\d+:)) <-- HERE / at - +e line 1. [user@host]$ perl -e 'print reverse sort{/(:\d+:)/<=>/(:\d+:/)}<>' /et +c/passwd Unmatched ( in regex; marked by <-- HERE in m/( <-- HERE :\d+:/ at -e +line 1. [user@host]$ perl -e 'print reverse sort{/(:\d+:)/<=>/(:\d+:)/}<>' /et +c/passwd

      ...and AWESOMENESS HAPPENS ALMOST HAPPENED!

      Read through the rest of the thread for the reason why this awesome snippet didn't quite work as intended...

      Tommy
      A mistake can be valuable or costly, depending on how faithfully you pursue correction
Re: Golf: reverse sort /etc/passwd by UID
by MidLifeXis (Monsignor) on Feb 06, 2013 at 14:22 UTC

    51:

    # 1 2 3 4 5 # 123456789012345678901234567890123456789012345678901 print sort{($x,$y)=(split(":",$a.$b))[2,8];$y-$x}<>

    First-ever golf, has to be a better solution.

    --MidLifeXis

      Nice trick with split. Can be improved somewhat...

      # 1 2 3 4 # 1234567890123456789012345678901234567890123 perl -e'print sort{@x=split":",$a.$b;$x[8]-$x[2]}<>' /etc/passwd

      Or even:

      # 1 2 3 4 # 123456789012345678901234567890123456789012 perl -e'print sort{(@x=split":",$a.$b)[8]-$x[2]}<>' /etc/passwd

      Update: or with a sufficiently outdated Perl...

      # 1 2 3 4 # 1234567890123456789012345678901234567890 perl5.8.9 -e'print sort{split":",$a.$b;$_[8]-$_[2]}<>' /etc/passwd

      (split in void context was deprecated in 5.10 and dropped in 5.12 IIRC.)

      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

        D'oh :-) Also, 41 using say.

        Update: Nice: 40?

        Update 2: Beat me to the punch on scalar split and @_ :-)

        --MidLifeXis

      49: s/print/say/ ( I think - don't have a perl > 5.8.8 installed with a passwd file ).

      --MidLifeXis

      That. Was. Awesome.

      ++MidLifeXis

      Tommy
      A mistake can be valuable or costly, depending on how faithfully you pursue correction
Re: Golf: reverse sort /etc/passwd by UID
by tobyink (Canon) on Feb 06, 2013 at 10:23 UTC

    OK, using salva's wonderful Sort::Key, I can go five characters shorter...

    perl -MSort::Key=rikeysort -e'print rikeysort{/(\d+)/;$1}<>' /etc/pass +wd

    If you don't mind a spurious blank line at the end of the output, then it's easy to drop another two characters...

    perl -MSort::Key=rikeysort -E'say rikeysort{/(\d+)/;$1}<>' /etc/passwd
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Golf: reverse sort /etc/passwd by UID
by choroba (Cardinal) on Feb 06, 2013 at 15:29 UTC
    Only suitable for a terminal, not to be redirected to a file:
    # 1 2 3 4 # 12345678901234567890123456789012345678901234 perl -e 'print sort map 9/(1+(split/:/)[2])."\r$_",<>' /etc/passwd
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Nice, but one of the lines in my /etc/passwd file comes out as:

      1.52590218966964e-05\rnobody:x:65534:65534:nobody:/nonexistent:/bin/sh

      And thus gets sorted after root.

      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
        OK, just change 1/ to 9/ (or 8 or 7). Updated.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

        I'm not clear on how it sorts on a field in the text while at the same time appending the text to the field which is sorted. What am I missing here?

        Tommy
        A mistake can be valuable or costly, depending on how faithfully you pursue correction

      Good gosh, can't even decipher that. Sorting the output of a map applying a mathematical expression to the list of lines on STDIN... but how do you discard the text it generated here --> (1+(split/:/)[2])<-- ??

      Tommy
      A mistake can be valuable or costly, depending on how faithfully you pursue correction

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://1017212]
Approved by Ratazong
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (3)
As of 2024-03-19 04:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found