2

I found a similar question here, but it is not exactly what I want.

Let's suppose I have a text file like this:

[...]

age: 10
country: United States
city: New York
name: John

age: 27
country: Canada
city: Toronto
name: Robert

age: 32
country: Mexico
city: Guadalajara
name: Pedro

[...]

I want to match the line starting with "name: Robert" and print the 3 previous lines along with the matched result, getting only these lines:

age: 27
country: Canada
city: Toronto
name: Robert

How can I do this in the terminal?

2
  • Can the target name ever contain regexp metachars like name: J.K. Rowling?
    – Ed Morton
    Aug 5 at 13:27
  • @EdMorton In my case is just matching letters or words at the begin of a line. Aug 5 at 14:42

4 Answers 4

11

Using grep

$ grep -B3 '^name: Robert$' input_file
age: 27
country: Canada
city: Toronto
name: Robert
0
6

You already have the best way, just use grep -B, but you could also use perl in "paragraph mode". This mode is enabled by the -00 switch and means that a "line" is now defined by two consecutive newline characters, basically a paragraph. This lets you do things like:

$ perl -00 -ne 'print if /name: Robert\b/' file
age: 27
country: Canada
city: Toronto
name: Robert

$

You could also do the same thing in awk by setting the record separator (RS) to empty (''):

$ awk -v RS='' '/name: Robert$/' file
age: 27
country: Canada
city: Toronto
name: Robert
$

The awk one also has the advantage of not printing an extra newline which the perl does.

8
  • One issue with the awk script at least is it'd produce a false match if name: Robertson existed in the input. Idk if the perl script has the same issue or not as I don't dare to guess at what any perl script does :-).
    – Ed Morton
    Aug 5 at 13:11
  • @EdMorton true, I guess we could use /name: Robert\b/ or /name: Robert\>/ or whichever awk will understand best, or even /name: Robert\n/ although I suspect that will not be very portable.
    – terdon
    Aug 5 at 13:14
  • /\nname: Robert$/ would be portable if it's always a multi-line record with the name line at the end as the OP seems to be counting on, or if the name line could appear anywhere in the record then /(^|\n)name: Robert(\n|$)/. Using \> would be gawk-only, I'm not sure which awks support \b but those would both fail with a hyphenated name like name: Robert-Jones anyway,
    – Ed Morton
    Aug 5 at 13:16
  • 1
    You could alternatively add -F'\n' and then test for $NF == "name: Robert".
    – Ed Morton
    Aug 5 at 13:26
  • 1
    @phantomcraft yes, absolutely. Grep is the better approach.
    – terdon
    Aug 5 at 14:44
3

The format of your data is the same as the "recfile format" used by GNU recutils. This means we may query your file like so:

$ recsel -e 'name = "Pedro"' file
age: 32
country: Mexico
city: Guadalajara
name: Pedro

To get the names of everyone aged 20 or more:

$ recsel -e 'age >= 20' -P name file
Robert

Pedro

Etc.

See info recutils or the GNU recutils website for further information.

1
  • What I posted is just an example, I want to match texts and print the X previous lines for post-processing with another command (and now I can). Interesting this recsel, I didn't now it. Aug 4 at 19:59
2

Using the line editor ed we do it by first selecting the target lines(g//), then create a range around it(-3,.), and run a command on that range(p)

ed -s inp <<\eof
g/^name: Robert$/-3,.p
eof

## Gnu sed in extended regex mode
## collect 4 lines in pattern space then print the whole pattern space on match else clip first line and append the next

sed -E ':1;$!{N;/(.*\n){3}/!b1;/\nname: Robert$/b};D' file

# using the -p option to autoprint current record 

perl -00 -pe '($_)=/((?:.*\n){3}name: Robert\n)/' file

Output:

age: 27
country: Canada
city: Toronto
name: Robert

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.