Minsec
Minsec

Reputation: 35

Delete range of lines when line number of known or not in unix using head and tail?

This is my sample file.

I want to do this.

  1. I have fixed requirement to delete 2nd and 3rd line keeping the 1st line.
  2. From the bottom, I want to delete above 2 lines excluding last line, as I wouldn't know what my last line number is as it depends on file.

Once I delete my 2nd and 3rd line 4th line should ideally come at 2nd and so on, same for a bottom after delete.

I want to use head/tail command and modify the existing file only. as Changes to write back to the same file.

Sample file text format.

Input File

>     This is First Line
>     Delete Delete Delete This Line
>     Delete Delete Delete This Line
>     ..
>     ..
>     ..
>     ..
>     Delete Delete Delete This Line
>     Delete Delete Delete This Line
>     This is Last Line, should not be deleted It could be come at any line 

number (variable)

Output file (same file modified)

This is First Line
..
..
..
..
This is Last Line, should not be deleted It could be come at any line number (variable)

Edit - Because of compatibility issues on Unix (Using HP Unix on ksh shell) I want to implement this using head/tail/awk. not sed.

Upvotes: 1

Views: 732

Answers (6)

Dudi Boy
Dudi Boy

Reputation: 4900

I enjoyed this task and wrote awk script for more scaleable case (huge files).

Reading/scanning the input file once (no need to know line count), not storing the whole file in memory.

script.awk

BEGIN { range = 3}    # define sliding window range
{lines[NR] = $0}      # capture each line in array
NR == 1 {print}       # print 1st line
NR > range * 2{       # for lines in sliding window range bottom
    print lines[NR - range]; # print sliding window top line
    delete lines[NR - range];   # delete sliding window top line
}
END {print}           # print last line

running:

awk -f script.awk input.txt

input.txt

line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8
line 9
line 10

output:

line 1
line 4
line 5
line 6
line 7
line 10

Upvotes: 0

Paul
Paul

Reputation: 442

  awk '{printf "%d\t%s\n", NR, $0}' < file | sed '2,3d;N;$!P;D' file

The awk here serves the purpose of providing line numbers and then passing the output to the sed which uses the line numbers to do the required operations.

%d : Used to print the numbers. You can also use '%i'

'\t' : used to place a tab between the number and string

%s : to print the string of charaters

'\n' : To create a new line

NR : to print lines numbers starting from 1

For sed N: Read/append the next line of input into the pattern space.

$! : is for not deleting the last line

D : This is used when pattern space contains no new lines normal and start a new cycle as if the d command was issued. Otherwise, delete text in the pattern space up to the specified lines, and restart cycle with the resultant pattern space, without reading a new line of input.

P : Print up to the first embedded newline of the current pattern space.This prints the lines after removing the subjected lines.

Upvotes: 0

RavinderSingh13
RavinderSingh13

Reputation: 133640

Adding solution as per OP's request to make it genuine solution.

Approach: In this solution OP could provide lines from starting point and from ending point of any Input_file and those lines will be skipped.

What code will do: I have written code in that way it will generate an awk code as per your given lines to be skipped then and will run it too.

cat print_lines.ksh
start_line="2,3"
end_line="2,3"
total_lines=$(wc -l<Input_file)

awk -v len="$total_lines" -v OFS="||" -v s1="'" -v start="$start_line" -v end="$end_line" -v lines=$(wc -l <Input_file) '
BEGIN{
  num_start=split(start, a,",");
  num_end=split(end, b,",");
  for(i=1;i<=num_start;i++){
    val=val?val OFS "FNR=="a[i]:"FNR=="a[i]};
  for(j=1;j<=num_end;j++){
    b[j]=b[j]>1?len-(b[j]-1):b[j];
    val=val?val OFS "FNR=="b[j]:"FNR=="b[j]};
print "awk " s1 val "{next} 1" s1" Input_file"}
' | sh

Change Input_file name to your actual file name and let me know how it goes then.


Following awk may help you in same(Since I don't have Hp system so didn't test it).

awk -v lines=$(wc -l <Input_file) 'FNR==2 || FNR==3 || FNR==(lines-1) || FNR==(lines-2){next} 1'  Input_file

EDIT: Adding non-one liner form of solution too now.

awk -v lines=$(wc -l <Input_file) '
FNR==2 || FNR==3 || FNR==(lines-1) || FNR==(lines-2){
next}
1
'  Input_file

Upvotes: 1

sjsam
sjsam

Reputation: 21965

If you wish to have some flexibility a ksh script approach may work, though little expensive in terms of resources :

#!/bin/ksh
[ -f "$1" ] || echo "Input is not a file" || exit 1
total=$(wc -l "$1" | cut -d' ' -f1 )
echo "How many lines to delete at the end?"
read no
[ -z "$no" ] && echo "Not sure how many lines to delete, aborting" && exit 1
sed "2,3d;$((total-no)),$((total-1))d" "$1" >tempfile && mv tempfile "$1"

And feed the file as argument to the script.

Notes

  • This deletes second and third lines.
  • Plus no number of lines from last excluding last as read from user.

Note: My ksh version is 93u+ 2012-08-01

Upvotes: 0

Mark Setchell
Mark Setchell

Reputation: 207668

Perl suggestion... read whole file into array @L, get index of last line. Delete 2nd last, 3rd last, 3rd and 2nd line. Print what's left.

perl -e '@L=<>; $p=$#L; delete $L[$p-1]; delete $L[$p-2]; delete $L[2]; delete $L[1]; print @L' file.txt

Or, maybe a little more succinctly with splice:

perl -e '@L=<>; splice @L,1,2; splice @L,$#L-2,2; print @L' file.txt

Upvotes: 0

RomanPerekhrest
RomanPerekhrest

Reputation: 92874

wc + sed solution:

len=$(wc -l inpfile | cut -d' ' -f1)
sed "$(echo "$((len-2)),$((len-1))")d; 2,3d" inpfile > tmp_f && mv tmp_f inpfile

$ cat inputfile
>     This is First Line
>     ..
>     ..
>     ..
>     ..
>     This is Last Line, should not be deleted It could be come at any line

Upvotes: 0

Related Questions