5

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.

1
  • 3
    sed 's/\<[0-9]/c&/g' would also work for the given sample input...
    – Sundeep
    Commented Dec 12, 2023 at 10:04

7 Answers 7

11

You can use 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 globally

Once 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)

1
  • Shorter form: s/\b([0-9])/c\1/g. But not exactly equivalent if columns can begin with a non-alphanumeric symbol
    – meden
    Commented Dec 14, 2023 at 18:12
5

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 :)

3

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 mapped into in order to substitute 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

2

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
1
  • 2
    May be shortened to for ... printf "%s%s", ($i ~ /^[0-9]+/ ? "c" : "")$i, i == NF ? ORS : OFS. Commented Dec 12, 2023 at 18:01
2
    echo "4 r4 8 8 4" | sed -E 's/\<([[:digit:]])/c\1/g'
1
  • 1
    Note that in that case the columns are assumed to be separated by anything that is not a word character. So for instance on 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
2

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
0
2

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'

You must log in to answer this question.

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