2

I am trying to delete a file's contents from a supplied line number using sed. The problem is that sed isn't accepting the variable I supply to it

line_num=$(grep -n "debited" file.csv | sed -n '2 s/:.*//p') && sed -i.bak "$line_num,$d" file.csv

The idea is to delete all lines from a file after & including the second occurence of the pattern.

I'm not stubborn with sed. Awk & perl could do too.

usert4jju7
  • 1,653
  • 3
  • 27
  • 59
  • 2
    Your shell is probably interpolating the `$d`. Try putting a backslash in front of the `$d`. – Ed Sabol Dec 27 '21 at 23:15
  • Looks to me that the "given" part here is the word "debited", not a line number. – TLP Dec 27 '21 at 23:16
  • Why not use `head`? – Jiří Baum Dec 28 '21 at 05:41
  • See also [Counting lines or enumerating line numbers so I can loop over them - why is this an anti-pattern?](https://stackoverflow.com/questions/65538947/counting-lines-or-enumerating-line-numbers-so-i-can-loop-over-them-why-is-this) – tripleee Dec 28 '21 at 05:49
  • 2
    Any time you find yourself using multiple pipes between grep and sed you should be using awk instead. – Ed Morton Dec 30 '21 at 00:07

3 Answers3

4

Seems like you want to delete the rest of the file after a second showing of a pattern (debited), including that line.

Then can truncate it, ising tell for the length of what's been read up to that line

perl -e'while (<>) { 
    if ( ($cnt += /debited/) == 2 ) { truncate $ARGV, $len; exit } 
    $len = tell;
}' file

Here the $ARGV variable has the "current" file (when reading from <>). Feel free to introduce a variable with the pattern instead of the literal (debited), based on your context.

This can be made to look far nicer in a little script but it seems that a command-line program ("one-liner") is needed in the question.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • You could just `close ARGV`... `perl -pi.bak -e'if ( ($c += /debited/) == 2 ) { close ARGV }' file`. As I recall, it works even with multiple files. Untested. – TLP Dec 28 '21 at 00:55
  • @TLP Yeah, a good idea -- I'd think that it absolutely should truncate it -- but it doesn't? Maybe my testing's faulty somehow... – zdim Dec 28 '21 at 01:08
  • @TLP oh ... actually, can't do that: once the line with the second `debited` _has been_ read it's too late to simply truncate where we are (even if it worked); it has to truncate to the length of up to that line, and can't accomplish that using `close ARGV`. A good idea, regardless – zdim Dec 28 '21 at 01:10
  • The perldoc says that it can depend on your system. Oh right, yes, you have to print *after* the check, and use `-n`. – TLP Dec 28 '21 at 01:11
3

I always suggest ed for editing files over trying to use sed to do it; a program intended from the beginning to work with a file instead of a stream of lines just works better for most tasks.

The idea is to delete all lines from a file after & including the second occurence[sic] of the pattern

Example:

$ cat demo.txt
a
b
c
debited 12
d
e
debited 14
f
g
h
$ printf "%s\n" '/debited/;//,$d' w | ed -s demo.txt
$ cat demo.txt
a
b
c
debited 12
d
e

The ed command /pattern/;//,$d first sets the current line cursor to the first one that matches the basic regular expression pattern, then moves it to the next match of the pattern and deletes everything from there to the end of the file. Then w writes the changed file back to disk.

Shawn
  • 47,241
  • 3
  • 26
  • 60
  • Thank you Shawn. Choosing your solution over the other brilliant ones as it edits the file in place & is faster to run. – usert4jju7 Dec 31 '21 at 09:00
2

you're doing lot's of unnecessary steps, this will do what you want.

$ awk '/debited/{c++} c==2{exit}1' file

delete second occurrence of the pattern and everything after it.

To replace the original file (and create backup)

$ awk ... file > t && mv -b --suffix=.bak t file
karakfa
  • 66,216
  • 7
  • 41
  • 56