A generic solution for masking out a particular pattern of lines from a file:
#!/bin/sh
# The pattern is given on the command line.
pattern=$1
# The period is simply the length of the pattern.
period=${#pattern}
# Use bc to convert the binary pattern to an integer.
mask=$( printf 'ibase=2; %s\n' "$pattern" | bc )
awk -v mask="$mask" -v period="$period" '
BEGIN { p = lshift(1, period-1) }
and(rshift(p, (FNR-1) % period), mask)'
This relies on awk
implementing the non-standard functions and()
(bitwise AND), rshift()
and lshift()
(bitwise right and left shift), which both GNU awk
and some BSD implementations of awk
does, but not mawk
.
This takes a pattern, which is a binary number representing both the cyclic period and what lines within each period should be kept or masked out. A 1
means "keep" and a 0
means "delete".
For example: The pattern of line that should be applied in your question is 11100
, which means "for each set of five lines, keep the first three and delete the others".
Using 01001000
would delete all but the 2nd and 5th lines in every 8 lines.
The awk
program could also be written without the BEGIN
block as
and(lshift(1, (period-1) - (FNR-1) % period), mask)
Left-shifting 1 by (period-1) - (FNR-1) % period
positions is the same as calculating 2 to that power, but I'm using lshift()
since awk
does its arithmetics using floating point operations rather than in exact integer arithmetics.
Since the code relies on the binary representation of the pattern, very long patterns may not work well.
Testing:
Removing the lines you want to remove:
$ sh script.sh 11100 <file
1st line (keep)
2nd line (keep)
3rd line (keep)
6th (keep)
7nth (keep)
8th lines (keep)
11th (keep)
12th (keep)
13th (keep)
Inverting the pattern:
$ sh script.sh 00011 <file
4rth lines (delete)
5th (del)
9th (del)
10th (del)
14th (del)
15th (del)
print lines 1,2,3 out of each 5 lines
for ex:seq 15 | awk 'BEGIN { a[1] a[2] a[3] }; NR % 5 in a'
andseq 15 | sed -n 'p;n;p;n;p;n;n'
sed
version above might be faster than theawk
one for large files