9

I am looking for an easy way to cut key-value pairs (where the key is unique and specified) from text, much like how cut can be used to cut a specified column in a CSV file. The keys I'm looking for are not always in the same relative position in the line -- that is, using cut followed by sed won't do the trick, because they key I'm looking for is not always in the same ,-delimited column.

The text in question is indeed CSV, but it so happens that the values are key/value pairs, delimited with an =.

For example, I might parse a file with the following three lines:

Foo=1, Bar=2, Baz=3
Bar=4, Foo=2, Baz=3
Bar=42, Baz=42, Foo=3

And I would like to cut this text to yield the key/value pair for a specific key. If I was looking for Foo, then my desired output would be:

Foo=1
Foo=2
Foo=3

Ideally I would like a command-line tool that has similar syntax as cut, and can read both from stdin and from a file.

Is there such a tool?

5 Answers 5

13

grep can do this with -o option:

grep -o 'Foo=[^,]*' file
5
  • 1
    Probably need a boundary marker to avoid catching keys such as NotFoo Feb 23, 2015 at 22:41
  • With a little tweaking, I got this to work with my actual use case. I prefer this solution to the others posted simply because it involves less typing, and thus is more well-suited to quick hackery. Feb 23, 2015 at 22:42
  • That's a feature I've wanted from grep for a long time. Did it creep in recently or haven't I reread the man page for too long? Feb 23, 2015 at 22:44
  • 1
    @roaima one can add \b to not match NoFoo, so it would be: grep -o '\bFoo=[^,]*'. I don't know when -o was added, but it is there since I remember.
    – jimmij
    Feb 23, 2015 at 22:52
  • This would fail doing an exact search if you are looking for an specific key. For example if you have "FOO=1" and "MY_FOO=2" would match both lines. Add "^" if front of key to fix this: grep -o '^Foo=[^,]*'
    – sh4
    Apr 26, 2018 at 11:38
3

The awk solution to round up the list of alternatives:

awk -v RS=', ' -F'=' '$1=="Foo"' <file>

Treat each record to be delimited by ', ', and split each record into fields on the = character (using -F) as well. Then it's just a matter of matching on the first field $1. The suggestion shown here is a simple string matching, feel free to use regexes, e.g. $1~/\<Foo\>/.

2

Depending on the complexity of your real-world situation, this sed command may be sufficient:

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

Here's the worked example

FIELD='Foo'
sed -n "s/^.*\(\<${FIELD}=[^,]*\).*/\1/p' << xxEOFxx
Foo=1, Bar=2, Baz=3
Bar=4, Foo=2, Baz=3
Bar=42, Baz=42, Foo=3
xxEOFxx
Foo=1
Foo=2
Foo=3
2
  • @mikeserv where are multiple instances of the same key specified in the requirement? Feb 23, 2015 at 22:39
  • 1
    Actually, the keys are unique in my use case. This is stated in my OP. Feb 23, 2015 at 22:43
2

Given your example, a brittle solution could involve cut:

tr ', ' '[\n*]' <input | cut -sd F -f1-

...which would put each key/value pair on a separate line by transforming the intervening commas and spaces into \newlines, and then cutting out lines which don't contain an F. But that is a highly specialized example, and can only work if you can be sure an F only occurs in the wanted key/value pairs.

Otherwise, sed would be what I would use:

sed 'y/ ,/\n\n/;/^Foo=/P;D' <input

Which would also transform intervening commas and spaces into \newlines, but then only Print those key/value pairs which begin w/ the string Foo=. So long as the spaces are reliable separators, the above would work to portably print the Foo key/value pairs each on a separate line no matter how many times they might occur on an input line, and would print nothing else - even for lines which do not contain the key/values you want printed.

1
  • 1
    well, using grep '^Foo=' instead of cut ... should give the expected result (and would be pretty fast too). Jul 21, 2015 at 23:26
1

If you don't have a grep available with the -o option, this ought to do the trick as well:

sed -e 's/, /\n/g' | grep '^Foo='

That's using sed to replace every comma+space with a newline (breaking each key-value pair onto its own line), and then grep to search for only the 'Foo' key.

Test case:

printf "%s\n" "Foo=1, Bar=2, Baz=3" "Bar=4, Foo=2, Baz=3" "Bar=42, Baz=42, Foo=3" \
    | sed -e 's/, /\n/g' | grep '^Foo='

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .