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
  • 2
    What about in foo1234bar or 0x1234ff or 1.1234? – Stéphane Chazelas Nov 25 '14 at 16:03
  • 1
    @StéphaneChazelas It would output 1234 1234 1234. – DisplayName Nov 25 '14 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 '14 at 7:40
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
| improve this answer | |
  • Not "max length". It'll capture numbers with exactly 4 digits. – John WH Smith Nov 25 '14 at 15:35
  • @JohnWHSmith Uh yes my mistake ;) thank you – αғsнιη Nov 25 '14 at 15:36
  • @KasiyA you can add .. file | tr '\n' ' ' to put the numbers in the same line. – Valentin Bajrami Nov 25 '14 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 '14 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 '14 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.)

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

POSIXly:

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

With perl:

$ perl -Tnle 'BEGIN {$, = " "} print $_ =~ /\b\d{4}\b/g' file
9228 8473 1914 1918 8391 1939 1945
| improve this answer | |
  • That matches 1234 in 12345 though (and not 2345). – Stéphane Chazelas Nov 25 '14 at 17:06
  • @StéphaneChazelas: Good point. It will be better now. – cuonglm Nov 25 '14 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 '14 at 17:53
  • @cuonglm Prints empty line if not match with pattern. example for 12345 – αғsнιη Nov 25 '14 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 '14 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.

| improve this answer | |
  • @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 '14 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
| improve this answer | |
  • That matches 1234 in 12345 though (and not 2345). – Stéphane Chazelas Nov 25 '14 at 17:06
  • @StéphaneChazelas, hmm.. good point. I've attempted to address that with word boundaries now – iruvar Nov 25 '14 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).

| improve this answer | |
  • positive and negative lookahead assertions are overkill when word boundaries \b would suffice – iruvar Nov 27 '14 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 
| improve this answer | |

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.