Here is my code:

printf '%s' '{$aaa} \\{$bbb} \{$ccc}  {$ddd' | sed -r -e 's/(^|[^\\])\{\$/\1\$\{/g' -e 's/\\\\\{\$/\\\\\$\{/g'

and the result:

${aaa} \\${bbb} \{$ccc} ${ddd

The result is OK except for the last pattern, {$ddd, that shouldn't be replaced because it doesn't end with a closing brackets }.

How can I do that with sed?

Also if you know how to merge the two actual expressions, it's also welcomed (not that important to me)

Edit: note that the same is also done for the parentheses, ($foo) to $(foo)

  • 1
    can you have input like {$foo} {$abc {$bar} and can there be spaces inside {$...} – Sundeep May 1 '17 at 14:11
  • For your question, yes. There can be virtually any characters between {$ and } – Jun May 1 '17 at 15:38
  • can there be { and/or } characters inside? i.e nesting... also, what should be output for {$foo} {$abc {$bar} ? – Sundeep May 1 '17 at 15:41
  • Good catch. Thus inside {$...} there should only have [aZ-_] characters. This will fix the issue? – Jun May 1 '17 at 16:25

Starting with the first task, only perform the change for closed {}:

printf '%s' '{$aaa} \\{$bbb} \{$ccc}  {$ddd' | sed 's/{$\([^}]*\)}/${\1}/g'

This is, everything after the {$ up to the closing } is put in \(\), so we can backreference it in the replacement string as \1. Output:

${aaa} \\${bbb} \${ccc}  {$ddd

Now, about the backslash-escaping. Your solution works for \{$aaa} (don't touch) and \\{$aaa} (replace), but what about \\\{$aaa}? It will perform the change, which is probably not desired.

So I suggest to get rid of all double-backslashes first. We replace them by a character combination that cannot be part of the string, #1 as an example, and change it back in the end. This way we can concentrate on the single backslash only:

printf '%s' '{$aaa} \\{$bbb} \{$ccc} \\\{$eee} {$ddd' | sed 's/\\\\/#1/g;s/{$\([^}]*\)}/${\1}/g;s/#1/\\\\/g'

And finally we can change \{ to #2 and replace it back at the end to get it out of our way:

printf '%s' '{$aaa} \\{$bbb} \{$ccc} \\\{$eee} {$ddd' | sed 's/\\\\/#1/g;s/\\{/#2/g;s/{$\([^}]*\)}/${\1}/g;s/#2/\\{/g;s/#1/\\\\/g'

Result: ${aaa} \\${bbb} \{$ccc} \\\{$eee} {$ddd

And this will work for any number of backslashes.

But if there is no character like # you can freely use? You can use the newline, because a newline can't be part of a line. Since you seem to use GNU sed, you can do

printf '%s' '{$aaa} \\{$bbb} \{$ccc} \\\{$eee} {$ddd' | sed 's/\\\\/\n1/g;s/\\{/\n2/g;s/{$\([^}]*\)}/${\1}/g;s/\n2/\\{/g;s/\n1/\\\\/g'

If this should run on a POSIX sed, you need a workaround

  • Thank you for this nice explanation, your answer do perfectly what I would like! For the POSIX compliance, as long as it works on busybox's sed (which is the case), all's good. – Jun May 1 '17 at 18:26
  • Also, do you know how to do the same with parentheses in the same expression? If not, no worry, I can simply add an other expression with -e. – Jun May 1 '17 at 18:32
  • I've found myself thanks to your answer: sed 's/\\\\/\n1/g;s/\\(/\n2/g;s/($\([^)]*\))/$(\1)/g;s/\n2/\\(/g;s/\\{/\n2/g;s/{$\([^}]*\)}/${\1}/g;s/\n2/\\{/g;s/\n1/\\\\/g' – Jun May 5 '17 at 21:42

Using POSIX sed you can do your task as follows:

sed -e '
   # case for {$var}
   s/ \(\(\\\\\)\{0,\}\){\$\([^ }]*\)}/ \1${\3}/g
   s/^\(\(\\\\\)\{0,\}\){\$\([^ }]*\)}/\1${\3}/

   # case for ($var)
   s/ \(\(\\\\\)\{0,\}\)(\$\([^ )]*\))/ \1$(\3)/g
   s/^\(\(\\\\\)\{0,\}\)(\$\([^ )]*\))/\1$(\3)/
' input_file

Assuming spaces the only whitespace in the file. Of course, we can tackle with TAB and leave that as an exercise for the OP. With the extensions available in GNU sed we can further simplify the above, but I wanted to give a POSIX solution to give it greater applicability.

  • I also care like you about wide availability across POSIX systems, thank you for your alternative answer. – Jun May 2 '17 at 22:04

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.