4

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

If you are okay with using perl

$ s='abcTestString : "abc {ccb} bbc",'
$ echo "$s" | perl -Mopen=locale -Mutf8 -F: -lane '
               $F[-1]=~s/\{[^{}]+\}(*SKIP)(*F)|[a-z]+/$&=~tr|abc|абц|r/ge;
               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 '
               $F[-1]=~s/\{[^{}]+\}(*SKIP)(*F)|[a-z]+/$&=~tr|abc|абц|r/ge;
               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);
}
3

If you want to stay with sed:

sed 's/"/"_/;:l
  s/_[^{]*/&_/;h
  s/.*_\(.*\)_.*/\1/
  y/abc/абц/;G
  s/\(.*\)\n\(.*\)_.*_\([^}]*}\)\{0,1\}/\2\1\3_/
  /_$/!bl
  s/_//'

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 – Vanja Grujic Dec 4 '17 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 '17 at 15:31
3
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:

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

Testing:

Input

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

Output

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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