How can I add a character to the beginning of every column that starts with a number:
Example adding a c
to:
4 r4 8 8 4
So I get this output:
c4 r4 c8 c8 c4
It should work for a variable number of columns.
You can use sed to modify every column at once :
sed -E 's/(^| )([[:digit:]])/\1c\2/g' input
If your field separator is anything else than a space replace the space (^| )
in this part of the regular expression with your field separator.
-E
specify to use Extended Regular Expression (ERE) instead of Basic Regular expression (BRE)s
: s/regexp/replacement/
(^| )
match the beginning of a line or a space in a capture group([[:digit:]])
match a digit in a capture group\1c\2
the content of the first capture group (\1
), the character c
, the content of the second capture group (\2
)g
globallyOnce the result satisfies you and if your sed
implementation supports it, you may add -i
(-i ''
on FreeBSD or derivatives such as macos) to modify the file in-place. (-i.bak
if you want to make a backup of your original file)
s/\b([0-9])/c\1/g
. But not exactly equivalent if columns can begin with a non-alphanumeric symbol
I would use an awk
-oneliner like this:
echo '4 r4 8 8 4' | awk '{ for (i=1; i<=NF; i++) if ($i ~ /^[0-9]/) $i = "c" $i }1'
Hope that helped :)
Using Raku (formerly known as Perl_6)
~$ raku -pe 's:g / <(^ | \s+)> \d /{$/}c/;' file
#OR
~$ raku -pe 's:g / (^ | \s+) (\d) /$0c$1/;' file
Here are answers coded in Raku, a member of the Perl-family of programming languages. Above, the file is read with the -pe
linewise autoprinting flags. The substitution will be :g
global and says: "If the start-of-line ^
or \s+
whitespace is encountered followed by a \d
digit, substitute."
In the first example the match after whitespace is dropped using Raku's <(…)>
capture markers. The resultant $/
match-object is output in the replacement, followed by the letter c
. In the second example, the entire match is contained in two captures ($0
and $1
), and output in the replacement as $0c$1
.
Sample Input:
4 r4 8 8 4
Sample Output:
c4 r4 c8 c8 c4
Alternatively (and much more explicitly), below will "regularize" whitespace between columns:
~$ raku -ne 'put .split(/ \s+ /).map: *.subst(/^ \d /, {"c$/" // ""});' file
#OR
~$ raku -ne 'put .split(/ \s+ /).map: *.subst(/^ (\d) /, {"c$0" // ""});' file
First the input line is .split
on variable whitespace (\s+
), then each column is map
ped into in order to subst
itute any columns ^
starting with a \d
, adding a c
before the match (here using $/
match-object or $0
). If the match is undefined (using Raku's //
"defined-OR" operator), insert ""
nothing. Columns are returned with a single whitespace between them.
https://docs.raku.org/language/regexes#Regexes
https://raku.org
One way would be to use a regular expression to test if each field starts with a number:
echo '4 r4 8 8 4' | awk '
{
for (i=1; i<=NF; i++)
if ($i ~ /^[0-9]+/)
printf "c%s%s", $i, i == NF ? ORS : OFS
else
printf "%s%s", $i, i == NF ? ORS : OFS
}'
output:
c4 r4 c8 c8 c4
for ... printf "%s%s", ($i ~ /^[0-9]+/ ? "c" : "")$i, i == NF ? ORS : OFS
.
Commented
Dec 12, 2023 at 18:01
echo "4 r4 8 8 4" | sed -E 's/\<([[:digit:]])/c\1/g'
a-2.3 4.5
, that would yield a-c2.c3 c4.c5
. You could also simplify it to sed 's/\<[[:digit:]]/c&/g'
. Also beware the \<
is not standard, -E will soon be standard. Some alternatives to \<
(not standard either) you may find with other sed
implementations are \b
(from perl) or [[:<:]]
on some BSDs IIRC
Commented
Dec 13, 2023 at 6:57
Using Miller (mlr
) to apply a function that prepends the character c
to a field's value, if it is a number:
$ mlr --nidx put '$* = apply($*, func(k,v) { ch = (is_numeric(v) ? "c" : ""); return { k: ch . v } })' file
c4 r4 c8 c8 c4
This uses Miller to read the data as a Unix toolbox-formatted data set (newline-delimited records with space-delimited fields) and applies a put
expression to each record.
The put
expression uses apply()
to apply an anonymous function to each field. The function tests the field's value, and if it is numeric, it prepends the value with c
, otherwise it leaves it unmodified.
The is_numeric()
test is true for numbers, not just digits. This is what the question specifically asks for:
$ cat file
4 r4 8.8 8e7 -4 9hello
$ mlr --nidx put '$* = apply($*, func(k,v) { ch = (is_numeric(v) ? "c" : ""); return { k: ch . v } })' file
c4 r4 c8.8 c8e7 c-4 9hello
Would you want to test for an initial digit, simply replace the is_numeric()
test with a test for an initial digit:
$ cat file
4 r4 8.8 8e7 -4 9hello
$ mlr --nidx put '$* = apply($*, func(k,v) { ch = (v =~ "^[[:digit:]]" ? "c" : ""); return { k: ch . v } })' file
c4 r4 c8.8 c8e7 -4 c9hello
Using gawk
:
$ awk '{$0=gensub(/(^|\s+)([0-9]+)/,"\\1c\\2", "g")}1'
# The command for numbers instead of digits only
$ awk -v var="c" '{for(i=1;i<=NF;i++){ $i = ($i==$i+0 ? var : "") $i}}1'
sed 's/\<[0-9]/c&/g'
would also work for the given sample input...