如何用唯一编号的标记跨度替换分隔的跨度?

How to replace delimited spans with uniquely-numbered marked spans?

我有一个由 <BD> begin<ED> end delimiters 分隔的文本文件允许嵌套。我希望更改这些分隔符以唯一地指示它们之间的每个文本范围。这些定界符可以是任意字符串。例如:

%{                         # Begin delimiter <BD>
}%                         # End delimiter <ED>

我希望用 唯一编号的标记 :

替换分隔符
<BM><UniqueNumber><BM>     # <BD> is replaced by <BM>i<BM>
<EM><UniqueNumber><EM>     # <ED> is replaced by <EM>i<EM>

<BM><EM> 是任意长度的字符串,可以是二进制的,并且不存在于正在处理的文件中。例如,在大多数文本文件中,<BM> 可以使用 $'\x01'<EM> 可以使用 $'\x02'

例如,文件包含带分隔符的文本跨度,包括嵌套跨度:

A %{ B
C %{ D
E }% F %{ G }% H }% I
J %{ K }% L

其中字母 A..L 可以是任何文本。转换产生:

A <BM>0<BM> B
C <BM>1<BM> D
E <EM>1<EM> F <BM>2<BM> G <EM>2<EM> H <EM>0<EM> I
J <BM>3<BM> K <EM>3<EM> L

注意:不是寻找表示嵌套级别的编号;我正在寻找每个匹配的 <BM>i<BM>...<EM>i<EM> 文本范围以用 唯一整数 标记,从 0 向上计数。

而且,我希望能够存储为标记 0..N-1[=69= 生成的最大数量 N ].我在想象 Bash 函数:

ChangeMarkup()
{
   local InputFile=""
   local OutputFile=""
   local BD=""   # Begin delimiter
   local ED=""   # End delimiter
   local BM=""   # Begin unique numbered marker
   local EM=""   # End unique numbered marker
   local -i N=0    
   # ... convert InputFile to OutputFile, incrementing N for each span
   echo "$N"       # Echo the number of spans
}

# Example invocation:
NSpans=$(ChangeMarkup infile outfile '%{' '}%' $'\x01' $'\x02')

我认为解决方案如下:

  • 初始化N=0
  • 扫描 <BD> 并将 N 压入堆栈。将 <BD> 替换为 <BM>$N<BM>。递增 N.
  • 扫描 <ED> 并替换为 <EM><pop stack><EM>
  • 最后,回显$N

我认为 Bash 脚本中的一些 awk 可能会派上用场。我认为这超出了 sed 的能力范围。我也对 python 或任何可以用 Bash 脚本编写的解决方案持开放态度,仅限于使用 中可用的包]CentOS 7 最小版 iso。不幸的是,这意味着不能考虑perl

如果,可以使用gnu-awkRT special variable

awk -v BD='%{' -v ED='}%' -v BM='<BM>' -v EM='<EM>' '
    BEGIN{i=c=-1; RS=BD"|"ED}
    RT==BD {++i; ++c; d[i]=c; tag=BM}
    RT==ED {tag=EM}
    {printf "%s%s%s%s",[=10=],tag,d[i],tag}
    RT==ED{--i; if(i==-1) tag=""}
' file

你明白了,

A <BM>0<BM> B
C <BM>1<BM> D
E <EM>1<EM> F <BM>2<BM> G <EM>2<EM> H <EM>0<EM> I
J <BM>3<BM> K <EM>3<EM> L

编辑:要求 (2)

if improper nesting is detected, that the script can return an error code? For example: %{ A }% }% the second has no

awk -v BD='%{' -v ED='}%' -v BM='<BM>' -v EM='<EM>' '
    BEGIN{i=c=-1; RS=BD"|"ED}
    RT==BD {++i; ++c; d[i]=c; tag=BM}
    RT==ED {tag=EM}
    {
        if(i<0 && tag!=""){
            print "Error <ED> without opener" > "/dev/stderr"
            exit 1
        }
        printf "%s%s%s%s",[=12=],tag,d[i],tag
    }
    RT==ED{--i; if(i==-1) tag=""}
    END{
        if(i!=-1){
            print "Error <BD> without closer" > "/dev/stderr"
            exit 1
        }
    }
' file

编辑:要求 (1)

to allow for the and to be escaped? That is, if there is a backslash in front of these delimiters, then they're not treated as delimiters

例如

和转义为 \%{\}%

awk -v BD='%{' -v ED='}%' -v BM='<BM>' -v EM='<EM>' '
    BEGIN{i=c=-1; RS="\\"BD"|\\"ED"|"BD"|"ED}
    RT==BD {++i; ++c; d[i]=c; tag=BM}
    RT==ED {tag=EM}
    RT~/^\/{printf "%s%s",[=13=],RT; next}
    {
        if(i<0 && tag!=""){
            print "Error <ED> without opener" > "/dev/stderr"
            exit 1
        }
        printf "%s%s%s%s",[=13=],tag,d[i],tag
    }
    RT==ED{--i; if(i==-1) tag=""}
    END{
        if(i!=-1){
            print "Error <BD> without closer" > "/dev/stderr"
            exit 1
        }
    }
' file

带输入文件

A %{ B
C %{ D
E }% F %{ G }% H }% I
J %{ K }% L\%{ M\}%O

你明白了,

A <BM>0<BM> B
C <BM>1<BM> D
E <EM>1<EM> F <BM>2<BM> G <EM>2<EM> H <EM>0<EM> I
J <BM>3<BM> K <EM>3<EM> L\%{ M\}%O