4

I need to remove everything that is not a 4 character number such as 9838, 6738, 1337 or 1889.

I though that this command would work:

sed 's/....[^0-9]//g'

. Means any character in regex, and [^0-9] removes none numbers.

Here is an example input:

9228 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945

Would be:

9228 8473 1914 1918 8391 1939 1945
3
  • 2
    What about in foo1234bar or 0x1234ff or 1.1234? Nov 25, 2014 at 16:03
  • 1
    @StéphaneChazelas It would output 1234 1234 1234. Nov 25, 2014 at 20:51
  • What about an 8 digit number or 9 digit, how would you decide which to drop, or would you print all combinations in it ?
    – user78605
    Nov 27, 2014 at 7:40

8 Answers 8

8

I can answer with grep command:

Input file:

9228 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945

Command:

grep -Eo '\<[0-9]{4}\>' file |tr '\n' ' '

Return any number with length=4.

-E switches to extended regex -o print only the matching part

Output:

9228 8473 1914 1918 8391 1939 1945

Update answer:

Input file:

9228 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945

foo1234bar
a1111
12345
0x2222ff
1.3333
2.54321

Command

grep -oP '(?<![0-9])[0-9]{4}(?![0-9])' file | tr '\n' ' '

grep with negative lookbehind/lookahead:

(?<![0-9])[0-9]{4} (negative lookbehind): matches numbers to length=4 that is not preceded by a number[0-9].
[0-9]{4}(?![0-9]) (negative lookahead): match numbers to length=4 not followed by a number.

Output:

9228 8473 1914 1918 8391 1939 1945 1234 1111 2222 3333
7
  • Not "max length". It'll capture numbers with exactly 4 digits. Nov 25, 2014 at 15:35
  • @JohnWHSmith Uh yes my mistake ;) thank you Nov 25, 2014 at 15:36
  • @KasiyA you can add .. file | tr '\n' ' ' to put the numbers in the same line. Nov 25, 2014 at 15:46
  • This seems to work but will also give you strings with more than 4 digits. You can fix that by changing the regex pattern to: `\<[0-9]{4}\>'. The angular brackets delimit words.
    – unxnut
    Nov 25, 2014 at 15:46
  • 3
    @JohnWHSmith: I had tried it and it gave me the first four digits of a five digit string without the angular brackets.
    – unxnut
    Nov 25, 2014 at 18:58
5

You can use perl.

perl -nle'print join " ", /(?<![0-9])[0-9]{4}(?![0-9])/g'

This also works for multi-line input, so if you have:

9228 Hello 8473 World War 1 1914-1918
Hello 8391 World War 2 1939-1945

You'll get the following returned:

9228 8473 1914 1918
8391 1939 1945

(Add -0777 if you want the numbers on the same line.)

1
  • Might be faster: perl -nle'print join " ", grep { length($_) == 4) } /[0-9]+/g'
    – ikegami
    Nov 25, 2014 at 18:53
4

POSIXly:

< file tr -cs 0-9 '[\n*]' | grep -xE '.{4}' | paste  -sd ' ' -
2

With perl:

$ perl -Tnle 'BEGIN {$, = " "} print $_ =~ /\b\d{4}\b/g' file
9228 8473 1914 1918 8391 1939 1945
12
  • That matches 1234 in 12345 though (and not 2345). Nov 25, 2014 at 17:06
  • @StéphaneChazelas: Good point. It will be better now.
    – cuonglm
    Nov 25, 2014 at 17:09
  • @StéphaneChazelas: Add -T for filename beginning or ending with | after reading your editing. Can you give some reference for this? I only read perldoc -f open, and it has a little information.
    – cuonglm
    Nov 25, 2014 at 17:53
  • @cuonglm Prints empty line if not match with pattern. example for 12345 Nov 25, 2014 at 17:55
  • @KasiyA: I think it fit in with OP's requirement. All things which is not 4 digit long will be removed. So when not matches, all things was removed.
    – cuonglm
    Nov 25, 2014 at 18:00
1

With a single s/// you can do this in a POSIX sed - but there are a lot of backslashes:

sed 's/[^0-9]*\([0-9]\{5,\}\)*[^0-9]*\([0-9]\{4\}\)*.\{0,1\}/ \2/g
' <<\IN
92828 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945
IN

OUTPUT

8473  1914 1918 8391  1939 1945

It globally gobbles all of the sequences with 5 or more digits first - then any ^not digits - then your match, and last 0 or 1 of any other character. In that entire statement only 1 expression is saved - your match - and so, that is what remains - besides any intervening blanks.

But with a little translation things get easier:

sed 'y/ /./;s/[0-9]\{4,\}/ & /g;s/\( [^ ]\{4\} \)*[^ ]*/\1/g
' <<\IN
92828 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945
IN

OUTPUT

  8473  1914  1918  8391  1939  1945 

sed first transliterates all spaces to dots. It next wraps any sequence of 4 or more digits in spaces. And last it removes all not-space characters save any sequence of four surrounded by spaces.

1
  • @StéphaneChazelas - thank you for the edit. I didn't even consider that context at all. Maybe you might have a look at this question? I made an attempt - but I'm kind of hopeless w/ join and I think probably you could a better answer for the guy.
    – mikeserv
    Nov 25, 2014 at 21:54
0

with gawk

echo '9228 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945' |
awk --re-interval -v RS='\\y[[:digit:]]{4}\\y' '{printf "%s ", RT}END{print ""}'
9228 8473 1914 1918 8391 1939 1945
2
  • That matches 1234 in 12345 though (and not 2345). Nov 25, 2014 at 17:06
  • @StéphaneChazelas, hmm.. good point. I've attempted to address that with word boundaries now
    – iruvar
    Nov 25, 2014 at 17:25
0

Python

In python i would do like the below,

#!/usr/bin/python
import re
import sys
file = sys.argv[1]
with open(file, 'r') as f:
    for line in f:
        print ' '.join(re.findall(r'(?<!\d)\d{4}(?!\d)', line))

Save the above script as script.py and then run it by applying the below command on the terminal.

python script.py file

Example:

$ cat file
9228 Hello 8473 World War 1 1914-1918 Hello 8391 World War 2 1939-1945
foo1234bar 0x1234ff 1.1234
$ python f.py file
9228 8473 1914 1918 8391 1939 1945
1234 1234 1234

Perl

Through perl's s/// command. That is search and replace command.

perl -pe 's/(?<!\d)\d{4}(?!\d)(*SKIP)(*F)|.+?(?=\d{4}|$)/ /g;s/^ +| +$//g' file

Example:

$ cat file
9228 Hello 8473 World War 1 1914-1918 Hello
foo1234bar 0x1234ff 1.1234
$ perl -pe 's/(?<!\d)\d{4}(?!\d)(*SKIP)(*F)|.+?(?=\d{4}|$)/ /g;s/^ +| +$//g' file
9228 8473 1914 1918
1234 1234 1234

Read here to know more about the PCRE verb (*SKIP)(*F).

1
  • positive and negative lookahead assertions are overkill when word boundaries \b would suffice
    – iruvar
    Nov 27, 2014 at 17:32
0

Just in case if you need another version of answer based on sed.

$ sed -re 's#(\b[0-9]{4}\b)*([^0-9]|\b[0-9]\b)*#\1 #g' file.txt
9228 8473 1914 1918 8391 1939 1945 

You must log in to answer this question.

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