8

I want to empty the content of every HTML tag but "keeping the structure".

From:

<h5>Holdrs <div class="tooltip" data-tooltip="Accounts with ..."></div></h5>
<div class="value">
  <span class="amount">25,241</span><a class="smallnav" href="/c/token/0xB31f66AA3C1e785363F0875A1B7"><svg class="icon-s icon">

I want to get:

<>Holdrs <><><>
<>
  <>25,241<><><>

From my understanding of sed this should be:

sed 's/<.*>/<>/'

but it only returns:

<>
<>
  <>

(Tested here: https://sed.js.org/?gist=7af9c1c1762a6a93d582502b3d4fe22f).

What I'm doing wrong? What's the correct pattern?

2
  • 13
    Note that attempting to manipulate HTML with regular expressions usually ends badly. At some point you'll have a < or > in an attribute, a > in the text, or some other thing you hadn't thought of. The right way to manipulate HTML is to use an HTML parser which can keep track of the context and interpret things correctly.
    – jcaron
    Oct 18 at 9:26
  • 8
    Relevant cross-site duplicate. Note that this looks very mutch like the XY problem.
    – Didier L
    Oct 18 at 13:03
14

* is greedy, so <.*> matches everything from the first < to the last > in the line. Some tools understand *? as non-greedy analogue of *, but not sed.

In your case one can still go with sed. Replace . (any character) with [^>] (any character but >). You should also add g flag because you want to replace all matches in the line, not just the first.

This should work:

sed 's/<[^>]*>/<>/g'
4
  • 14
    Note: this won't work if there's any < or > in f.e. HTML attributes or in HTML nodes (like in JavaScript snippets). Oct 18 at 8:52
  • 4
    I think this will also break on multiline tags: <tag value="line1\nline2"/> Oct 18 at 16:32
  • 3
    Don't forget commented-out tags that sed doesn't understand as being commented-out. htmlparsing.com/regexes.html Oct 18 at 22:32
  • 4
    Or <![CDATA[]]>. Generating XML is fairly simple, parsing less so.
    – MSalters
    Oct 19 at 9:50
9

Just rename all nodes to empty strings and delete all attributes using xmlstarlet:

xml ed -r '//*' '' -d '//@*'

This will add an XML header (<?xml version="1.0"?>) and leave a slash in the closing tags (</>) which may be acceptable, or which you can remove with an additional tail/sed pass.

Like others have already said, sed alone will never be able to handle all cases correctly.

5
  • 1
    If you're going to do a simple sed-pass afterwards, using empty tags isn't even necessary. You can use <Foo></Foo> explicitly. The goal is to get rid of the regex wildcard; <Foo> is a simple literal match.
    – MSalters
    Oct 19 at 9:56
  • @MSalters Wouldn't that still fall prey to the problems like CDATA and <script> tags that motivated us to avoid regexen in the first place? Oct 19 at 18:56
  • 1
    @DanielWagner: It starts out by reducing the impact, since the <! prefix already cannot match <Foo>. Still, the CDATA can contain <Foo>, which is a second problem. But the idea is that <> is more likely to occur than <Foo>. And furthermore, Foo is here the usual meta-variable. You can use a locally generated GUID instead, which due to the usual GUID properties will statistically not occur in your input text.
    – MSalters
    Oct 20 at 7:51
  • @DanielWagner Also, the OP didn't explain what purpose those empty tags <> serve. Depending on how the output is used, </> or <Foo>/</Foo> can actually be acceptable as is. Oct 20 at 11:15
  • @MSalters Nice, I love the idea of putting randomness in the place of Foo! Oct 20 at 15:51

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.