How to separate strings and numbers from one line using a bash command.
Example: I have a string containing
string123anotherstr456thenanotherstr789
The output should be:
string
123
anotherstr
456
thenanotherstr
789
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityGNU grep
or compatible solution:
s="string123anotherstr456thenanotherstr789"
grep -Eo '[[:alpha:]]+|[0-9]+' <<<"$s"
[[:alpha:]]+|[0-9]+
- regex alternation group, matches either alphabetic character(s) or number(s); both will be considered as separate entries on output The output:
string
123
anotherstr
456
thenanotherstr
789
Add a newline character after every [[:alpha:]]+
(sequence of letters) and after every [[:digit:]]+
(sequence of numerals):
awk '{ gsub(/([[:alpha:]]+|[[:digit:]]+)/,"&\n",$0) ; printf $0 }' filename
(The &
is awk
shorthand for the matched sequence.)
As before, but now also dealing with substrings of [^[:alnum:]]+
(non-letter, non-numeral) characters:
awk '{ gsub(/([[:alpha:]]+|[[:digit:]]+|[^[:alnum:]]+)/,"&\n",$0) ; printf $0 }' filename
Treat -
(hyphen) and .
(period) as numbers:
awk '{ gsub(/([[:alpha:]]+|[[:digit:].-]+|[^[:alnum:].-]+)/,"&\n",$0) ; printf $0 }' filename
Those characters must appear in both the [[:digit:].-]+
and [^[:alnum:].-]+
expressions. Also, to be interpreted as a literal hyphen, the -
must be last character before the final right square bracket of each expression; otherwise, it indicates a range of characters.
Example:
[test]$ cat file.txt
string123another!!str456.001thenanotherstr-789
[test]$ awk '{ gsub(/([[:alpha:]]+|[[:digit:].-]+|[^[:alnum:].-]+)/,"&\n",$0) ; printf $0 }' file.txt
string
123
another
!!
str
456.001
thenanotherstr
-789
If the input file requires it, you could modify the awk
command to:
-
only counts as part of a number if it occurs at the start of a numeral sequence.printf
is the format, you should use $0
there. Use printf "%s", $0
Jan 9, 2018 at 11:20
POSIXly:
string=string123anotherstr456thenanotherstr789
sed '
s/[^[:alnum:]]//g; # remove anything other than letters and numbers
s/[[:alpha:]]\{1,\}/&\
/g; # insert a newline after each sequence of letters
s/[0-9]\{1,\}/&\
/g; # same for digits
s/\n$//; # remove a trailing newline if any' << EOF
$string
EOF
3.14
becomes two output lines (3
and 14
) and G-Man@stack
becomes three (G
, Man
and stack
). Your answer ignores them, so those inputs yield one output each (314
and GManstack
).
Jan 9, 2018 at 19:51
GNU sed
(or compatible) solution:
s="string123anotherstr456thenanotherstr789"
sed 's/[a-zA-Z]*\|[0-9]*/&\n/g; s/\n$//' <<<"$s"
The output:
string
123
anotherstr
456
thenanotherstr
789
python3
python3 -c '
from itertools import groupby
s = ("".join(g) for k, g in
groupby("string123anotherstr456thenanotherstr789", lambda x: x.isalpha()))
print(*s, sep="\n")
'
string
123
anotherstr
456
thenanotherstr
789
Used below one liner to achieve the same. As tested its worked fine
sed "s/[0-9]\{3\}/\n&/g" filename | sed "s/[0-9]\{3\}/&\n/g"| sed '/^$/d'
output
string
123
anotherstr
456
thenanotherstr
789
printf 'string\n123\nanotherstr\n456\nthenanotherstr\n789\n'
. I admit that the question is unclear, in the sense that it is incomplete, but the string presented in the question is clearly labeled as an example. I see no reason to assume that every number that will ever be in the input will be a three-digit number.
Jan 9, 2018 at 4:14
s/.../\n&\n/g
. I also don't see why insist on the numbers being exactly three digits long, since it's trivial to make a more general solution.
I didn't see a Perl solution yet, so here:
$ cat s
string123anotherstr456thenanotherstr789
$ perl -lne 'print $& while /[[:alpha:]]+|[[:digit:]]+/g' < s
string
123
anotherstr
...
Of course, for wider definitions of "numbers", we might want to use
[-+]?[0-9]+
(leading sign), [-+]?[0-9]+(.[0-9]+)?
(plus optional fractional part), or even [-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?
(plus
optional exponent). The latter two ones require at least one digit before and after the decimal point, if it is present.
This is relatively inefficient because it makes several (shorter) copies of the original string:
declare s=string123anotherstr456thenanotherstr789
while [[ "$s" =~ ^([a-z]+)([0-9]+) ]]; do
echo ${BASH_REMATCH[1]}
echo ${BASH_REMATCH[2]}
s="${s:${#BASH_REMATCH[0]}}"
done
How many letters-digits pairs per line are you dealing with?
gawk '{ $1 = $1; print }' FPAT='[a-z]+|[0-9]+' OFS='\n' input.txt
Testing
gawk '{ $1 = $1; print }' FPAT='[a-z]+|[0-9]+' OFS='\n' <<< 'string123anotherstr456thenanotherstr789'
Output
string
123
anotherstr
456
thenanotherstr
789
3.14
and-1
qualify as numbers?