Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

One to one file output idiom

by Eily (Monsignor)
on Jan 15, 2020 at 17:41 UTC ( [id://11111448]=perlquestion: print w/replies, xml ) Need Help??

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

Hello monks. I was using perl to translate files from one format to another with something like the following: perl -p translation.pl file.in > file.out when I got lazy and decided that I should be able to just pass a list of input files, and let perl append .out (or replace the extension) to create an output file for each input file.

There are obviously more than one way to do it and I settled for:

for my $filename (@ARGV) { open my $input, '<', $filename or die "Couldn't open $filename: $!"; open my $output, '>', $filename.'.new' or die "Couldn't open $filena +me: $!"; while (<$input>) { say $output translate($_); } }

Now I'm wondering, is there some simpler way to do it? This looks like a pretty common task, and it is very close to what -i does, except the two files are swapped: I want the output in the file with the added extension and the original unchanged. There doesn't seem to be an option for it, so do you know of some hack (bring out your secret operators, I'm asking out of curiosity so I want even the bad answers :D ) or some module that would let me write my code without having to write the explicit while(<>) loop on the data?

NB: yup I already just asked that in the CB and got some answers. Like choroba's proposition to call the translation.pl file inside a shell loop to handle the output files.

Edit: perl -pE "open STDOUT, '>>', qq<$ARGV.txt>; s/(.*)/\U$1/;" FILES works once :P (inspired by choroba's proposition)

Replies are listed 'Best First'.
Re: One to one file output idiom
by 1nickt (Canon) on Jan 15, 2020 at 18:15 UTC

    Hello Eily, I use Path::Tiny for almost all the file things.

    $ rm foo* bar* $ echo foo > foo $ echo bar > bar
    $ perl -MPath::Tiny -wnE 'sub baz {uc} chomp; my $in = path($_); $in-> +sibling("$_.new")->spew(baz($in->slurp));' foo bar
    $ cat foo.new FOO $ cat bar.new BAR

    Hope this helps!


    The way forward always starts with a minimal test.

      Is it just me or are the method names of this module really not helfpul? I had to loop spew in the dictionary because I wasn't sure of the implication, I knew of "spewing nonsense" but I didn't realize the word had such a negative connotation to it. And somehow I would understand spew to be the reverse action (getting data out of a file, not in). And I got what sibling does because I knew what to expect. Beyond that I guess it is a pretty short and "clean" (except for the confusing names) way to do what I want.

      Also, here your code works because each file contains its own name, but I think it should be path(shift), without the -n option

        Well, since "slurp" is reading a file in, it makes sense to use the opposite of "slurp" for writing. So, for me, "spew" looks completely intuitive ,-)

        you seem to see it from the view of the file. Hmmm, yes, from an OO view the opposite might make sense…

Re: One to one file output idiom
by haukex (Archbishop) on Jan 15, 2020 at 18:10 UTC

    I think your solution is fine. If you want to get fancy, you could use my Tie::Handle::Argv, which is the basis of my File::Replace::Inplace, an emulation of the -i switch (note: Perl v5.16 strongly recommended):

    use warnings; use 5.016; package Tie::Handle::FancyThingy { use parent 'Tie::Handle::Argv'; use File::Basename qw/fileparse/; sub OPEN { my ($self, $origfn) = @_; my ($fn, $dirs, $ext) = fileparse($origfn, qr/\.[^\.]+$/); my $outfn = $dirs.$fn.'.out'; print STDERR "Debug: $origfn => $outfn\n"; open ARGVOUT, '>', $outfn or die "$outfn: $!"; select ARGVOUT; return $self->SUPER::OPEN($origfn); } sub inner_close { my $self = shift; select STDOUT; return $self->SUPER::inner_close(@_); } } tie *ARGV, 'Tie::Handle::FancyThingy'; while (<>) { chomp; say translate($_); } sub translate { return "<".shift.">" }

    A more simplistic approach might be to just use the -i switch to write the "backup" (input) files to a different directory and then rename the output afterwards.

    Minor edit to code: Replaced my $ofh (output filehandle) with the more appropriate ARGVOUT.

      I think your solution is fine.
      I think so too. But it's perl, I like to know I have options :P

      And I guess somehow Tie::Handle::Argv was the module I was looking for. Or at least the module I had forgotten about that could prove to be a solution to my non-problem. Thanks :)

Re: One to one file output idiom
by choroba (Cardinal) on Jan 15, 2020 at 17:52 UTC
    Another suggestion of mine form the CB discussion:
    perl -pwe 'open STDOUT, ">", "$ARGV.txt" if $last ne $ARGV; s/.*/\U$&/ +; $last = $ARGV' *.obj

    Update: Using eof is a bit cleaner, but it still triggers at a wrong time, so it couldn't be used directly:

    perl -pwe 'open STDOUT, ">", "$ARGV.txt" unless $e; $e = ! eof; s/.*/\ +U$&/' *.obj

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      maybe try $. to catch the first line per file, like
      C:\tmp\files>perl -nE"say qq($.--$ARGV) if $.==1; $.=0 if eof " a b c 1--a 1--b 1--c

      Unfortunately I need to reset $. at the end of each file, which is kind of hacky! :/

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        Closing ARGV before it gets opened again would be the non hacky thing way to do it since it's shown in the doc, although it's a bit longer.

        Edit: choroba pointed out it was shown in the doc for eof not open

Re: One to one file output idiom
by tybalt89 (Monsignor) on Jan 15, 2020 at 22:39 UTC

    "I'm asking out of curiosity so I want even the bad answers"

    Is this bad enough ?

    perl -MPath::Tiny -n0e 'sub translate {map tr/a-z/A-Z/r, @_} path($ARG +V =~ s/in$/out/r)->spew(translate($_))' *.in

    I had to fake a translate() to test it...

Re: One to one file output idiom
by shmem (Chancellor) on Jan 15, 2020 at 20:30 UTC

    Just open a new file whenever the current $ARGV isn't the same as the remembered one (which can be undefined):

    perl -p -e '$o ne $ARGV and open STDOUT,">","$ARGV.out" and $o = $ARGV +; s/perl6/raku/;' foo bar baz

    When the input file changes ($ARGV), a new output file is opened. Of course, a file can be run instead of -e '$proggy'.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: One to one file output idiom
by LanX (Saint) on Jan 15, 2020 at 18:11 UTC
    as I suggested in the CB do the open explictely:

    I'm not good with one-liners, but this seems to do the job on Windows

    >perl -pE"open OUT => qq(>$ARGV.ext); select (OUT); s/bla/drivel/" a b + c

    C:\tmp\files>dir Datenträger in Laufwerk C: ist WINDOWS Volumeseriennummer: AE0B-3544 Verzeichnis von C:\tmp\files 15.01.2020 19:09 <DIR> . 15.01.2020 19:09 <DIR> .. 15.01.2020 18:51 12 a 15.01.2020 18:51 12 b 15.01.2020 18:51 12 c 3 Datei(en), 36 Bytes 2 Verzeichnis(se), 12.802.805.760 Bytes frei C:\tmp\files>perl -pE"open OUT => qq(>$ARGV.ext); select (OUT); s/bla/ +drivel/" a b c C:\tmp\files>type * a "bla bla" a.ext "drivel bla" b "bla bla" b.ext "drivel bla" c "bla bla" c.ext "drivel bla"

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Does it work if the files have more than one line? It seems the open overwrites the output file every time...

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        No, but >> for append does, though you must be sure the OUT-file doesn't exist already

        C:\tmp\files>perl -pE"open STDOUT=>qq(>>$ARGV.ext); s/bla/drivel/" a b + c

        There was a hack with Eskimo kiss } { in the -e but as I said I'm not experienced with one liners.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: One to one file output idiom
by LanX (Saint) on Jan 16, 2020 at 18:04 UTC
    After a lot of trying I'm convinced there is no justified one-liner solution.

    Any approach is either unclean, vulnerable or so "golfy", that a multi-line script is to be preferred.

    If I were you, I'd propose a new Perlrun option -o to change the output file the same way -i is changing the input.

    This would be very "orthogonal" and allow to combine both options in the same run.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: One to one file output idiom
by LanX (Saint) on Jan 15, 2020 at 23:50 UTC
    I was playing around (and abusing) the debugger as shell. ;)

    That's what I got so far, not perfect though

    C:\tmp\files>set TERM=dumb C:\tmp\files>perl -dE0 Loading DB routines from perl5db.pl version 1.49_05 Editor support available. Enter h or 'h h' for help, or 'perldoc perldebug' for more help. main::(-e:1): 0 DB<1> sub out { if ($.==1) { open(ARGVOUT,">","$ARGV".shift); select + (ARGVOUT) } ; $. = "0 EOF at $." if eof } DB<2> x @ARGV=<?> 0 'a' 1 'b' 2 'c' DB<3> x <> 0 '1:bla bla ' 1 '2:bla bla ' 2 '3:bla bla ' 3 '1:bla bla ' 4 '2:bla bla ' 5 '3:bla bla ' 6 '1:bla bla ' 7 '2:bla bla ' 8 '3:bla bla ' DB<4> x @ARGV=<?> 0 'a' 1 'b' 2 'c' DB<5> print s/bla/drivel/r while (<>) 1:drivel bla 2:drivel bla 3:drivel bla 1:drivel bla 2:drivel bla 3:drivel bla 1:drivel bla 2:drivel bla 3:drivel bla DB<6> x @ARGV=<?> 0 'a' 1 'b' 2 'c' DB<7> out(".ext"), print s/bla/drivel/r while (<>) DB<8> x @ARGV=<?.ext> 0 'a.ext' 1 'b.ext' 2 'c.ext' DB<9> x <> 0 '1:drivel bla ' 1 '2:drivel bla ' 2 '3:drivel bla ' 3 '1:drivel bla ' 4 '2:drivel bla ' 5 '3:drivel bla ' 6 '1:drivel bla ' 7 '2:drivel bla ' 8 '3:drivel bla ' DB<10>

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (7)
As of 2024-04-18 13:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found