在 bash 脚本中通过正则表达式获取目标子字符串的最佳方法是什么

what is the best way to get target substring through regex in bash script

我正在创建一个脚本来自动提取大量文本文件;目前,我的问题是从 .html 文件中获取目标 ID,示例如下:

 \ \ <body id="some_id" class="calibre2">

我的脚本功能是获取“some_id”并检查它是否有效(ID 不允许以数字开头)否则将此 id 修复为 .html 文件和其他相关文件(toc.ncx、content.opf 等),我主要使用的命令是 sed(但我认为我的方法很麻烦),shell 是下面:

#!/bin/bash
for var in ./*
do
        if [[ $var =~ .*.html ]]
        then
                if grep -q -E '<body id="[0-9]+' $var
                then
                        ID="$(sed -n -E 's/\ \ <body id="[0-9]+(.*?)"\ .*//gp' $var)"
                        echo $ID
                        sed -i -E 's/<body\ id="([0-9]+)/<body id="id/g' $var
                        sed -i -E "s/$ID/id$ID/g" ./../toc.ncx
                        echo $var
                fi
        fi
done

也就是说我不知道​​html的ID,但是我知道ID的规则,例子如下:

\ \ <body id="123char" class="calibre2">

"123char"无效,因为ID不允许以数字开头,所以我需要通过附加前缀字符来修复ID,比如"idchar",所以html变成如下:

\ \ <body id="idchar" class="calibre2">

同时我需要更新其他文件的id(将“123char”更改为“idchar”),例如.ncx文件

<content src="Text/xxx1.html#123char"/>
<!--need changes id as follow-->
<content src="Text/xxx1.html#idchar"/>

PS:如上所示,此shell旨在修复无法通过epub验证器的.epub修复,许多从mobi到epub的电子书转换器都存在此类错误(calibre, convertio...等)

使用 Regex 解析 并不容易,也不是正确的工具。
您可以使用 pup 这是一个 HTML 解析器。

输入

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>Here is h1 tag</h1>
</body>
</html>

测试

pup 'h1 text{}' < index.html

输出

Here is h1 tag

出于任何原因,如果您更喜欢使用正则表达式, is much more suitable than 。将此作为输入:

样本 1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body id="some_id" class="calibre2">
    <h1>this is h1 tag</h1>
</body
</html>

用这个 perl one-liner

perl -lne '/<body\s+id="\K[^"]+/ && print $&' index.htm

输出将是:

some_id

样本 2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body id="some_id" class="calibre2">
    <h1 id="number-1">this is h1 tag</h1>
    <h1 id="number-2">this is h1 tag</h1>
    <h1 id="number-3">this is h1 tag</h1>
</body
</html>

Perl one-liner

perl -lne '/<h1\s+id="\K[^"]+/ && print $&' index.html

输出

number-1
number-2
number-3

如果您更喜欢使用 grep,您可以使用 -P 选项来应用 PCRE(Perl 兼容正则表达式)

grep -oP '<h1\s+id="\K[^"]+'  index.html

# output
number-1
number-2
number-3

使用 函数获取标签的 ID 值:

#!/bin/bash

function match_html_id(){
    {
        local tag=;
        local regex="<${tag}\s+id=\"\K[^\"]+";
        local filename="";
        local result='';

        if grep -P "$regex" "$filename" > /dev/null 2>&1; then
            result=$(grep -oP  $regex $filename);
            echo 'match found';
        else 
            echo 'match not found';
        fi
    } >&2;

    echo $result;
}

declare -r r=$(match_html_id body index.html);
echo r: "'$r'"

body 标签上样本 2 或 1 的输出

match found
r: 'some_id'

这已经在这里重复了无数次;使用正则表达式 parse/edit HTML 是一个非常糟糕的主意!像 这样的 HTML 解析器会更合适。事实上,凭​​借其集成的 EXPath 文件模块,您只需一次调用即可:

$ xidel -se '
  for $x in file:list(.,false(),"*.html")
  where matches(doc($x)//body/@id,"^\d")
  return
  file:write(
    $x,
    x:replace-nodes(
      doc($x)//body/@id,
      function($x){attribute {name($x)} {replace($x,"^\d+","id")}}
    ),
    {"method":"html","indent":true()}
  )
'
  • file:list(.,false(),"*.html") returns 当前目录下的所有 HTML-files
  • matches(doc($x)//body/@id,"^\d") 将其限制为仅那些 HTML-files 的 id 属性值以数字开头。
  • x:replace-nodes( [...] ) 用字符串“id”替换该值的数字。
  • file:write( [...] )替换原来的HTML-file.