convert snake case to camel case until a special character with sed

Viewed 123

I'm trying to convert the keys of Localizable.strings from snake case to camel case on a Mac. I can use gsed since it supports \U for uppercase.

My files looks like this:

/* Title  */
"home.title" = "Welcome";

/* Email  */
"home.signup_email" = "Email";

/* recover_email  */
"home.signup_email_recover" = "Recover Email";

/* password  */
"home.password" = "Enter your __Password__:";

I'm expecting to modify this file to obtain this:

/* Title  */
"home.title" = "Welcome";

/* Email  */
"home.signupEmail" = "Email";

/* recover_email  */
"home.signupEmailRecover" = "Recover Email";

/* password  */
"home.password" = "Enter your __Password__:";

I tried using gsed with something like this but unfortunately, I'm not able to stop at after the = character.

find . -name "*.strings" | xargs gsed -i -e '/=/! s/_\([a-z]\)/\U\1/gi'

Any idea on how to stop replacing _ after =?

Thanks!

3 Answers

You can use

sed ':a;s/^\([^=]*\)_\([[:alpha:]]\)/\1\U\2\E/;ta'

You may also use [[:lower:]] instead of [[:alpha:]] if you want to only replace _ + lowercase letters.

Details:

  • :a - set an a label
  • s/^\([^=]*\)_\([[:alpha:]]\)/\1\U\2\E/ - find and capture into Group 1 any zero or more chars other than a = char, then match a _ and then capture any letter into Group, and replace with Group 1 + an uppercased letter in Group 1
  • ta - if there was a successful replacement, jump back to a label position.

See the online demo:

#!/bin/bash
s='/* Title  */
"home.title" = "Welcome";
 
/* Email  */
"home.signup_email" = "Email";
 
/* recover_email  */
"home.signup_email_recover" = "Recover Email";
 
/* password  */
"home.password" = "Enter your __Password__:";

/* One more string */
"my_string_key" = "[Click here](https://my.url.com/deeplink?id=UUID&section_id=foo)";'
sed ':a;s/^\([^=]*\)_\([[:alpha:]]\)/\1\U\2\E/;ta' <<< "$s"

Output:

/* Title  */
"home.title" = "Welcome";
 
/* Email  */
"home.signupEmail" = "Email";
 
/* recoverEmail  */
"home.signupEmailRecover" = "Recover Email";
 
/* password  */
"home.password" = "Enter your __Password__:";

/* One more string */
"myStringKey" = "[Click here](https://my.url.com/deeplink?id=UUID&section_id=foo)";

A bit verbose but here is a non-regex approach using default BSD awk available on OSX:

awk '
BEGIN {FS=OFS="="}
NF == 2 && (n = split($1, a, /_/)) {
   s = a[1]
   for (i=2; i<=n; ++i)
      s = s toupper(substr(a[i], 1, 1)) substr(a[i], 2)
   $1 = s
} 1' file

/* Title  */
"home.title" = "Welcome";

/* Email  */
"home.signupEmail" = "Email";

/* recover_email  */
"home.signupEmailRecover" = "Recover Email";

/* password  */
"home.password" = "Enter your __Password__:";

if you don't mind a gnu gawk-specific solution :

           # always true condition whether
           # ORS contains empty string
           #
  gawk    '(ORS=toupper(substr(RT,2)))~_'     RS='_[a-z]' file
or
           #     +ORS is always 0/false,
           # so !+ORS is always 1/ true
           #
  gawk  '!+(ORS=toupper(substr(RT,2)))'       RS='_[a-z]' file
or
  gawk 'sub(/$/,toupper(substr(RT,2)))' ORS=  RS='_[a-z]' file
or
           # this just means 
           # 0-to-0th-power
           #
  gawk 'FS^(ORS=toupper(substr(RT,2)))'       RS='_[a-z]' file  
or
           # ASCII string-compare that is always true since
           # underscore "_" has higher byte ordinance than
           # either empty string or any ASCII uppercase letters
           #
  gawk '(ORS=toupper(substr(RT,2)))<RS'       RS='_[a-z]' file

=

/* Title  */
"home.title" = "Welcome";

/* Email  */
"home.signupEmail" = "Email";

/* recoverEmail  */
"home.signupEmailRecover" = "Recover Email";

/* password  */
"home.password" = "Enter your __Password__:";
Related