Nico Rittner
Nico Rittner

Reputation: 199

gnu sed - delete lines between first X and last Y lines

the goal is to shorten a large text:
delete everything between the first X lines and the last Y lines
and maybe insert a line like "file truncated to XY lines..." in the middle.
i played around and achieved this with weird redirections ( Pipe output to two different commands ), subshells, tee and multiple sed invocations and i wonder if

sed -e '10q'

and

sed -e :a -e '$q;N;11,$D;ba'

can be simplified by merging both into a single sed call.

thanks in advance

Upvotes: 2

Views: 218

Answers (6)

nisetama
nisetama

Reputation: 8893

You can also use sed -u 5q (with GNU sed) as an unbuffered alternative to head -n5:

$ seq 99|(sed -u 5q;echo ...;tail -n5)
1
2
3
4
5
...
95
96
97
98
99

Upvotes: 1

Thor
Thor

Reputation: 47109

Here is a sed alternative that does not require knowledge of file length.

You can insert a modified "head" expression into the sliding loop of your "tail" expression. E.g.:

sed ':a; 10s/$/\n...File truncated.../p; $q; N; 11,$D; ba'

Note that if the ranges overlap there will be duplicate lines in the output.

Example:

seq 30 | sed ':a; 10s/$/\n...File truncated.../p; $q; N; 11,$D; ba'

Output:

1
2
3
4
5
6
7
8
9
10
...File truncated...
20
21
22
23
24
25
26
27
28
29
30

Here is a commented multi-line version to explain what is going on:

:a                                   # loop label
10s/$/\n...File truncated.../p       # on line 10, replace end of pattern space
$q                                   # quit here when on the last line
N                                    # read next line into pattern space
11,$D                                # from line 11 to end, delete the first line of pattern space
ba                                   # goto :a

Upvotes: 0

potong
potong

Reputation: 58430

This might work for you (GNU sed):

sed '1,5b;:a;N;s/\n/&/8;Ta;$!D;s/[^\n]*\n//;i\*** truncated file ***' file

Here x=5 and Y=8.

N.B. This leaves short files unadulterated.

Upvotes: 0

glenn jackman
glenn jackman

Reputation: 246847

You can do it through a magical incantation of tee, process substitutions, and stdio redirections:

x=5 y=8
seq 20 | { 
    tee >(tail -n $y >&2) \
        >({ head -n $x; echo "..."; } >&2) >/dev/null 
} 2>&1
1
2
3
4
5
...
13
14
15
16
17
18
19
20

This version is more sequential and the output should be consistent:

x=5 y=8
seq 20 | {
    { 
        # read and print the first X lines to stderr
        while ((x-- > 0)); do 
            IFS= read -r line 
            echo "$line" 
        done >&2
        echo "..." >&2  
        # send the rest of the stream on stdout
        cat - 
    } |
    # print the last Y lines to stderr, other lines will be discarded
    tail -n $y >&2
} 2>&1

Upvotes: 1

Mark Setchell
Mark Setchell

Reputation: 207475

Use head and tail:

(head -$X infile; echo Truncated; tail -$Y infile) > outfile

Or awk:

awk -v x=$x -v y=$y '{a[++i]=$0}END{for(j=1;j<=x;j++)print a[j];print "Truncated"; for(j=i-y;j<=i;j++)print a[j]}' yourfile

Or you can use tee like this with process substitution if, as you say, input is coming from a pipe:

yourcommand | tee >(head -$x > p1) | tail -$y > p2 ; cat p[12]

Upvotes: 2

NeronLeVelu
NeronLeVelu

Reputation: 10039

if you know the length of the file

EndStart=$(( ${FileLen} - ${Y} + 1))
sed -n "1,${X} p
${X} a\\
 --- Truncated part ---
${EndStart},$ p" YourFile

Upvotes: 0

Related Questions