linux – Limit Original Pattern Space to Matched String When Piping

linuxsedstring

I have a text file where I want to replace all spaces inside [[ and ]] with hyphens (brackets are never nested and always matched). The following is an example:

$ cat test.txt 
abc [[foo]] xyz
abc [[foo bar]] xyz
abc [[foo bar baz]] xyz [[something else]]

So the desired output is:

abc [[foo]] xyz
abc [[foo-bar]] xyz
abc [[foo-bar-baz]] xyz [[something-else]]

I thought I could use sed to match the string inside the brackets and then use the e flag to run the result through sed again to make the replacement. However the problem is that not only the matched string gets executed as a command, but the whole pattern space (which seems to be the entire line):

$ sed -E 's@(\[\[)(.+)(\]\])@sed -e "s/ /-/g" <<< "\1\2\3"@gpe' test.txt 
abc sed -e "s/ /-/g" <<< "[[foo]]" xyz
sh: 1: Syntax error: redirection unexpected

abc sed -e "s/ /-/g" <<< "[[foo bar]]" xyz
sh: 1: Syntax error: redirection unexpected

abc sed -e "s/ /-/g" <<< "[[foo bar baz]]" xyz
sh: 1: Syntax error: redirection unexpected

Is there a way to limit what gets executed via the e flag to the matched string? If not, how would I solve this problem with sed?

Best Answer

I don't think there is a way to limit what's passed to the shell by the e modifier; however you could do something like this:

$ sed -E ':a;s@(.*\[\[)([^][]* [^][]*)(\]\].*)@printf "%s%s%s" "\1" "$(printf "\2" | sed "s/ /-/g")" "\3"@e;ta' test.txt
abc [[foo]] xyz
abc [[foo-bar]] xyz
abc [[foo-bar-baz]] xyz [[something-else]]

Note that handling of multiple replacements is done via a loop - and due to the greediness of the match, it actually makes the substitutions in reverse order.

Note also that e uses /bin/sh which will likely not support the <<< input redirection (hence the use of the piped equivalent printf "\2" | sed "s/ /-/g").


If perl is an option you could do something closer to your original intent ex.:

$ perl -pe 's/(?<=\[\[)(.*?)(?=\]\])/$1 =~ s: :-:rg/ge' test.txt
abc [[foo]] xyz
abc [[foo-bar]] xyz
abc [[foo-bar-baz]] xyz [[something-else]]

Since perl provides a non-greedy modifier ?, this can handle multiple replacements per line more conventionally using the g flag on the outer substitution.

Related Question