Bubnoff
Bubnoff

Reputation: 4097

sed: flexible template w/ line number constraint

Problem I need to insert text of arbitrary length ( # of lines ) into a template while maintaining an exact number of total lines.

Sample source data file:

You have a hold available for pickup as of 2012-01-13:
Title: Really Long Test Title Regarding Random Gibberish. Volume 1, A-B, United States
 and affiliated territories, United Nations, countries of the world
Author: Barrel Roll Morton
Title: How to Compromise Free Speech Using Everyday Tools. Volume XXVI
Author: Lamar Smith
#end-of-record
You have a hold available for pickup as of 2012-01-13:
Title: Selling Out Democracy For Fun and Profit. Volume 1, A-B, United States
Author: Lamar Smith
Copy: 12
#end-of-record

Sample Template ( simplified for brevity ):

<%CUST-NAME%>
<%CUST-ADDR%>
<%CUST-CTY-ZIP%>

<%TITLES GO HERE%>

<%STORE-NAME%>
<%STORE-ADDR%>
<%STORE-CTY-ZIP%>

At this point I use bash's 'mapfile' to load the source file record by record using the /^#end-of-file/ regex ...so far so good. Then I pull predictable aspects of each record according to the line on which they occur, then process the info using a series of sed search replace statements.

The Hang-Up So the problem is the unknown number of 'title' records that could occur. How can I accommodate an unknown number of titles and always have output of precisely 65 lines?

Given that title records always occur starting on line 8, I can pull the titles easily with:

 sed -n '8,$p' test-match.txt

However, how can I insert this within an allotted space, ex, between <%CUST-CTY-ZIP%> and <%STORE-NAME%> without pushing the store info out of place in the template?

My idea so far:

-first send the customer info through:
Ex.

sed 's/<%CUST-NAME%>/Benedict Arnold/' template.txt

-Append title records ???

-Then the store/location info

sed 's/<%STORE-NAME%>/Smith's House of Greasy Palms/' template.txt

I have code and functions for this stuff if interested but this post is 'windy' as it is. Just need help with inserting the title records while maintaining position of following text and maintaining total line number of 65.*

UPDATE I've decided to change tactics. I'm going to create place holders in the template for all available lines between customer and store info --- then:

Eventually, I plan to invest some time looking closer at Triplee's suggestion regarding Perl. The Perl way really does look simpler and easier to maintain if I'm going to be stuck with this project long term.

Upvotes: 0

Views: 191

Answers (3)

tripleee
tripleee

Reputation: 189679

Here's a quick proof of concept using Perl formats. If you are unfamiliar with Perl, I guess you will need some additional help with how to get the values from two different files, but it's quite doable, of course. Here, the data is simply embedded into the script itself.

I set the $titles format to 5 lines instead of the proper value (58 or something?) in order to make this easier to try out in a terminal window, and to demonstrate that the output is indeed truncated when it is longer than the allocated space.

#!/usr/bin/perl                                                                 

use strict;
use warnings;

use vars (qw($cust_name $cust_addr $cust_cty_zip $titles                        
    $store_name $store_addr $store_cty_zip));

my $fmtline = '@' . '<' x 78;
my $titlefmtline = '^' . '<' x 78;
my $empty = '';
my $fmt = join ("\n$fmtline\n", 'format STDOUT = ',
                '$cust_name', '$cust_addr', '$cust_cty_zip', '$empty') .
    ("\n$titlefmtline\n" . '$titles') x 5 . #58                                 
    join ("\n$fmtline\n", '', '$empty',
          '$store_name', '$store_addr', '$store_cty_zip');
#print $fmt;                                                                    
eval "$fmt\n.\n";

titles = <<____HERE;
Title: Really Long Test Title Regarding Random Gibberish. Volume 1, A-B, United States
 and affiliated territories, United Nations, countries of the world
Author: Barrel Roll Morton
Title: How to Compromise Free Speech Using Everyday Tools. Volume XXVI
Author: Lamar Smith
____HERE
# Preserve line breaks -- ^<< will fill lines, but preserves line breaks on \r  
$titles =~ s/\n/\r\n/g;

while (<DATA>) {
    chomp;
    ($cust_name, $cust_addr, $cust_cty_zip, $store_name, $store_addr, $store_cty_zip)
        = split (",");
    write STDOUT;
}
__END__
Charlie Bravo,23 Alpa St,Delta ND 12345,Spamazon,98 Spamway,Atlanta GA 98765

The use of $empty to get an empty line is pretty ugly, but I wanted to keep the format as regular as possible. I'm sure it could be avoided, but at the cost of additional code complexity IMHO.

If you are unfamiliar with Perl, the use strict is a complication, but a practical necessity; it requires you to declare your variables either with use vars or my. It is a best practice which helps immensely if you try to make changes to the script.

Here documents with <<HERE work like in shell scripts; it allows you to create a multi-line string easily.

The x operator is for repetition; 'string' x 3 is 'stringstringstring' and ("list") x 3 is ("list" "list" "list"). The dot operator is string concatenation; that is, "foo" . "bar" is "foobar".

Finally, the DATA filehandle allows you to put arbitrary data in the script file itself after the __END__ token which signals the end of the program code. For reading from standard input, use <> instead of <DATA>.

Upvotes: 1

Dennis Williamson
Dennis Williamson

Reputation: 360355

This will give you five lines of output regardless of the number of lines in titles.txt:

sed -n '$s/$/\n\n\n\n\n/;8,$p' test-match.txt | head -n 5

Another version:

sed -n '8,$N; ${s/$/\n\n\n\n\n/;s/\(\([^\n]*\n\)\{4\}\).*/\1/p}' test-match.txt

Use one less than the number of lines you want (4 in this example will cause 5 lines of output).

Upvotes: 1

potong
potong

Reputation: 58483

This might work for you:

cat <<! >titles.txt
> 1
> 2
> 3
> 4
> 5
> 6
> 7
> Title 1
> Title 2   
> Title 3
> Title 4
> Title 5
> Title 6
> !
cat <<! >template.txt
> <%CUST-NAME%>
> <%CUST-ADDR%>
> <%CUST-CTY-ZIP%>
> 
> <%TITLES GO HERE%>
> 
> <%STORE-NAME%>
> <%STORE-ADDR%>
> <%STORE-CTY-ZIP%>
> !
sed '1,7d;:a;$!{N;ba};:b;G;s/\n[^\n]*//5g;tc;bb;:c;s/\n/\\n/g;s|.*|/<%TITLES GO HERE%>/c\\&|' titles.txt | 
sed -f - template.txt
<%CUST-NAME%>
<%CUST-ADDR%>
<%CUST-CTY-ZIP%>

Title 1
Title 2
Title 3
Title 4
Title 5

<%STORE-NAME%>
<%STORE-ADDR%>
<%STORE-CTY-ZIP%>

This pads/squeezes the titles to 5 lines (s/\n[^\n]*//5g) if you want fewer or more change the 5 to the number desired.

Upvotes: 1

Related Questions