37

I have a file with contents

x
a
x
b
x
c

I want to grep the last occurrence,

x
c

when I try

sed -n  "/x/,/b/p" file

it lists all the lines, beginning x to c.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
user3702858
  • 381
  • 1
  • 3
  • 3
  • 2
    So you want to print from the last `x` up to the end? – fedorqui Jun 03 '14 at 11:40
  • as far as I know sed manipulates each line separately. ("The commands you give it are run on each line of input in turn" - quote from http://en.flossmanuals.net/command-line/sed/) – trainoasis Jun 03 '14 at 11:55

7 Answers7

79

I'm not sure if I got your question right, so here are some shots in the dark:

  • Print last occurence of x (regex):

    grep x file | tail -1
    
  • Alternatively:

    tac file | grep -m1 x
    
  • Print file from first matching line to end:

    awk '/x/{flag = 1}; flag' file
    
  • Print file from last matching line to end (prints all lines in case of no match):

    tac file | awk '!flag; /x/{flag = 1};' | tac
    
steffen
  • 16,138
  • 4
  • 42
  • 81
  • 2
    This `awk "/x/ { flag = 1 }; { if (flag == 1) print; }" txt` can be written some more simple like this `awk '/x/ {flag=1} flag' txt` – Jotne Jun 03 '14 at 12:33
  • 1
    Using `tac` to facilitate getting the last instance is ingenious! Thank you – Paul Grinberg Jun 19 '19 at 14:14
  • Need to be careful with the use of " or ' for the awk statement. I had trouble with the negation ! - I got an error "awk: cmd. line:1 ^backslash not last char - as I thought I had to use \!, but using ' prevents this. – Jobst Jun 21 '19 at 06:02
  • 1
    Also the /x/ is a regular expression, you need to be aware of the chars used within the regular expression, need to escape. Luckily I could change the string in the file I had to search for which made the regular expression easier. – Jobst Jun 21 '19 at 06:04
  • Note: `tac` requires the GNU utilities. – jvriesem Jun 18 '20 at 15:17
  • can someone explain what this command does? tac file | awk '!flag; /x/{flag = 1};' | tac – HookUp Mar 28 '22 at 18:39
  • 1
    @HookUp `!flag` sets a condition on awk's default action (`print $0`). An unset `flag` is `false`. So this code prints lines as long as pattern `/x/` didn't set `flag=1`. But this solution isn't performing well. If (at all) one could probably better go `'1;/x/{exit}'` instead of `'!f;/x/{f=1}'`. But always given that you know for sure the file contains `/x/`. – steffen Mar 28 '22 at 20:04
  • @steffen - great - thank you for explaining and adding a better solution for this. I am not so familiar with linux commands so trying to understand how exactly this works : '1;/x/{exit}' ? does this mean as you as it finds the 1st occurrence of 'x' in the tac command result - it will exit and print the lines until the end of the file? – HookUp Mar 28 '22 at 22:37
8
grep -A 1 x file | tail -n 2

-A 1 tells grep to print one line after a match line
with tail you get the last two lines.

or in a reversed way:

tac fail | grep -B 1 x -m1 | tac

Note: You should make sure your pattern is "strong" enough so it gets you the right lines. i.e. by enclosing it with ^ at the start and $ at the end.

elig
  • 2,635
  • 3
  • 14
  • 24
4

This might work for you (GNU sed):

sed 'H;/x/h;$!d;x' file

Saves the last x and what follows in the hold space and prints it out at end-of-file.

potong
  • 55,640
  • 6
  • 51
  • 83
1

not sure how to do it using sed, but you can try awk

awk '{a=a"\n"$0; if ($0 == "x"){ a=$0}}  END{print a}' file
ray
  • 4,109
  • 1
  • 17
  • 12
0

POSIX vi (or ex or ed), in case it is useful to someone

Done in Command mode, of course

:set wrapscan

Go to the first line and just search Backwards! 1G?pattern

Slower way, without :set wrapscan

G$?pattern

Explanation:

G go to the last line

Move to the end of that line $

? search Backwards for pattern

The first backwards match will be the same as the last forward match

Either way, you may now delete all lines above current (match)

:1,.-1d

or

kd1G

You could also delete to the beginning of the matched line prior to the line deletions with d0 in case there were multiple matches on the same line.

POSIX awk, as suggested at get last line from grep search on multiple files

awk '(FNR==1)&&s{print s; s=""}/PATTERN/{s=$0}END{if(s) print s}'

Kajukenbo
  • 109
  • 1
  • 4
0

if you wanna do awk in truly hideous one-liner fashion but getting awk to resemble closer to functional programming paradigm syntax without having to keep track when the last occurrence is

mawk/mawk2/gawk 'BEGIN { FS = "=7713[0-9]+="; RS = "^$";

 } END { print ar0[split($(0 * sub(/\n.+$/,"",$NF)), ar0, ORS)] }'

Here i'm employing multiple awk short-hands :

    sub(/[\n.+$/, "", $NF) # trimming all extra rows after pattern

g/sub() returns # of substitutions made, so multiplying that by 0 forces the split() to be splitting $0, the full file, instead.

split() returns # of items in the array (which is another way of saying the position of last element), so even though I've already trimmed out the trailing \n, i still can directly print ar0[split()], knowing that ORS will fill in the missing trailing \n.

That's why this code looks like i'm trying to extract array items before the array itself is defined, but due to flow of logic needed, the array will become defined by the time it reaches print.

Now if you want something simpler, these 2 also work

mawk/gawk 'BEGIN { FS="=7713[0-9]+="; RS = "^$" 

   } END { $NF = substr($NF, 1, index($NF, ORS)); 
           
           FS = ORS; $0 = $0; print $(NF-1) }'

or

mawk/gawk '/=7713[0-9]+=/ { lst = $0 } END { print lst }'
  • I didn't use the same x|c requirements as OP just to showcase these work regardless of whether you need fixed-strings or regex based matches.
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11
0

The above solutions only work for one single file, to print the last occurrence for many files (say with suffix .txt), use the following bash script

#!/bin/bash
for fn in `ls *.txt`
do
    result=`grep 'pattern' $fn | tail -n 1`
echo $result
done

where 'pattern' is what you would like to grep.

zyy
  • 1,271
  • 15
  • 25