views:

848

answers:

5

I got this strange line of code today, it tells me 'empty' or 'not empty' depending on whether the CWD has any items (other than . and ..) in it.

I want to know how it works because it makes no sense to me.

perl -le 'print+(q=not =)[2==(()=<.* *>)].empty'

The bit I am interested in is <.* *>. I don't understand how it gets the names of all the files in the directory.

+3  A: 

<.* *> means (glob(".*"), glob("*")). glob expands file patterns the same way the shell does.

Dave Hinton
+7  A: 
<.* *>

is a glob consisting of two patterns: .* are all file names that start with . and * corresponds to all files (this is different than the usual DOS/Windows conventions).

(()=<.* *>)

evaluates the glob in list context, returning all the file names that match.

Then, the comparison with 2 puts it into scalar context so 2 is compared to the number of files returned. If that number is 2, then the only directory entries are . and .., period. ;-)

Sinan Ünür
+11  A: 

It's a golfed one-liner. The -e flag means to execute the rest of the command line as the program. The -l enables automatic line-end processing.

The <.* *> portion is a glob containing two patterns to expand: .* and *.

This portion

(q=not =)

is a list containing a single value -- the string "not". The q=...= is an alternate string delimiter, apparently used because the single-quote is being used to quote the one-liner.

The [...] portion is the subscript into that list. The value of the subscript will be either 0 (the value "not ") or 1 (nothing, which prints as the empty string) depending on the result of this comparison:

2 == (()=<.* *>)

There's a lot happening here. The comparison tests whether or not the glob returned a list of exactly two items (assumed to be . and ..) but how it does that is tricky. The inner parentheses denote an empty list. Assigning to this list puts the glob in list context so that it returns all the files in the directory. (In scalar context it would behave like an iterator and return only one at a time.) The assignment itself is evaluated in scalar context (being on the right hand side of the comparison) and therefore returns the number of elements assigned.

The leading + is to prevent Perl from parsing the list as arguments to print. The trailing .empty concatenates the string "empty" to whatever came out of the list (i.e. either "not " or the empty string).

Michael Carman
+1 nice, concise explanation
hillu
That's a little misleading. The inner () make it a list assignment, which gives its right operand list context, but it could just as well be (2==(@a=<.* *>)).
ysth
@ysth: I'm not sure why you think that's misleading. Since there's nothing in the inner `()` the list is discarded. The purpose of the parenthesis (from the programmer's perspective) was to impose list context on glob. He could have assigned to an array instead (which would have been clearer IMHO) but he didn't. Or are you referring to the fact that the empty assignment changes the semantics from "a list in scalar context returns the last element" to "list assignment returns the number of elements"? I should probably edit the answer to clarify that bit...
Michael Carman
@Michael Carman: yes, that was what I was referring to.
ysth
A: 

The documentation for that feature is here. (Scroll near the end of the section)

dsm
What feature? Is this supposed to be a comment to someone's answer?
Telemachus
@Telemachus: <> to mean glob, presumably.
ysth
@Telemachus: The documentation for the glob/<>, as @ysth mentioned
dsm
+2  A: 

I find that the B::Deparse module helps quite a bit in deciphering some stuff that throws off most programmers' eyes, such as the q=...= construct:

$ perl -MO=Deparse,-p,-q,-sC 2>/dev/null << EOF
> print+(q=not =)[2==(()=<.* *>)].empty
> EOF
use File::Glob ();
print((('not ')[(2 == (() = glob('.* *')))] . 'empty'));

Of course, this doesn't instantly produce "readable" code, but it surely converts some of the stumbling blocks.

hillu
Heh. No amount of deparsing will de-obfuscate the use of a boolean to index into an anonymous list. [anti-pedantry: Yes, I know that "anonymous list" is redundant.]
Michael Carman