Tom
Tom

Reputation: 23

Remove last character of previous line under condition

Using bash, from the following piece of F90 code, I try to remove the last "&" if the next line begins with a "AA" (note the whitespace before AA).

     F = 2 * 3 * a * b * 7&
    & * 3 * b * c&
     AA = ...

should become

     F = 2 * 3 * a * b * 7&
    & * 3 * b * c
     AA = ...

There has been a suggestion on Bash - Remove the last character of the line this before? . Based on this, I tried

perl -0pe 's/\&\n\s*AA/\nAA/g' $MYFILE

and also

sed -i 's/\&\n\s*AA/\nAA/g' $MYFILE

which does not create any errors but also does not change anything. I also tried without \s* .

Upvotes: 2

Views: 1180

Answers (3)

John1024
John1024

Reputation: 113864

Using sed

Using GNU sed:

$ sed -z 's/&\n AA/\n AA/g' file
 F = 2 * 3 * a * b * 7&
& * 3 * b * c
 AA = ...

To keep this command simple, we use the -z option to read in the whole file at once. (Technically, -z reads in NUL-separated input. Since no valid Fortran file contains a NUL, this has the effect of reading in the whole file.)

s/&\n AA/\n AA/g does the substitution that we want. Any place where the file contains & followed by newline followed by space followed by AA, this substitution removes the &.

Reading the whole file in at once is not a good approach if the file is too big to fit in memory. This should not be a problem for Fortran files.

For non-GNU sed (BSD, OSX), we need to add code to replace the -z flag:

sed 'H;1h;$!d;x;  s/&\n AA/\n AA/g' file

Using awk

$ awk '{if (/^ AA/) sub(/[&]$/, "", last); if (NR>1) print last; last=$0} END{print last}' file
 F = 2 * 3 * a * b * 7&
& * 3 * b * c
 AA = ...

How it works:

This script uses one variable last which contains the contents of the previous line. If the current line starts with AA, then we remove, if present, the final & from last. In more detail:

  • if (/^ AA/) sub(/&$/, "", last)

    If the current line starts with AA, then remove the final & from the previous line.

  • if (NR>1) print last

    If we are not on the first line, then print the previous line.

  • last=$0

    Save the current line as last.

  • END{print last}

    After we reach the end of the file, print last.

Changing files in-place

With GNU sed:

sed -zi.bak 's/&\n AA/\n AA/g' file

With other sed:

sed -i.bak 'H;1h;$!d;x;  s/&\n AA/\n AA/g' file

With recent GNU awk:

awk -i inplace '{if (/^ AA/) sub(/&$/, "", last); if (NR>1) print last; last=$0} END{print last}' file

With older awk or non-GNU awk:

awk '{if (/^ AA/) sub(/&$/, "", last); if (NR>1) print last; last=$0} END{print last}' file >file.tmp && mv file.tmp file

Upvotes: 3

ikegami
ikegami

Reputation: 385907

It becomes quite easy if you load the entire file into memory (as -0777 causes).

perl -0777pe's/&(?=\n[^\S\n]*AA)//g'

Doing it without loading the entire file into memory is done using a sliding window.

perl -ne'$p=~s/&(?=\n)// if /^\s*AA/; print $p; $p=$_; END { print $p }'

or

perl -pe'print $s if !/\s*AA/; $s = s/&\n// ? $& : ""; END { print $s }'

All three accept any number of spaces and tabs before the AA.

Usage:

perl ... file.in >file.out    # From a file
perl ... <file.in >file.out   # From STDIN
perl -i~ ... file             # "In-place", with backup
perl -i ... file              # "In-place", without backup

Upvotes: 3

potong
potong

Reputation: 58430

This might work for you (GNU sed):

sed -r 'N;s/&([^&]*\n\s*AA)/\1/;P;D' file

Read two lines into the pattern space (PS) and using pattern matching remove the & from the first line if the second line begins (less whitespace) with AA.

N.B. this caters for the second line also containing an & etc, etc...

Upvotes: 0

Related Questions