http://www.perlmonks.org?node_id=762986

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

Hello! I wrote something recently, and then had to go through it and actually seperate things into a few lines to get it to work (not that I did it right the first time...) It's a simple code to make a list of files dropping out the first few levels of file paths. The code in question is here:
use File::Find; open (OUT, ">filelist.txt"); find( sub { $temp = $File::Find::name; $temp =~ s/C:\/users\/User\/Music\/(.*)/$1/gi; print OUT "$temp\n"; }, "C:/users/User/Music/"); close OUT;
If you can't tell just by looking at it that I'm running Windows, you probably can't help me here :~)

It's Strawberry Perl 5.8

I recall reading once that someone wrote a web spider in one line...I'm not really good at one liners. Is there a way to squish
$temp = $File::Find::name; $temp =~ s/C:\/users\/User\/Music\/(.*)/$1/gi; print OUT "$temp\n";
into two or even one lines? And does anyone know a good place to start looking for something like a brevity tutorial? I recently watched a podcast where the speaker said, "If statements are obsolete if you know how to use the space-ship operator!" I'm not so much looking to obfuscate, but I'd like to save a few keystrokes.

Thanks and Cheers!
~Petras

Ready.
poke 53280,0
poke 53281,0
ctrl+2 load "zork",8,1

West of House

Replies are listed 'Best First'.
Re: How to make code smaller--saving steps and writing one liners
by perrin (Chancellor) on May 09, 2009 at 03:46 UTC
    In general, making things easy to read is more important than making them short. In this case though, I'd agree that you're taking the long way around without a good reason. I'd do something like this (untested!):
    print ($File::Find::name =~ m/^C:\/users\/User\/Music\/(.*)$/i);
    P.S. Please use strict and warnings!
Re: How to make code smaller--saving steps and writing one liners
by ikegami (Patriarch) on May 09, 2009 at 03:38 UTC

    I wasn't happy with the fragile redundancy present in the OP and my earlier solution, so what follows are solutions without that problem:

    perl -MFile::Find -le"chdir($ARGV[0]); find { wanted=>sub{ -d || print + /..(.*)/ }, no_chdir=>1 }, '.'" C:\users\User\Music >filelist.txt
    perl -MFile::Find::Rule -le"chdir($ARGV[0]); print for File::Find::Rul +e->file->in('.')" C:\users\User\Music >filelist.txt

    The key is to use chdir to select the perspective you want (and preserve that perspective using no_chdir=>1 with File::Find)

    In both cases, I filtered out directories.

    Ref: File::Find, File::Find::Rule

Re: How to make code smaller--saving steps and writing one liners
by afoken (Chancellor) on May 09, 2009 at 02:18 UTC

    My ideas:

    • If you invoke File::Find::find() with the no_chdir=>1 option, $_ and $File::Find::name have the same value. Use local $_=$_; in the "wanted" callback and you can modify it as you like.
    • Just remove the unwanted prefix instead of matching the entire name (unless you want to untaint), and use different RE quotes to avoid the leaning toothpick syndrom: s|^C:/users/User/Music/||i;
    • Save the path in a variable, pass that to File::Find::find(), and use the variable in the substitution using quotemeta: s|^\Q$path\E||i;
    • Your code lacks a check for a successful open, and it uses the "ancient" two-argument open: open OUT,'>','filename.txt' or die "Can't open filename.txt: $!";
    • If you would redirect STDOUT when invoking the script, you don't have to work with OUT and open() at all. That would also allow to pipe the output into another program.
    • If you want to list only files, test for files inside the callback, before modifying $_: return unless -f $_;

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
      Use local $_=$_; in the "wanted" callback and you can modify it as you like.
      Or just modify it as you like :) File::Find doesn't mind

        You are right, I was too careful here. The written contract (the File::Find documentation) explicitly states:

        The above variables [$File::Find::dir, $File::Find::name, and $_] have all been localized and may be changed without effecting data outside of the wanted function.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: How to make code smaller--saving steps and writing one liners
by ikegami (Patriarch) on May 09, 2009 at 02:32 UTC
    dir /s/b C:\users\User\Music | perl -pe"s/^C:\\users\\User\\Music\\//" + >filelist.txt

    You probably want to add /a-d to avoid listing directory names.

Re: How to make code smaller--saving steps and writing one liners
by ig (Vicar) on May 09, 2009 at 04:10 UTC

    I agree with perrin that making code easy to read and understand is more important than making them short.

    Sometimes making code short does make it easier to read and understand, but not always. In some cases, making code shorter may make it easier to read while you are actively working on it but it may be harder for others, or yourself when you return to it after some time.

    Perl is a free-form language, so one can put anything and everything on one line. I sometimes put multiple statements on one line and sometimes split a long statement across multiple lines, both to improve readability.

    I would make only a slight change to perrin's recommended solution - changing the delimiters on the re to avoid the escapes (tested (on linux)):

    print ($File::Find::name =~ m!^/some/path/prefix/(.*)$!i);
Re: How to make code smaller--saving steps and writing one liners
by Marshall (Canon) on May 09, 2009 at 02:54 UTC
    "If statements are obsolete if you know how to use the space-ship operator!"

    That is just NONSENSE!

    1) The space-ship operator, <=>, is a numeric comparison.
    2) eq is an alpha-numeric comparison.
    There are very good reasons for using <=> or eq in conjunction with an if statement! DUH!!

Re: How to make code smaller--saving steps and writing one liners
by Anonymous Monk on May 09, 2009 at 02:14 UTC
    Add autodie since you're not error checking, all one line
    use File::Find; use autodie; open (OUT, ">filelist.txt"); find( sub { + $temp = $File::Find::name; $temp =~ s/C:\/users\/User\/Music +\/(.*)/$1/gi; print OUT "$temp\n"; }, "C:/users/User/Music/"); c +lose OUT;
Re: How to make code smaller--saving steps and writing one liners
by BrowserUk (Patriarch) on May 09, 2009 at 12:26 UTC

      I would consider that line noise. ;-)

      (Not exactly your code, but that sick syntax extension of DOS batches based on implementation errors in ancient DOS versions.)

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)