sed command working on command line but not in perl script

前端 未结 1 756
渐次进展
渐次进展 2020-12-22 08:26

I have a file in which i have to replace all the words like $xyz and for them i have to substitutions like these:

$xyz with ${xyz}.
$abc_xbs with ${abc_xbc}         


        
相关标签:
1条回答
  • 2020-12-22 09:01

    In a Perl script you need valid Perl language, just like you need valid C text in a C program. In the terminal sed.. is understood and run by the shell as a command but in a Perl program it is just a bunch of words, and that line sed.. isn't valid Perl.

    You would need this inside qx() (backticks) or system() so that it is run as an external command. Then you'd indeed need "some backslashes," which is where things get a bit picky.

    But why run a sed command from a Perl script? Do the job with Perl

    use warnings;
    use strict;
    use File::Copy 'move';
    
    my $file     = 'filename';
    my $out_file = 'new_' . $file;
    
    open my $fh,     '<', $file     or die "Can't open $file: $!";
    open my $fh_out, '>', $out_file or die "Can't open $out_file: $!"; 
    
    while (<$fh>) 
    {
        s/\$( [^{] [a-z_]* )/\${$1}/gix;
        print $fh_out $_;
    }
    close $fh_out;
    close $fh;
    
    move $out_file, $file or die "Can't move $out_file to $file: $!";
    

    The regex uses a negated character class, [^...], to match any character other than { following $, thus excluding already braced words. Then it matches a sequence of letters or underscore, as in the question (possibly none, since the first non-{ already provides at least one).

    With 5.14+ you can use the non-destructive /r modifier

    print $fh_out s/\$([^{][a-z_]*)/\${$1}/gir;
    

    with which the changed string is returned (and original is unchanged), right for the print.

    The output file, in the end moved over the original, should be made using File::Temp. Overwriting the original this way changes $file's inode number; if that's a concern see this post for example, for how to update the original inode.

    A one-liner (command-line) version, to readily test

    perl -wpe's/\$([^{][a-z_]*)/\${$1}/gi' file
    

    This only prints to console. To change the original add -i (in-place), or -i.bak to keep backup.


    A reasonable question of "Isn't there a shorter way" came up.

    Here is one, using the handy Path::Tiny for a file that isn't huge so we can read it into a string.

    use warnings;
    use strict; 
    use Path::Tiny;
    
    my $file     = 'filename';
    my $out_file = 'new_' . $file;
    
    my $new_content = path($file)->slurp =~ s/\$([^{][a-z_]*)/\${$1}/gir;
    
    path($file)->spew( $new_content );
    

    The first line reads the file into a string, on which the replacement runs; the changed text is returned and assigned to a variable. Then that variable with new text is written out over the original.

    The two lines can be squeezed into one, by putting the expression from the first instead of the variable in the second. But opening the same file twice in one (complex) statement isn't exactly solid practice and I wouldn't recommend such code.

    However, since module's version 0.077 you can nicely do

    path($file)->edit_lines( sub { s/\$([^{][a-z_]*)/\${$1}/gi } );
    

    or use edit to slurp the file into a string and apply the callback to it.

    So this cuts it to one nice line after all.

    I'd like to add that shaving off lines of code mostly isn't worth the effort while it sure can lead to trouble if it disturbs the focus on the code structure and correctness even a bit. However, Path::Tiny is a good module and this is legitimate, while it does shorten things quite a bit.

    0 讨论(0)
提交回复
热议问题