20

Recently I came across this way to filter out every second value of a list:

perl -E 'say grep --$|, 1..10'
13579

How does it work?

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • 5
    +1. BUT...Please don't EVER EVER use hacky code like that in production. Ever. It's better to parse HTML with regexes. mob's answer explains why it works, but it also explains the readable version: `grep $zero_or_one = 1 - $zero_or_one, @list` – DVK Feb 10 '11 at 15:50
  • 2
    It's a handy trick for code golf, though. – mob Feb 10 '11 at 15:55
  • 4
    @DVK Another "readable" version: `grep $zero_or_one^=1, @list` – bvr Feb 10 '11 at 16:30
  • @bvr - +1. I like your version better – DVK Feb 10 '11 at 17:37
  • 1
    `grep { $i++ % 2 == 0 }, @list` and the intent is clear. Or eliminate extra variables: `@list[ grep { $_ % 2 == 0 } 0 .. $#list ]` – hobbs Feb 11 '11 at 04:32

4 Answers4

25

$| is a special Perl variable that can only have the values 0 and 1. Any assignment to $| of a true non-zero value, like

$| = 1;
$| = 'foo';
$| = "4asdf";          # 0 + "4asdf" is 4
$| = \@a;

will have the effect of setting $| to 1. Any assignment of a false zero value

$| = 0;
$| = "";
$| = undef;
$| = "erk";            # 0 + "erk" is 0

will set $| to 0.

Expand --$| to $| = $| - 1, and now you can see what is going on. If $| was originally 1, then --$| will change the value to 0. If $| was originally 0, then --$| will try to set the value to -1 but will actually set the value to 1.

mob
  • 117,087
  • 18
  • 149
  • 283
  • 1
    Interestingly enough, perldoc perlvar does NOT mention "0 or 1 only" IIRC – DVK Feb 10 '11 at 15:58
  • 4
    @DVK I've seen a discussion on perlmonks about it. They came to conclusion that only place where it is "documented" is perl source code. Rather accidental feature that real intention, obviously. – bvr Feb 10 '11 at 16:28
  • @eugene y - good catch. The value assigned to `$|` is evaluated as a number, not for truthiness. – mob Feb 10 '11 at 16:29
  • 5
    @DVK - other undocmented but handy code golf tricks: `$%` and `$=` can only take on integer values (good when integer division is required), `$-` can only take on *non-negative* integer values, and `$?` can only take on integers on the range 0 to 65535. – mob Feb 10 '11 at 16:32
  • 2
    @mob -OMFG. I'm switching to Ruby! :) – DVK Feb 10 '11 at 17:36
  • Correction: `$?` can take on integer values between -1 and 65535. – mob Oct 23 '11 at 04:52
6

Ha! $| flips between the values of zero (false, in perl) and one (true) when "predecremented" -- it can only hold those values.

So, your grep criterion changes on each pass, going true, false, true, false, etc., and so returning every other element of the list.

Too clever by half.

pilcrow
  • 56,591
  • 13
  • 94
  • 135
3

$| can only be zero or one. The default is 0, so by decrementing it before grep -ing the 0th index, it'll be one.

The subsequent decrements will actually "toggle" it from zero to one to zero and so forth.

Linus Kleen
  • 33,871
  • 11
  • 91
  • 99
3

The point is this use is just a nasty hack.
$| ( or his more readable alias $OUTPUT_AUTOFLUSH) is a special variables to control the autoflush of STDOUT ( or the current selected filehandle). Therefore it only accepts true (1) or false (0).