2

From a pattern such as

[string 1]{string 2}

I want to extract string 2, the string between the last pair of matching curly braces -- that is delete [string 1] and the open { and close }. My attempt below breaks when there is a additional [, ] pairs in either string 1 or string 2.

Desired Output:

The desired output from the script below begins with foo and ends with a digit:

foo bar 1
foo bar 2
foo[3]{xyz} bar 3
foo $sq[3]{xyz}$ bar 4
foo $sq[3]{xyz}$ bar 5
foo $sq[3]{xyz}$ bar 6
foo $sq[3]{xyz}$ bar 7
foo $sq[3]{xyz}$ bar 8'
foo $sq[abc]{xyz}$ bar 9'
foo $sq[abc]{xyz}$ bar 10'

Assumptions:

  • Parameter to RemoveInitialSquareBraces always begins with a [ and ends with a }.
  • The opening [ for string 1 will have a matching ] at the point where the opening { begins for string 2.

Platform:

  • MacOS 10.9.5

Script

#!/bin/bash

function RemoveInitialSquareBraces {
    #EXTRACTED_TEXT="$(\
    #      echo "$1" \
    #    | sed 's/^\[.*\]//'              \
    #    | sed 's/{//'                    \
    #    | sed 's/}$//'                   \
    #    )"
    EXTRACTED_TEXT="$(\
          echo "$1" \
        | sed 's/.*[^0-9]\]{\(.*\)}/\1/' \
        )"

    echo "${EXTRACTED_TEXT}"
}

RemoveInitialSquareBraces '[]{foo bar 1}'
RemoveInitialSquareBraces '[abc]{foo bar 2}'
RemoveInitialSquareBraces '[]{foo[3]{xyz} bar 3}'
RemoveInitialSquareBraces '[]{foo $sq[3]{xyz}$ bar 4}'
RemoveInitialSquareBraces '[goo{w}]{foo $sq[3]{xyz}$ bar 5}'
RemoveInitialSquareBraces '[goo[3]{w}]{foo $sq[3]{xyz}$ bar 6}'
RemoveInitialSquareBraces '[goo[3]{w} hoo[3]{5}]{foo $sq[3]{xyz}$ bar 7}'
RemoveInitialSquareBraces '[goo[3]{w} hoo[3]{5}]{foo $sq[3]{xyz}$ bar 8}'
RemoveInitialSquareBraces '[goo[3]{w} hoo[xyz]{5}]{foo $sq[abc]{xyz}$ bar 9}'
RemoveInitialSquareBraces '[goo[3]{w} hoo[xyz]{uvw}]{foo $sq[abc]{xyz}$ bar 10}'

exit 0
  • Does it need to be sed or are you open to other tools? If so, do you have perl installed? – terdon Jan 11 '16 at 9:27
  • @terdon: Yes I do have perl installed, but this is a piece of a larger bash script, so would prefer a sed solution. But if it turns out too difficult to do with sed ten I'll consider alternate solutions. – Peter Grill Jan 11 '16 at 9:29
2

Regarding to above input examples the script can be:

sed s/[^\"\']*[^0-9]\]{\(.*\)}/\1/ <<\END
"[]{foo bar 1}"
"[abc]{foo bar 2}"
"[]{foo[3]{xyz} bar 3}"
"[]{foo $sq[3]{xyz}$ bar 4}"
"[goo{w}]{foo $sq[3]{xyz}$ bar 5}"
"[goo[3]{w}]{foo $sq[3]{xyz}$ bar 6}"
"[goo[3]{w} hoo[3]{5}]{foo $sq[3]{xyz}$ bar 7}"
END

produces

"foo bar 1"
"foo bar 2"
"foo[3]{xyz} bar 3"
"foo $sq[3]{xyz}$ bar 4"
"foo $sq[3]{xyz}$ bar 5"
"foo $sq[3]{xyz}$ bar 6"
"foo $sq[3]{xyz}$ bar 7"

Other thing is your function which can be simplified:

function RemoveInitialSquareBraces {
    printf '%s\n' "$@" |
    sed ...
}

thus it will accept many argument(s).

Update: for more general case you can do the task in two steps:

sed -e "
s/\[.*\[.*\][^[]*\]/[]/  #remove square brackets inside square brackets
s/\[[^]]*\]{\(.*\)\}/\1/ #lazy strip square brackets and curle brackets
"

Addition: you can use perl-grep(GNU grep with perl extention):

grep -Po '\[([^][]*\[\w+\][^][]*)*\]{\K.*(?=})'

or sed with same regexp:

sed 's/\[\([^][]*\(\[\w\+\][^][]*\)*\)*\]{\(.*\)}/\3/'
  • It seems I need to use single quotes not double quotes in the script. Then things do work for the given test cases, but an explanation of .*[^0-9]\] would be helpful. – Peter Grill Jan 11 '16 at 10:03
  • .*[^0-9]\] mean any symbol(s) till the non-digit char with square bracket after it – Costas Jan 11 '16 at 10:16
  • 1
    So, does that mean that this relies on there begin only digits within the square braces? Also, your output has stripped off the $sq before the [3]. – Peter Grill Jan 11 '16 at 10:23
  • Have updated test case to include non-digits within square brackets -- apologies for not considering that case before. – Peter Grill Jan 11 '16 at 10:27
  • $sq was stripped becouse variable expansion (due to double-quotes) – Costas Jan 11 '16 at 10:28
2

This works for your input:

sed -Ee's/.*(\{[^}]*(\{[^{}]*\}[^{]*)*\})/\1/'

{foo bar 1}'
{foo bar 2}'
{foo[3]{xyz} bar 3}'
{foo $sq[3]{xyz}$ bar 4}'
{foo $sq[3]{xyz}$ bar 5}'
{foo $sq[3]{xyz}$ bar 6}'
{foo $sq[3]{xyz}$ bar 7}'
{foo $sq[3]{xyz}$ bar 8}'
{foo $sq[abc]{xyz}$ bar 9}'
{foo $sq[abc]{xyz}$ bar 10}'

It just removes everything before the last occurrence on a line of a matched pair of curlies which might fully contain any number of other matched curly pairs.

  • Yep, this too seems to work. It leaves the initial { and trailing } but that is minor. – Peter Grill Jan 11 '16 at 21:26
  • At Peter Grill - drop them. Leave them outside the ( ) group: sed -Ee 's/.*\{([^}]*(\{[^{}]*\}[^{]*)*)\}/\1/'. Know, though, that it doesn't work to infinite depth. There isn't a mathematical equation for that. This selects the last } on a line and matches it to its paired { so long as that within the { and } only any number of { which are not followed again by a { before a } occurs. And so { ... { .. } ... { ... } ... { ... } ...} will match fine, but { ... { ... { ... } ... } ... } will not. You have to specify the depth to which you wish to go, and allow for it. – mikeserv Jan 11 '16 at 21:35

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.