4

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
2
  • Does it need to be sed or are you open to other tools? If so, do you have perl installed?
    – terdon
    Jan 11, 2016 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. Jan 11, 2016 at 9:29

2 Answers 2

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/'
8
  • 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. Jan 11, 2016 at 10:03
  • .*[^0-9]\] mean any symbol(s) till the non-digit char with square bracket after it
    – Costas
    Jan 11, 2016 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]. Jan 11, 2016 at 10:23
  • Have updated test case to include non-digits within square brackets -- apologies for not considering that case before. Jan 11, 2016 at 10:27
  • $sq was stripped becouse variable expansion (due to double-quotes)
    – Costas
    Jan 11, 2016 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.

2
  • Yep, this too seems to work. It leaves the initial { and trailing } but that is minor. Jan 11, 2016 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, 2016 at 21:35

You must log in to answer this question.

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