Spinone has asked for the wisdom of the Perl Monks concerning the following question:

I have inherited a script which contains (amongst other things) a one-line function as follows:

sub Basename { return $0 =~ m!/! ? $0 =~ m!/?([^/]+$)! : $0; }

This gets called from the main program using &Basename in a PRINT statement.

I can see that this is using the Perl ?: Ternary Operator and the m (match) operator with ! delimiters rather than the usual slash.

$0 is the full filename, so the first part of the ternary (the test) checks if the filepath contains a forward slash.

If it DOESN'T (e.g. if it's a Windows filepath which uses backslashes), the full filepath is returned (via the last part of the ternary), BUT if it's a UNIX-like filepath, it returns just the name of the file, stripping out the path - e.g. if the full script path is /opt/bin/mydir/perl/setup.pl, it will return setup.pl

I can see what it's doing, but don't understand how the bind in $0 =~ m!/?([^/]+$)! strips the path off - or, in fact, returns anything.

Can anyone explain what's happening here?

Cheers, Chris

Replies are listed 'Best First'.
Re: Return value from a binding operator?
by LanX (Saint) on Apr 12, 2013 at 16:15 UTC
    > I can see what it's doing, but don't understand how the bind in $0 =~ m!/?([^/]+$)! strips the path off - or, in fact, returns anything.

    In list context the match returns a list of those (grouped) submatches.

    In this case everything "non /" [^/] between one or none slash /? and end of string $.

    HTH =)

    Cheers Rolf

    ( addicted to the Perl Programming Language)

    EDIT: I'm not sure if it makes sense to put $ is within the group. Not my style...

    UPDATE

    CAREFUL!!!

    This Basename() routine is dangerous cause it expects a scalar to be returned but only in list context!

    DB<116> $0="a/b" => "a/b" DB<117> @a=Basename() => "b" # all grouped matches DB<118> $a=Basename() => 1 # true it matched

    Plz be aware that the context is propageted to return.

    UPDATE

    thx Athanasius for spotting the same problem... race condition between my update and your post :)

      it returns those submatches in list context.

      But note that in this particular case, the context of the return statement — void, scalar, or list — is determined dynamically by the subroutine’s caller. Then, within the conditional operator ?:, the context “propagates downward into the 2nd or 3rd argument, whichever is selected.” (Conditional Operator). So if the sub is called in scalar context, and the condition evaluates to true, the regex will return true or false (i.e. 1 or 0) depending on whether it matches. On the face of it, this seems like a very poor design. :-(

      Hope that helps,

      Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        Thx for spotting the same problem! ;)

        Here another version¹ independent from callers context!

        DB<138> sub Basename { my ($file) = $0 =~ m#/?([^/]+)$#; return $file; } DB<139> $0="a/b" => "a/b" DB<140> $a=Basename() => "b" DB<141> $0="b" => "b" DB<142> $a=Basename() => "b"

        Cheers Rolf

        ( addicted to the Perl Programming Language)

        ¹) Just for didactic reasons, using a well tested basename-sub from CPAN is almost always better, File::Basename is even core!

        DB<153> use File::Basename DB<159> basename("a") => "a" DB<160> basename("a/b") => "b" DB<161> basename("a/b/c") => "c" DB<162> basename("a/") => "a"

      Thanks.

      I was reading the second section as {match}{optional /}+{line beginning /} {not sure what $ meant}

      So that has helped some, but I still don't really get what that regex syntax does, particularly those square brackets [].

      I clearly have a lot to learn. Maybe there should be a "Absolute Perl Newbie, Don't be afraid to ask really dumb questions" section. :)

      My fault, not anyone elses. Thanks to everyone who replied.

      Chris

        > So that has helped some, but I still don't really get what that regex syntax does, particularly those square brackets [].

        it's a character class with just one element '/', which is negated b/c of leading '^'.

        That means: match any character except '/'.

        see perlretut Using-character-classes for details

        Cheers Rolf

        ( addicted to the Perl Programming Language)

Re: Return value from a binding operator?
by Don Coyote (Hermit) on Apr 12, 2013 at 16:21 UTC

    Hi Chris,

    Think of the middle match as a substition.

    the match basically looks to match all of the string after the last / character. The end of line anchor $ is included in the capturing group, where we may be used to seeing this outside of the capturing group.

    It does not so much as as strip the path off, rather matches the final part of the path, or the basename. I don't know if including the eol anchor $ retrieves the eol character or makes no difference.

    m! #start match /? # 0 or 1 '/' ( # start capture $1 [^/] #consisting of no '/' characters + # 1 or more times $ # until and including eol (?) ) # end capture $1 ! # end match

    this would also match a path with no directories.

      Really useful, thanks.

      I'd worked out that /? meant "optional /" (though not why that was relevant) but, as an absolute Perl beginner, I had assumed that the caret slash (^/) meant "a line beginning with a slash" (still not sure why it doesn't), which didn't make any sense, so I obviously need to get to grips with that.

      Lots to learn....

      Chris
        The ^ metacharacter has different meanings in different parts of a regex.

        Normally, it does mean "start of line", as you thought. If the regex were m!^/!, then it would match lines beginning with /.

        However, as the first character in a character class, it negates the class, so [^/] means "match any character except /". (But, again, that's only if it's the first character in the class - [/^] means "match either a / or a ^". And [^/^] would match any character except those two.)

Re: Return value from a binding operator?
by AnomalousMonk (Archbishop) on Apr 13, 2013 at 21:09 UTC

    This is a prime example of a situation in which the effort of Reading The Fine Manual would have been generously rewarded. See the discussion of  m/PATTERN/ in Regexp Quote-Like Operators in perlop and in particular the sub-section on "Matching in list context".

    There are also tools for elucidating regex behavior. See YAPE::Regexp::Explain for regexes of a vintage no later than Perl 5.6. See also davido's nice Perl Regular Expression Tester, which is very informative about the pattern of the  m!/?([^/]+$)! regex of the OP.