79

Example:

a43
test1
abc
cvb
bnm
test2
kfo

I need all lines between test1 and test2. Normal grep does not work in this case. Do you have any propositions?

1

9 Answers 9

83

Print from test1 to test2 (Trigger lines included)

awk '/test1/{f=1} /test2/{f=0;print} f'
awk '/test1/{f=1} f; /test2/{f=0}' 
awk '/test1/,/test2/'

test1
abc
cvb
bnm
test2

Prints data between test1 to test2 (Trigger lines excluded)

awk '/test1/{f=1;next} /test2/{f=0} f' 
awk '/test2/{f=0} f; /test1/{f=1}' 

abc
cvb
bnm
Sign up to request clarification or add additional context in comments.

9 Comments

Hello sir , do you know how can use your same script in shell language
If my input is in a string, how would I use this to process that string?
@LennartRolland See my post here about how to get data inn to awk from variables. stackoverflow.com/questions/19075671/…
How would you modify this to include one trigger line, but not the other? i.e., include test1, but not test2
Would be better if you could explain what the commands here mean
|
60

You could use sed:

sed -n '/test1/,/test2/p' filename

In order to exclude the lines containing test1 and test2, say:

sed -n '/test1/,/test2/{/test1/b;/test2/b;p}' filename

7 Comments

Or, if you are OK with shell expansion in your sed string, you can do start="test1"; end="test2"; sed -n "/$start/,/$end/{/$start/b;/$end/b;p}" filename. That way you only have to type each search pattern once.
To exclude the lines it can be shortened to just sed -n '/test1/,/test2/{//b;p}'
What does //b mean? I just know b means branch, but I don't know about //
Is it possible to print few lines before 'test1' string and few lines after 'test2' string? What modifications are required?
Would be better with semantics explained
|
16

If you can only use grep:

grep -A100000 test1 file.txt | grep -B100000 test2 > new.txt

grep -A and then a number gets the lines after the matching string, and grep -B gets the lines before the matching string. The number, 100000 in this case, has to be large enough to include all lines before and after.

If you don't want to include test1 and test2, then you can remove them afterwards by grep -v, which prints everything except the matching line(s):

egrep -v "test1|test2" new.txt > newer.txt

or everything in one line:

grep -A100000 test1 file.txt | grep -B100000 test2 | egrep -v "test1|test2" > new.txt 

3 Comments

I didn't know about egrep, interesting, thanks. Details here. unix.stackexchange.com/questions/17949/…
just want to point out the obvious that this might fail if test1/test2 pairs occurs more than once in the input.
@LennartRolland in which case, it takes the first occurrence of test1, and the first occurrence of test2 after that, which happened to be exactly what I wanted. :)
8

Yep, normal grep won't do this. But grep with -P parameter will do this job.

$ grep -ozP '(?s)test1\n\K.*?(?=\ntest2)' file
abc
cvb
bnm

\K discards the previously matched characters from printing at the final and the positive lookahead (?=\ntest2) asserts that the match must be followed by a \n newline character and then test2 string.

2 Comments

Which grep flavor is this? I'm on mac os and there is no -P. What meaning does -P have in the grep context above? It would also help to explain -oz if they are part of the solution. In mac os grep -o is --only-matching and -z is --decompress. The former might be relevant but the latter doesn't seem relevant.
P represents perl regex. ie, we can use perl regex on grep. Unfortunately grep on Mac won't support this option.
1

You can do something like this too. Lets say you this file test.txt with content:

a43
test1
abc
cvb
bnm
test2
kfo

You can do

cat test.txt | grep -A10 test1 | grep -B10 test2

where -A<n> is to get you n lines after your match in the file and -B<n> is to give you n lines before the match. You just have to make sure that n > number of expected lines between test1 and test2. Or you can give it large enough to reach EOF.

Result:

test1
abc
cvb
bnm
test2

1 Comment

this is cool if you know number of lines, but is not so in real world when you need to get all lines between two markers. For example a list of files are packed into a tar, but you do not know which ones and you want full list. Then your solution will not work. Jotne's solution will work perfectly.
1

The answer by PratPor above:

cat test.txt | grep -A10 test1 | grep -B10 test2

is cool.. but if you don't know the file length:

cat test.txt | grep -A1000 test1 | grep -B1000 test2

Not deterministic, but not too bad. Anyone have better (more deterministic)?

Comments

1

To make it more deterministic and not having to worry about size of file, use the wc -l and cut the output.

grep -Awc -l test.txt|cut -d" " -f1 test1 test.txt | grep -Bwc -l test.txt|cut -d" " -f1 test2

To make it easier to read, assign it to a variable first.

fsize=wc -l test.txt|cut -d" " -f1; grep -A$fsize test1 test.txt | grep -B$fsize test2

Comments

0

The following script wraps up this process. More details in this similar StackOverflow post

get_text.sh

#!/usr/bin/env bash
function show_help()
{
  ME=$(basename "$0")
  IT=$(cat <<EOF

  $ME: extracts lines in a file between two tags

  usage: FILENAME {TAG_PREFIX|START_TAG} {END_TAG}

  examples:
    $ME 1.txt AA     => extracts lines in file 1.txt between AA_START and AA_END
    $ME 1.txt AA BB  => extracts lines in file 1.txt between AA and BB
EOF
)
  echo "$IT"
  echo
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi
if [ -z "$2" ]
then
  show_help
fi

function doMain()
{
  FILENAME=$1
  if [ ! -f $FILENAME ]; then
      echo "File not found: $FILENAME"
      exit;
  fi

  if [ -z "$3" ]
  then
    START_TAG=$2_START
    END_TAG=$2_END
  else
    START_TAG=$2
    END_TAG=$3
  fi

  CMD="cat $FILENAME | awk '/$START_TAG/{f=1;next} /$END_TAG/{f=0} f'"
  eval $CMD
}

doMain "$@"

2 Comments

I love this answer, small and strong!
It just encouraged me to trust the awk answer!
0

I wrote a script which even works for the lines that might have any special character.

#!/bin/bash
if  [ "$#" -ne 1 ]; then
    echo "Usage: $0 <logfile_name>"
    exit 1
fi

log_file="$1"

if [ ! -f "$log_file" ]; then
    echo "File not found: $log_file"
    exit 1
fi

# User Input
read -p "Enter 1st string: " str1
read -p "Enter 2nd string: " str2

# Escape special characters in str1 and str2 for sed
escaped_str1=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< "$str1")
escaped_str2=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< "$str2")

# Check if str1 and str2 exist in the file
if ! grep -q "$escaped_str1" "$log_file"; then
    echo -e "\e[31mString \"$str1\" not found in \"$log_file\"\e[0m"
    exit 1
fi

if ! grep -q "$escaped_str2" "$log_file"; then
    echo -e "\e[31mString \"$str2\" not found in \"$log_file\"\e[0m"
    exit 1
fi

# Using sed to extract lines between str1 and str2
if [[ "$escaped_str1" < "$escaped_str2" ]]; then
    echo -e "\e[31mError: Second string \"$str2\" appears before first string \"$str1\" in the file $log_file\e[0m"
    echo -e "\e[32mSwapping strings: \"$str1\" and \"$str2\"\e[0m"
    tmp="$escaped_str1"
    escaped_str1="$escaped_str2"
    escaped_str2="$tmp"
    
fi

sed -n "/$escaped_str1/,/$escaped_str2/p" "$log_file"

This script accepts a file as an argument and prompts the user to enter two lines of strings one by one and extracts the lines in between them.

Note: the input lines of strings are also included.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.