so, I have files with text formatted like this:

untranslatedString : "translated string",

and I need to replace characters in "translated string" part with their Cyrillic representation. I use something like this:

paste <(sed 's/\([^:]\+:\)\([^:]\+\)/\1/' resources.js) <(sed 's/[^:]\+:\([^:]\+\)/\1/;y/abc/абц/' resources.js)

(abc/абц/ part is actually longer and includes all characters, this is for illustrative purposes).

problem arises in lines like this one:

abcTestString : "abc {ccb} bbc",

everything between {} should be left in it's original state, ie. character shouldn't be replaced. result should be:

abcTestString : "aбц {ccb} ббц",

and not

abcTestString : "aбц {ццб} ббц",

Also, there can be multiple {} parts per line.

How can I do that?

3 Answers 3


If you are okay with using perl

$ s='abcTestString : "abc {ccb} bbc",'
$ echo "$s" | perl -Mopen=locale -Mutf8 -F: -lane '
               print join ":",@F'
abcTestString : "абц {ccb} ббц",
  • -Mopen=locale -Mutf8 unicode settings (thanks to this wonderful answer tr analog for unicode characters?)
  • -F: -lane use : as field separator, saved in @F array (See https://perldoc.perl.org/perlrun.html#Command-Switches for other options)
  • $F[-1] last field of @F array
  • \{[^{}]+\}(*SKIP)(*F)|[a-z]+ here we say that [a-z]+ portion has to match but \{[^{}]+\} should be left as it is
  • $&=~tr|abc|абц|r perform transliteration for the matched portion
  • ge the g modifier for replacing all matches, e modifier to allow the Perl code in replacement section

If this is too big a code to handle from command line, change it to a program

$ echo "$s" | perl -MO=Deparse -Mopen=locale -Mutf8 -F: -lane '
               print join ":",@F'
BEGIN { $/ = "\n"; $\ = "\n"; }
use open (split(/,/, 'locale', 0));
use utf8;
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    our @F = split(/:/, $_, 0);
    $F[-1] =~ s[\{[^{}]+\}(*SKIP)(*F)|[a-z]+][use utf8 ();
    $& =~ tr/abc/\x{430}\x{431}\x{446}/r;]eg;
    print join(':', @F);

If you want to stay with sed:

sed 's/"/"_/;:l

An underscore is used as mark during processing. If underscores can be part of the file, use a different delimiter.

The idea is to mark a part of the string, save a copy to hold space, remove everything outside the marks, do your conversion, get back the copy, compose a string with the converted part and move the underscore forward. If there is a part in {}, skip that.

I can give a more detailed explanation, if you are interested.

  • Hmm, I am having problem with this line: /_$/!bl Dec 4, 2017 at 15:28
  • The underscore will will moved to the right after each part. If it is not yet moved to the end of the string (/ _$/!), jump to the start of the loop (bl means branch to mark :l).
    – Philippos
    Dec 4, 2017 at 15:31
sed -rf <(echo ':l'; printf 's/("| [^{]*)%s/\\1%s/g\n' a а b б c ц; echo 'tl') input.txt

Or more concisely, but also more prone to errors, because the sed's expression is enclosed in double quotes, not in single quotes, so bash special characters should be escaped, for example the $ sign.

sed -r ":l; $(printf 's/("| [^{]*)%s/\\1%s/g;' a а b б c ц) tl" input.txt

Explanation (the first command were taken)

sed -f script-file - add the contents of script-file to the commands to be executed.

<() - process substitution. It allows represent the commands output as a file and pass it to the command, which is expecting a file.

echo ':l'; printf 's/("| [^{]*)%s/\\1%s/g\n' a а b б c ц; echo 'tl' - it is converting to the next sed's commands sequence:

s/("| [^{]*)a/\1а/g
s/("| [^{]*)b/\1б/g
s/("| [^{]*)c/\1ц/g



abcTestString : "abc {bcb} bbc",
abcTestString : "bbc {acb} bbc {bcb}",
abcTestString : "acc {cab} {ccb} bbc",
abcTestString : "cbc {ccb} bac {aca} bac",


abcTestString : "абц {bcb} ббц",
abcTestString : "ббц {acb} ббц {bcb}",
abcTestString : "ацц {cab} {ccb} ббц",
abcTestString : "цбц {ccb} бац {aca} бац",

You must log in to answer this question.

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