12

How can I substitute everything in a string with sed except % and any number directly succeding it? Meaning, everything except strings such as:

%1 %1000 %55 etc.

Given strings of this form:

    1: [18x14] [history 1/2000, 268 bytes] %3
    2: [18x14] [history 1/2000, 268 bytes] %4 (active)

I only want to get the %3 and %4 parts. The numbers can go up to 999.

4
  • 1
    Some sample input and expected output might help. As of now, the question isn't very clear.
    – devnull
    May 5, 2014 at 19:37
  • Sorry for that. Given strings of this form: 1: [18x14] [history 1/2000, 268 bytes] %3 and 2: [18x14] [history 1/2000, 268 bytes] %4 (active) I only want to get the %3 and %4 parts. The numbers can be up to 999. May 5, 2014 at 19:39
  • does each line contain only one % sign?
    – miracle173
    May 6, 2014 at 7:03
  • Yes @miracle173, each line does only contain one % sign. May 6, 2014 at 10:22

4 Answers 4

8
$ sed 's/^.*\(%[0-9]\+\).*$/\1/' input

Assuming that a line contains at most one of those %123 tokens and that every line contains such a token.

The \( \) meta character mark a match-group - which is then referenced in the substitution via the \1 back-reference. ^/$ match the beginning/end of a line.

Otherwise you can pre-filter the input, e.g.:

$ grep '%[0-9]\+' input | sed 's/^.*\(%[0-9]\+\).*$/\1/'

(when not all lines contain such a token)

Another variant:

$ sed 's/\(%[0-9]\+\)/\n\1\n/g' | grep '%[0-9]'

(when a line may contain multiple of those tokens)

Here are line breaks inserted directly before and after each token - in the first part of the pipe. Then the grep part removes all non %123 token lines.

1
  • + is a gnu extension... without that (on bsd / os x) it would be sed 's/^.*\(%[0-9]\{1,\}\).*$/\1/' input
    – masukomi
    Jun 9, 2016 at 13:52
7

You might be better off using grep -o in this case:

grep -oP '\B%[0-9]{1,3}\b' inputfile

Assuming that your version of grep supports Perl compatible regular expressions (-P). Otherwise:

grep -o '\B%[0-9]\{1,3\}\b' inputfile

Using GNU sed, one could transliterate spaces to newlines and get the desired lines:

sed 'y/ /\n/' inputfile | sed '/^%[0-9]\{1,\}/!d'
4

When working with sed it's almost always advisable to:

/address then/s/earch/replace/

There are two reasons for this. The first is that with multiple lines /addressing/ is faster - it's optimized only to find a match and doesn't bother selecting only portions of a line for editing and so it can narrow the results sooner.

The second reason is that you can play multiple edit operations off of the same address - it makes things much easier.

Of course, in this case, given only the data you show, it makes no practical difference. Still, this is how I would do the thing you ask about:

sed '/^[^%]*\|[^0-9]*$/s///g' <<\DATA
    1: [18x14] [history 1/2000, 268 bytes] %3
    2: [18x14] [history 1/2000, 268 bytes] %4 (active)
DATA

#OUTPUT
%3
%4

It just selects all characters that are non-% characters from the beginning of the line and all non-numeric characters from the end of the line in the address and then removes them with s/// - and that that's that.

In it's current form it might mangle data in unexpected ways if you feed it lines not containing a %digit combo - and that's why addressing is important. If we alter it a little:

/%[0-9]/s/[^%]*\|[^0-9]*$//g

It gets safer and faster.

2

My solution doesn't use sed but grep with extended-regex and only-matching options.


$ cat file
1: [18x14] [history 1/2000, 268 bytes] %3
2: [18x14] [history 1/2000, 268 bytes] %4 (active)
$ cat file | grep -Eo '%[0-9]+'
%3
%4

Using grep in this case is simpler than using sed.

2
  • 1
    I would tend to agree about the simpler part - and that it is also faster, but... why do you need E? Or cat? If you want to ensure at least one numeric match the + is not necessary: grep -o '%[0-9][0-9]*'- and of course grep's primary purpose is to read files - that's why it works on |pipe files - so grep -${opts} ${files} is all you need, I think.
    – mikeserv
    May 7, 2014 at 11:36
  • 1
    I reuse cat from the previous command line. I use E option and + for regexp. %x can go up to %999 in the question. grep -o '%[0-9]\+' file and grep -oE '%[0-9]+' file are equivalent for numbers over 100.
    – jfgiraud
    May 9, 2014 at 6:59

You must log in to answer this question.

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