Add a line after last matched expression

I thought this would be easy to Google, but I am having trouble getting a clean result that I can understand.

I simply want to insert the the line:
My Network 192.168.1.1

After the last line that begins with ACL localnet

Try:

#!/bin/ksh
ed - in.addafter <<EOF
?^ACL localnet?a
My Network 192.168.1.1 
.       
w       
EOF

Is ed - intended to be ed -s ?

I'm not sure if "After the last line that begins with ACL localnet" means that there is only one match and that it occurs on the last line of the file, or if it means that there may be multiple matches. If the latter, I would suggest moving to line 1 before beginning the backwards search. As it is, if the final match is the last line, and if there is an earlier match, the earlier match will "win".

Regards,
Alister

Don't use it.

lem@biggy:/tmp$ line=$(sed -n '/^ACL localnet/ =' file |tail -n1)
lem@biggy:/tmp$ (( ${line:-0} == 0 )) || { rm file ; sed  "$line a\
My Network 192.168.1.1" > file ; } <file

I said don't use it.
--
Bye

Yes. Thank you for catching this. I thought I typed the "s", but it isn't in my test script. :mad:

The POSIX standard says that a "-" operand produces unspecified results. On some systems, "-" is a synonym for "-s"; on other systems "-" is a file operand referring to ed's standard input. On OS X (the OS on my primary machine), "-" and "-s" are synonyms so I didn't notice the typo when I was testing.

You are also correct in noting that the current line is set to the last line in the buffer when a file is loaded into the buffer by the e command, the E command, or by naming a file operand on the command line.

Note that the following script could just use the command 1 (digit one; not letter el), but that would print the first line. Substituting the start of line 1 to nothing doesn't change anything on the line, but changes the current line to the 1st line in the buffer without printing anything.

#!/bin/ksh
ed - in.addafter <<EOF
1s/^//
?^ACL localnet?a
My Network 192.168.1.1
.
w
q
EOF

Note that although I specified /bin/ksh for this script, any shell should work. I tested it with ksh, bash, csh, sh, and tcsh.

I mean that there are multiple lines that begin with ACL localnet and I want to insert a line after the last instance.

An alternate approach:

awk -v cache=/tmp/PID$$.cache '
    function print_cache(       rec )
    {
        if( ! buffer )
            return;

        close( cache );
        while( (getline rec <cache ) > 0 )
            print rec;
        close( cache );
    }

    /^ACL localnet/ {
        print_cache();
        print;
        buffer = 1;
        next;
    }

    {
        if( buffer )
            print >cache;
        else
            print;
        next;
    }

    END {
        if( buffer )
            print "My Network 192.168.1.1 "
        print_cache( );
        system( "rm " cache );
    }
'  input-file >output-file

Doesn't insert the line if the pattern isn't found.

sed '${/^ACL localnet/{s/.*/&\nMy Network 192.168.1.1/}}' infile

This will work only if the last line of the file begins with "ACL localnet". :slight_smile:
But in this case an echo "My Network 192.168.1.1" >> file would be enough.

The OP wants to insert a line after the last occurrence, in the file, of a line beginning with "ACL localnet". This last occurrence may be everywhere in the file.

The solution I posted above works well (I wrote it for bash), but I suggested not to use it because of the trick of deleting the file before rewriting it and having it only in RAM for a while, which would end up in a loss of data in case of a system crash.

However:

lem@biggy:/tmp$ line=$(sed -n '/^ACL localnet/ =' file |tail -n1)
lem@biggy:/tmp$ (( ${line:-0} == 0 )) || sed  "$line a\
My Network 192.168.1.1" <file >newfile

(after "$line a\" press ENTER)
is perfectly safe. :slight_smile:
--
Bye

Any one liners?

Given what it is you want to do, I'd say not.

Why is it important to have it all smashed on one line?

Most anything can be written as a one liner, but IMHO they become unmanageable very quickly which opens them up for errors.

Here is the suggestion I posted earlier, as a one (huge) liner:

 function print_cache( rec ) { if( ! buffer ) return; close( cache ); while( (getline rec <cache ) > 0 ) print rec; close( cache ); } /^ACL localnet/ { print_cache(); print; buffer = 1; next; } { if( buffer ) print >cache; else print; next; } END { if( buffer ) print "My Network 192.168.1.1 "; print_cache( ); system( "rm " cache ); } ' input >output

I'm not sure this will work under every circumstance, but it's a one liner, give it a try:

sed "$(awk '/^ACL localnet/ {n=NR} END{print  n}' infile)  a \My Network 192.168.1.1" infile
|                                                            ^---  edited after Don Cragun's below post    

On OS X this yields:

sed: 1: "14  a My Network 192.16 ...": command a expects \ followed by text

Put a \ between the "a" and "My ..." - it worked without on my linux but the standard is "a \"

On OS X, adding a slash gives the error:

sed: 1: "14  a \My Network 192.1 ...": extra characters after \ at the end of a command

The standard says:

[1addr]a\
text	    Write text to standard output as described previously.

The following works, but it is now a two-liner instead of a one-liner:

sed "$(awk '/^ACL localnet/ {n=NR} END{print  n}' infile)  a \\
My Network 192.168.1.1" infile

Note that two backslashes are needed due to the shell's escape processing in double quoted strings.

As a quick answer i would say :

sed + back references + sed commands

should be a good part of a working and simple.

I'll try to give you a more comprehensive answer if i found some time for that.

EDIT :

Sorry i just did not notice there were more than one page... Forget about this answer (some comprehensive answers had already been given.