0

I have a CSV file in which I have thousands of lines like this:

bla bla blab [FR] john is bla bla
bla [US] blue house in Chicago...
[ES] accessing the safe... bla bla

See the elements between [ ]? These elements are always followed by a space and the start of a new phrase. I want to convert the first letter found after the space to uppercase, making the file like

bla bla blab [FR] John is bla bla
bla [US] Blue house in Chicago...
[ES] Accessing the safe... bla bla

How do I do that from terminal, using, sed, awk or whatever?

Please explain the solution, I want to understand and learn.

Thanks.

3
  • 1
    Can the [..] be the last word in a line? If so, should the word in next line be handled? – Inian Oct 22 '20 at 8:20
  • no. [..] is never the last thing in a line. There is always a space and a phrase starting after that. – Duck Oct 22 '20 at 8:22
  • The title asks for the first letter after a character. So what about [US] 12 people were found...? Should p become uppercase? – Quasímodo Oct 22 '20 at 10:23
4

If on a GNU system, you can use sed:

sed 's/] ./\U&/g' <infile

stream editor 'substitute/replace-what/replace-with/globally' <inputfile

Here "replace-what" is a literal ] followed by a single space then a single character (. matches a single character except \newline but would match a \newline character if it was ever found in the pattern space. In perl, it doesn't unless the s flag is added to the regex.)

"replace-what" is \U&, in sed & is back-reference to "replace-what" part (\U stands for \Upper-case), so \U& will replace matched part to Upper-case.


If your sed is not GNU sed, you could do the same with perl with:

perl -Mopen=locale -pe 's/] ./\U$&/g' < infile
5
  • Not working for me. It is converting things like [FR] into [FRU] and removing what is inside the brackets for other cases. – Duck Oct 22 '20 at 8:36
  • yep, macOS. sed --version = illegal option on macOS. I am on Catalina. – Duck Oct 22 '20 at 8:40
  • ahhhhhh, the perl version works wonderfully. Thanks. – Duck Oct 22 '20 at 8:41
  • 1
    In sed, . would match a newline character if it was ever found in the pattern space. In perl, it doesn't unless the s flag is added to the regex. – Stéphane Chazelas Oct 22 '20 at 8:42
  • 1
    @StéphaneChazelas thanks for perl command and correction as well as explanation on . match in sed. I have added into body of answer, thanks – αғsнιη Oct 22 '20 at 8:45
6

POSIXly, you could do:

awk -F '] ' '{
  out = $1
  for (i = 2; i <= NF; i++)
    out = out FS toupper(substr($i, 1, 1)) substr($i, 2)
  print out}' < infile

Or:

awk '
  BEGIN {FS = OFS = "] "}
  {
    for (i = 2; i <= NF; i++)
      $i = toupper(substr($i, 1, 1)) substr($i, 2)
    print
  }' < infile

That defines "] " as the Field Separator, and we join the fields back together after having converted the first character of each field (starting from the 2nd and up to NF the Number of Fields) to uppercase.

(beware however that a few awk implementations including mawk and FreeBSD awk (so possibly macOS' as well) have not been internationalised, so would fail to change [GR] π into [GR] Π (or [FR] éric into [FR] Éric if é was expressed in its pre-composed form (U+00E9)).

1

First, you can set FS: -v FS="[]][[:space:]]+" i.e. []] followed by [[:space:]]+. With this you can get the field for making changes:

$ awk -v FS="[]][[:space:]]+" '{print  $2}' file
john is bla bla
blue house in Chicago...
accessing the safe... bla bla

Here you "want to convert the first letter found after the space to uppercase", so you can test:

awk -v FS="[]][[:space:]]+" '{print toupper(substr($2,1,1))substr($2,2)}'  file

John is bla bla
Blue house in Chicago...
Accessing the safe... bla bla

and print all:

$ awk -v FS="[]][[:space:]]+" '{print $1, toupper(substr($2,1,1))substr($2,2)}'  file
bla bla blab [FR John is bla bla
bla [US Blue house in Chicago...
[ES Accessing the safe... bla bla

but you see a problem: the separator's chars are missing. In these cases we can use the 4th argument of the split() function and insert FS in the function. So with this you can get changes and the desired output:

awk -v FS="[]][[:space:]]+" '                                              
{
split($0, a, FS, seps)
a[i]=$1
a[2]= toupper(substr($2,1,1))substr($2,2)
for (i=1; i<=NF; i++)
printf "%s%s", a[i], seps[i]
print ""
  }
' file
bla bla blab [FR] John is bla bla
bla [US] Blue house in Chicago...
[ES] Accessing the safe... bla bla

Thanks to Ed Morton code: https://stackoverflow.com/a/22211988 Here you can see details.

Not the answer you're looking for? Browse other questions tagged or ask your own question.