jonah_w
jonah_w

Reputation: 1032

This perl oneliner that handles multiple lines doesn't work

I have this string as follows(which is the content of the test1.txt file):

one
1
</>
two
2
</>

I want it to become a new string like this:

one
1
</>
1
one
</>
two
2
</>
2
two
</>

I use the following perl oneliner to do just that.

perl -pi.bak -e 's#((.*)\n(.*)\n<\/>)#$1\n$3\n$2\n<\/>#g' "test1.txt"

but it did nothing to the test1.txt file.

Update:I like all three answers. They all provide some brilliantly useful information. I'm not sure in this situation which answer to accept…

Upvotes: 1

Views: 318

Answers (3)

ikegami
ikegami

Reputation: 385506

You are reading a line at a time and matching against that one line, so your pattern can't possibly match.

The simple solution is to read the entire file as one line by using -0777 (which sets $/ to undef).

perl -i.bak -0777pe's#((.*)\n(.*)\n<\/>)#$1\n$3\n$2\n<\/>#g' test1.txt

Upvotes: 3

blhsing
blhsing

Reputation: 106445

The -p option assigns the input to the $_ variable on a per-line basis, so your regex, which matches multiple lines, would not find a match. You should read the entire file before you try to apply the regex instead:

perl -i.bak -e 'undef $/;$_=<>;s#((.*)\n(.*)\n</>)#$1\n$3\n$2\n</>#g;print' "test1.txt"

Sample run in command line:

# perl -e 'undef $/;$_=<>;s#((.*)\n(.*)\n</>)#$1\n$3\n$2\n</>#g;print'<<EOF
> one
> 1
> </>
> two
> 2
> </>
> EOF
one
1
</>
1
one
</>
two
2
</>
2
two
</>

Upvotes: 2

Stefan Becker
Stefan Becker

Reputation: 5952

Di-section of your one liner:

$ perl -MO=Deparse -pi.bak -e 's#((.*)\n(.*)\n<\/>)#$1\n$3\n$2\n<\/>#g' test.txt
BEGIN { $^I = ".bak"; }
LINE: while (defined($_ = readline ARGV)) {
    s[((.*)\n(.*)\n<\/>)][$1\n$3\n$2\n</>]g;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

i.e. your processing loop is line-based, whereas your regex wants to match multiple lines.

NOTE: my solutions use the more generic filter approach STDIN to STDOUT, not -i.bak.

You'll either have to slurp the file to memory and then apply the substitution...

#!/usr/bin/perl
use warnings;
use strict;
use open qw(:encoding(UTF-8) :std);

my $input;
{
    local $/;
    $input = <STDIN>;
}

$input =~ s,((.*)\n(.*)\n<\/>),$1\n$3\n$2\n<\/>,g;

print $input;

exit 0;

... or use section detection with bi-stable range operator in scalar context:

#!/usr/bin/perl
use warnings;
use strict;
use open qw(:encoding(UTF-8) :std);

my @section;
while (<STDIN>) {
    if (/^\w+$/../^<\/>$/) {
        push(@section, $_);
    }

    print;

    # End of section reached
    if (/^<\/>$/) {
        # swivel lines around for desired output result...
        print @section[1, 0, 2];
        @section = ();
    }
}

exit 0;

Which approach is more appropriate depends on your real-life input file or additional processing requirements.

Test run:

$ perl dummy.pl <dummy.txt
one
1
</>
1
one
</>
two
2
</>
2
two
</>

UPDATE if "no redirection" is an absolute requirement, you can replace the <STDIN> with <> to process the files on the command line, i.e.

my $input = <>;

or

while (<>) {

and on the command line:

$ perl -i.bak dummy.pl test1.txt

Upvotes: 2

Related Questions