Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I've got a situation where I've got several lines, and I need two different fields out of each of them. To be specific, I have a list of references in a bibliography and I want to get the last name and the year.

Sample input:

Aloise-Young, P.A. (1993). The development of self-presentation.  Self-promotion in 6- to 10-year-old children. Social Cognition, 11, 201-222.
Banerjee, R. (2002). Children's understanding of self-presentational behavior: Links with mental-state reasoning and the attribution of embarrassment. Merril-Palmer Quarterly, 48, 378-404.
Bennett, M., & Wellman, H. (1989). The role of second-order belief-understanding and social context in children's self-attribution of social emotions. Social Development, 9, 126-130.

Desired output:

Aloise-Young 1993
Banerjee 2002
Bennett 1989

I can get the last names with cat file | cut -d, -f1

I can get the years with cat file | grep -o "[[:digit:]]\{4\}"

My problem is that now I have two separate outputs and I don't know how to combine them in the way I want. Any ideas? I suspect maybe awk can do what I need, but I'm a bash noob and I don't know awk yet.

share|improve this question
up vote 3 down vote accepted

I put your snippet in a file called alois:

sed -r 's/^([^ ,]+)[^0-9]+([0-9]+).*$/\1 \2/' alois 
Aloise-Young 1993
Banerjee 2002
Bennett 1989

Quick explanation: we use sed's search & replace function s/pattern/replacement/

^([^ ,]+) means: from the beginning of line take anything that's not a space or a , and remember those. (that's that the parentheses are there for).

[^0-9]+ look for anything that's not a numeric value, but ignore it.

([0-9]+) take and remember any consecutive digits

.*$ match everything else to the end of line.

\1 \2 replace everything matched (the whole line) with the remembered values from above.

share|improve this answer
I'm on a Mac, so I had to use -E instead of -r flag, but that worked great after that. Thanks! – Nathan Wallace May 2 '13 at 1:28

When the text processing gets too tough for basic tools, try Awk.

awk -F , '{last_name = $1; sub(/\).*/, ""); sub(/.*\(/, ""); print last_name, $0}'

Here sed is about on par — it's less readable, but awk suffers from the lack of backreferences.

sed -n 's/^\([^,]*\),[^(]*(\([^()]*\)).*/\1 \2/p'

For this particular task, Perl is a little easier overall. You can use the non-greedy repetition operator *? to ensure that you capture the first parenthesized part of the line.

perl -l -ne '/^([^,]*),.*?\(([^()]*)\)/ and print "$1 $2"'
share|improve this answer
With your sed command I get this: sed -n 's/^([^,]*),[^(]*([^()])$/\1 \2/p' alois sed: -e expression #1, char 34: invalid reference \2 on `s' command's RHS – tink May 2 '13 at 1:23
I also got an error with the sed command and with awk one as well. Could be because I'm running Mac, not Linux. Perl worked straight away, though – Nathan Wallace May 2 '13 at 1:29
@tink Sorry, I shouldn't have typed the commands directly in my browser. There, I've tested them and corrected them. – Gilles May 2 '13 at 1:29
They all work for me now. Thanks for all those! I'm definitely going to study these and figure out what they're doing. I'd upvote you if I were allowed yet – Nathan Wallace May 2 '13 at 1:31

In general, you can join the output of commands with paste and process substitution <(...), so in your case this works:

 paste -d ' ' <(cut -d, -f1 file) <(grep -o "[[:digit:]]\{4\}" file)


Aloise-Young 1993
Banerjee 2002
Bennett 1989

But this entails passing file twice which is unnecessary, so you should probably be using a tool that can grab both items in one go, e.g. sed, awk, etc.

share|improve this answer

You may find out that you need to refine your requirements. For instance, none of the solutions given so far work for input like:

Smith, J., & 3Com(Inc.) research (1999), XYZ statistics (1960 - 1998)

To report everything up to the first coma along with the first occurrence of a sequence of 4 digits enclosed in parenthesis, you could do:

perl -ne 'print "$1 $2\n" if /^(.*?),.*?\((\d{4})\)/'
share|improve this answer

Not sexy but you can trim the text from the first comma to the first open paren. Substitute all that with a space. Then just trim the character from the first closing paren to the end of the line.

1. convert this => , ..... ( to a space
2. convert this => )........ to nothing

The command

$ cat file | sed 's/,.*(/ /' | sed 's/).*//'
Aloise-Young 1993
Banerjee 2002
Bennett 1989
share|improve this answer
This doesn't work if the part after the year contains (, but you could use 's/,[^(]*(/ /;s/).*//'. – user495470 May 2 '13 at 15:39
@LauriRanta - Thanks. It works for the sample data that the OP provided. If he wanted it to be more robust then he should've included more examples 8-). – slm May 2 '13 at 15:48

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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