Remove lines from a file

From Rosetta Code
Revision as of 01:37, 23 August 2012 by rosettacode>Fwend (→‎{{header|D}}: added D)
Remove lines from a file is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The task is to demonstrate how to remove a specific line or a number of lines from a file. This should be implemented as a routine that takes three parameters (filename, starting line, and the number of lines to be removed). For the purpose of this task, line numbers and the number of lines start at one, so to remove the first two lines from the file foobar.txt, the parameters should be: foobar.txt, 1, 2

Empty lines are considered and should still be counted, and if the specified line is empty, it should still be removed. An appropriate message should appear if an attempt is made to remove lines beyond the end of the file.

AutoHotkey

<lang AHK>RemoveLines(filename, startingLine, numOfLines){

      Loop, Read, %filename%
              if ( A_Index < StartingLine )
                      || ( A_Index >= StartingLine + numOfLines )
                              ret .= "`r`n" . A_LoopReadLine
      FileDelete, % FileName
      FileAppend, % SubStr(ret, 3), % FileName

}

SetWorkingDir, % A_ScriptDir RemoveLines("test.txt", 4, 3)</lang>

Output

with test.txt starting as

1
2
3
4
5
6
7
8

Running the code it is now

1
2
3
7
8

C++

<lang cpp>#include <fstream>

  1. include <iostream>
  2. include <string>
  3. include <cstdlib>
  4. include <list>

void deleteLines( const std::string & , int , int ) ;

int main( int argc, char * argv[ ] ) {

  if ( argc != 4 ) {
     std::cerr << "Error! Invoke with <deletelines filename startline skipnumber>!\n" ;
     return 1 ;
  }
  std::string filename( argv[ 1 ] ) ;
  int startfrom = atoi( argv[ 2 ] ) ;
  int howmany = atoi( argv[ 3 ] ) ;
  deleteLines ( filename , startfrom , howmany ) ;
  return 0 ;

}

void deleteLines( const std::string & filename , int start , int skip ) {

  std::ifstream infile( filename.c_str( ) , std::ios::in ) ;
  if ( infile.is_open( ) ) {
     std::string line ;
     std::list<std::string> filelines ;
     while ( infile ) {

getline( infile , line ) ; filelines.push_back( line ) ;

     }
     infile.close( ) ;
     if ( start > filelines.size( ) ) {

std::cerr << "Error! Starting to delete lines past the end of the file!\n" ; return ;

     }
     if ( ( start + skip ) > filelines.size( ) ) {

std::cerr << "Error! Trying to delete lines past the end of the file!\n" ; return ;

     }
     std::list<std::string>::iterator deletebegin = filelines.begin( ) , deleteend ;
     for ( int i = 1 ; i < start ; i++ )

deletebegin++ ;

     deleteend = deletebegin ;
     for( int i = 0 ; i < skip ; i++ )

deleteend++ ;

     filelines.erase( deletebegin , deleteend ) ;
     std::ofstream outfile( filename.c_str( ) , std::ios::out | std::ios::trunc ) ;
     if ( outfile.is_open( ) ) {

for ( std::list<std::string>::const_iterator sli = filelines.begin( ) ; sli != filelines.end( ) ; sli++ ) outfile << *sli << "\n" ;

     }
     outfile.close( ) ;
  }
  else {
     std::cerr << "Error! Could not find file " << filename << " !\n" ;
     return ;
  }

}</lang>

D

<lang d>import std.stdio, std.file, std.string;

void main() {

   deleteLines("deleteline_test.txt", 1, 2);

}

void deleteLines(string name, int start, int num) in {

   assert(start > 0, "Line counting must start at 1");

} body {

   start--;
   if (!exists(name) || !isFile(name))
       throw new FileException("File not found");
   auto lines = readText(name).splitLines();
   if (lines.length < start + num)
       throw new Exception("Can't delete lines past the end of file!");
   auto f = File(name, "w");
   foreach (int i, line; lines) {
       if (start > i || i >= start + num)
           f.writeln(line);
   }

}</lang>

Go

<lang go>package main

import (

   "bytes"
   "errors"
   "fmt"
   "io/ioutil"
   "os"

)

func main() {

   if err := removeLines("foobar.txt", 1, 2); err != nil {
       fmt.Println(err)
   }

}

func removeLines(fn string, start, n int) (err error) {

   if start < 1 {
       return errors.New("invalid request.  line numbers start at 1.")
   }
   if n < 0 {
       return errors.New("invalid request.  negative number to remove.")
   }
   var f *os.File
   if f, err = os.OpenFile(fn, os.O_RDWR, 0); err != nil {
       return
   }
   defer func() {
       if cErr := f.Close(); err == nil {
           err = cErr
       }
   }()
   var b []byte
   if b, err = ioutil.ReadAll(f); err != nil {
       return
   }
   cut, ok := skip(b, start-1)
   if !ok {
       return fmt.Errorf("less than %d lines", start)
   }
   if n == 0 {
       return nil
   }
   tail, ok := skip(cut, n)
   if !ok {
       return fmt.Errorf("less than %d lines after line %d", n, start)
   }
   t := int64(len(b) - len(cut))
   if err = f.Truncate(t); err != nil {
       return
   }
   if len(tail) > 0 {
       _, err = f.WriteAt(tail, t)
   }
   return

}

func skip(b []byte, n int) ([]byte, bool) {

   for ; n > 0; n-- {
       if len(b) == 0 {
           return nil, false
       }
       x := bytes.IndexByte(b, '\n')
       if x < 0 {
           x = len(b)
       } else {
           x++
       }
       b = b[x:]
   }
   return b, true

}</lang>

Haskell

<lang haskell>import System.Environment (getArgs)

main = getArgs >>= (\[a, b, c] ->

           do contents <- fmap lines $ readFile a
              let b1 = read b :: Int
                  c1 = read c :: Int
              putStr $ unlines $ concat [take (b1 - 1) contents, drop c1 $ drop b1 contents]
      )</lang>

Icon and Unicon

<lang Icon>procedure main() # remove lines

  removelines("foo.bar",3,2) | stop("Failed to remove lines.")

end

procedure removelines(fn,start,skip)

  f := open(fn,"r") | fail                  # open to read
  every put(F := [],|read(f))               # file to list
  close(f)
  if *F < start-1+skip then fail
  every F[start - 1 + (1 to skip)] := &null # mark delete
  
  f := open(fn,"w") | fail                  # open to rewrite
  every write(\!F)                          # write non-nulls
  close(f)
  return                                    # done

end</lang>

J

<lang j>removeLines=:4 :0

 'count start'=. x
 file=. boxxopen y
 lines=. <;.2]1!:1 file
 (;lines {~ <<< (start-1)+i.count) 1!:2 file

)</lang>

Thus: <lang bash>$ cal >cal.txt $ cat cal.txt

     July 2011

Su Mo Tu We Th Fr Sa

               1  2
3  4  5  6  7  8  9

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31</lang>

<lang j> 2 1 removeLines 'cal.txt'</lang>

<lang bash>$ cat cal.txt

               1  2
3  4  5  6  7  8  9

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31</lang>

Note that this code assumes that the last character in the file is the line end character.

Liberty BASIC

It's always a bit dangerous to experiment with code that alters files. Un-rem the 'kill' to remove the temp file and the next line so the file is renamed to the original! <lang lb> call removeLines "foobar.txt", 1, 2 end

sub removeLines filename$, start, count

   open filename$ for input as #in
   open filename$ + ".tmp" for output as #out
   lineCounter = 1
   firstAfterIgnored = start + count
   while not(eof(#in))
       line input #in, s$
       if lineCounter < start or lineCounter >= firstAfterIgnored then
           print #out, s$
       end if
       lineCounter = lineCounter + 1
   wend
   close #in
   close #out
   'kill filename$
   'name filename$ + ".tmp" as filename$

end sub </lang>


Lua

<lang lua>function remove( filename, starting_line, num_lines )

   local fp = io.open( filename, "r" )
   if fp == nil then return nil end
   content = {}
   i = 1;
   for line in fp:lines() do
       if i < starting_line or i >= starting_line + num_lines then

content[#content+1] = line end i = i + 1

   end
   if i > starting_line and i < starting_line + num_lines then

print( "Warning: Tried to remove lines after EOF." )

   end
   fp:close()
   fp = io.open( filename, "w+" )
   for i = 1, #content do

fp:write( string.format( "%s\n", content[i] ) )

   end
   fp:close()

end</lang>

OCaml

<lang ocaml>let input_line_opt ic =

 try Some (input_line ic)
 with End_of_file -> None

let delete_lines filename start skip =

 if start < 1 || skip < 1 then
   invalid_arg "delete_lines";
 let tmp_file = filename ^ ".tmp" in
 let ic = open_in filename
 and oc = open_out tmp_file in
 let until = start + skip - 1 in
 let rec aux i =
   match input_line_opt ic with
   | Some line ->
       if i < start || i > until
       then (output_string oc line; output_char oc '\n');
       aux (succ i)
   | None ->
       close_in ic;
       close_out oc;
       Sys.remove filename;
       Sys.rename tmp_file filename
 in
 aux 1

let usage () =

 Printf.printf "Usage:\n%s <filename> <startline> <skipnumber>\n" Sys.argv.(0);
 exit 0

let () =

 if Array.length Sys.argv < 4 then usage ();
 delete_lines
   Sys.argv.(1) (int_of_string Sys.argv.(2)) (int_of_string Sys.argv.(3))</lang>
$ cal > cal.txt
$ cat cal.txt
    juillet 2011    
lu ma me je ve sa di
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
 
$ ocaml deleteLines.ml cal.txt 5 2
$ cat cal.txt
    juillet 2011    
lu ma me je ve sa di
             1  2  3
 4  5  6  7  8  9 10
25 26 27 28 29 30 31
 

Perl

The value of deleting certain lines from a file notwithstanding, here's how you'd normally do it in Perl (call with perl rmlines -from=3 -to=10 filename:<lang perl>#!/usr/bin/perl -n -s -i print unless $. >= $from && $. <= $to;</lang> If you want a backup file (maybe because deleting lines from a file in place is a pretty silly idea), change the -i in the first line to -i.bak, for example. <lang Perl>#!/usr/bin/perl -w use strict ; use Tie::File ;

sub deletelines {

  my $arguments = shift ;
  my( $file , $startfrom , $howmany ) = @{$arguments} ;
  tie my @lines , 'Tie::File' , $file or die "Can't find file $file!\n" ;
  my $nrecs = @lines ;
  if ( $startfrom > $nrecs ) {
     print "Error! Starting to delete lines past the end of the file!\n" ;
     return ;
  }
  if ( ( $startfrom + $howmany ) > $nrecs ) {
     print "Error! Trying to delete lines past the end of the file!\n" ;
     return ;
  }
  splice @lines , $startfrom - 1 , $howmany ;
  untie @lines ;

}

if ( @ARGV != 3 ) {

  print "Error! Invoke with deletelines <filename> <start> <skiplines> !\n" ;
  exit( 1 ) ;

}

deletelines( \@ARGV ) ;</lang>

Perl 6

<lang perl6>sub MAIN ($filename, $beg, $len) {

   my @lines = split /^^/, slurp $filename;
   unlink $filename;  # or rename
   splice(@lines,$beg,$len) == $len or warn "Too few lines";
   spurt $filename, @lines;

}</lang>

Output:
$ cal >foo
$ ./rmlines
 Usage:
   rmlines <filename> <beg> <len>
$ ./rmlines foo 1 2
$ cat foo
  1  2  3  4  5  6  7  
  8  9 10 11 12 13 14  
 15 16 17 18 19 20 21  
 22 23 24 25 26 27 28  
 29 30 31

PicoLisp

<lang PicoLisp>(de deleteLines (File Start Cnt)

  (let L (in File (make (until (eof) (link (line)))))
     (if (> (+ (dec 'Start) Cnt) (length L))
        (quit "Not enough lines")
        (out File
           (mapc prinl (cut Start 'L))
           (mapc prinl (nth L (inc Cnt))) ) ) ) )</lang>

PowerBASIC

<lang powerbasic>#DIM ALL

FUNCTION PBMAIN () AS LONG

   DIM filespec AS STRING, linein AS STRING
   DIM linecount AS LONG, L0 AS LONG, ok AS LONG
   filespec = DIR$(COMMAND$(1))
   WHILE LEN(filespec)
       linecount = 0: ok = 0
       OPEN filespec FOR INPUT AS 1
       OPEN filespec & ".tmp" FOR OUTPUT AS 2
       DO UNTIL EOF(1)
           LINE INPUT #1, linein
           INCR linecount
           IF VAL(COMMAND$(2)) <> linecount THEN
               PRINT #2, linein
           ELSE
               ok = -1
               FOR L0 = 2 TO VAL(COMMAND$(3))
                   IF EOF(1) THEN
                       ok = 1
                       PRINT "Tried to remove beyond the end of "; filespec
                       EXIT DO
                   END IF
                   LINE INPUT #1, linein
               NEXT
           END IF
       LOOP
       IF 0 = ok THEN
           PRINT "Lines to remove are beyond the end of "; filespec
       ELSEIF -1 = ok THEN
           PRINT filespec; ": done"
       END IF
       CLOSE
       filespec = DIR$
   WEND

END FUNCTION</lang>

Sample output:

E:\users\Erik\Desktop>rmvlns.exe ?.txt 500 100
Lines to remove are beyond the end of 0.txt
Tried to remove beyond the end of 1.txt
2.txt: done
3.txt: done
4.txt: done

Python

Uses the fileinput standard module. <lang python>#!/usr/bin/env python

import fileinput, sys

fname, start, count = sys.argv[1:4] start, count = int(start), int(count)

for line in fileinput.input(fname, inplace=1, backup='.orig'):

   if start <= fileinput.lineno() < start + count:
       pass
   else:
       print line[:-1]

fileinput.close()</lang>

Output:

There follows a Linux shell session showing the programs use to remove four lines starting from the second, of a file that starts with the digits one to ten on separate lines. The program keeps the original file with a ,orig prefix.

paddy@paddy-ThinkPad-T61:~$ seq 1 10 > testfile.txt
paddy@paddy-ThinkPad-T61:~$ ./remove_lines.py testfile.txt 2 4
paddy@paddy-ThinkPad-T61:~$ cat testfile.txt
1
6
7
8
9
10
paddy@paddy-ThinkPad-T61:~$ cat testfile.txt.orig
1
2
3
4
5
6
7
8
9
10
paddy@paddy-ThinkPad-T61:~$ 

REXX

This example is operating system dependent as this program uses the ERASE and RENAME commands. <lang rexx>/*REXX program to read a specified file and delete specified record(s). */ parse arg iFID ',' at ',' many /*input FID, del start, how many.*/ if iFID= then call er "no input fileID specified." if at= then call er "no start number specified." if many= then many=1 /*Not specified? Assume 1 line.*/ stop=at+many-1 /*calculate high end of deletes. */ oFID=iFID'.out' /*the name of the output file. */ w=0

     do j=1 while lines(iFID)\==0     /*J is the line number being read*/
     x=linein(iFID)                   /*read a line from the input file*/
     if j>=at & j<=stop then iterate  /*if in the range, then ignore it*/
     call lineout oFID,x;   w=w+1     /*write line, bump the write cnt.*/
     end

j=j-1 if j<stop then say "number of lines in file is less than the range given." q='"' /*handle cases of blanks in FID. */ 'ERASE' q || ifid || q 'RENAME' q || ofid || q q || ifid || q say 'file ' iFID " had" j 'record's(j)", it now has" w 'record's(w)"." exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────ER subroutine───────────────────────*/ er: say; say '***error!***; say; say arg(1); say; exit 13 /*──────────────────────────────────S subroutine────────────────────────*/ s: if arg(1)==1 then return arg(3);return word(arg(2) 's',1) /*plurals.*/</lang> output when using the input of: foobar.txt,2,4

file  foobar.txt  had 10 records, it now has 6 records.

file before processing

1
 2
  3
   4
    5
     6
      7
       8
        9
         10

file after processing

1
     6
      7
       8
        9
         10

Ruby

<lang ruby>require 'tempfile' require 'fileutils' require 'English'

def remove_lines(filename, start, num)

 tmp = Tempfile.new(filename)
 File.foreach(filename) do |line|
   if $NR >= start and num > 0
     num -= 1
   else
     tmp.write line
   end
 end
 tmp.close
 STDERR.puts "Warning: End of file encountered before all lines removed" if num > 0
 FileUtils.copy(tmp.path, filename)
 tmp.unlink

end</lang>

Test code <lang ruby>def setup(start, remove)

 puts "remove #{remove} lines starting at line #{start}"
 File.open($filename, "w") {|fh| (1..5).each {|i| fh.puts i}}
 puts "before:\n" + File.read($filename)

end

def teardown

 puts "after:\n" + File.read($filename)
 puts ""
 File.unlink($filename)

end

$filename = "test.file" start = 2 [2, 6].each do |remove|

 setup(start, remove)
 remove_lines $filename, start, remove
 teardown

end</lang>

output

remove 2 lines starting at line 2
before:
1
2
3
4
5
after:
1
4
5

remove 6 lines starting at line 2
before:
1
2
3
4
5
Warning: End of file encountered before all lines removed
after:
1

Seed7

<lang seed7>$ include "seed7_05.s7i";

 include "osfiles.s7i";

const proc: removeLines (in string: fileName, in integer: start, in integer: count) is func

 local
   var file: inFile is STD_NULL;
   var file: outFile is STD_NULL;
   var integer: lineNumber is 1;
   var string: line is "";
 begin
   inFile := open(fileName, "r");
   outFile := open(fileName & ".tmp", "w");
   while hasNext(inFile) do
     line := getln(inFile);
     if lineNumber < start or lineNumber >= start + count then
       writeln(outFile, line);
     end if;
     incr(lineNumber);
   end while;
   close(inFile);
   close(outFile);
   removeFile(fileName);
   moveFile(fileName & ".tmp", fileName);
 end func;

const proc: main is func

 begin
   if length(argv(PROGRAM)) = 3 then
     removeLines(argv(PROGRAM)[1], integer parse (argv(PROGRAM)[2]), integer parse (argv(PROGRAM)[3]));
   end if;
 end func;</lang>

Tcl

<lang tcl>proc removeLines {fileName startLine count} {

   # Work out range to remove
   set from [expr {$startLine - 1}]
   set to [expr {$startLine + $count - 2}]
   # Read the lines
   set f [open $fileName]
   set lines [split [read $f] "\n"]
   close $f
   # Write the lines back out, without removed range
   set f [open $fileName w]
   puts -nonewline $f [join [lreplace $lines $from $to] "\n"]
   close $f

}</lang>

TUSCRIPT

<lang tuscript> $$! input=testfile,begnr=3,endnr=4 $$ MODE TUSCRIPT - CREATE inputfile ERROR/STOP CREATE (input,FDF-o,-std-) FILE/ERASE $input

LOOP n=1,9
content=CONCAT ("line",n)
DATA {content}
ENDLOOP

ENDFILE

- CREATE outputfile output="outputfile"

ERROR/STOP CREATE (output,fdf-o,-std-) ACCESS q: READ/RECORDS/utf8 $input L,line ACCESS z: WRITE/RECORDS/utf8 $output L,line PRINT "content: of output-file" LOOP/9999 READ/NEXT/EXIT q IF (begnr<=L&&endnr>=L) CYCLE PRINT line WRITE z ENDLOOP ENDACCESS/PRINT q ENDACCESS/PRINT z </lang> Output:

Start MAKRO   auf: XXXXXX   am: 02.12.11  um: 17:31:50
content: of output-file
line1
line2
line5
line6
line7
line8
line9
****  Eingabe: 9 Sätze von Datei -*TESTFILE
               Satzlänge: 5
****  Ausgabe: 7 Sätze auf Datei -*OUTPUTFILE
               Satzlänge: 5
Ende  MAKRO   auf: XXXXXX   am: 02.12.11  um: 17:31:50