将命令输出发送回管道中的前一个子 shell 进行处理

Send command output back to previous subshell in pipe for processing

给定 this html5 page,使用文件描述符与先前的子 shell 交互地 xmllint 处理它。
应用于 xml2xpath OS project.

如何重现:运行“问题”部分的脚本片段

基本命令是:

(echo 'xpath //*'; echo "bye") | xmllint --shell html5.html

其中给出了待处理的源输出:

/ > xpath //*
Object is a Node Set :
Set contains 346 nodes:
1  ELEMENT html
    default namespace href=http://www.w3.org/1999/xhtml
    ATTRIBUTE lang
      TEXT
        content=en
    ATTRIBUTE dir
      TEXT
        content=ltr
2  ELEMENT head
3  ELEMENT title
...
202  ELEMENT div
    default namespace href=http://www.w3.org/1999/xhtml
203  ELEMENT p
204  ELEMENT code
205  ELEMENT math
    default namespace href=http://www.w3.org/1998/Math/MathML
...
345  ELEMENT mo
346  ELEMENT mn
/ > bye

目标 是将包含 namespace 的行连接到上一行,将 n ELEMENT name 显示为 n name,忽略其余部分(并发送更多命令 xmllint).
以下命令给出了 正确的行 预期会出现在先前的子 shell

(echo 'xpath //*' )| xmllint --shell $proj/git/xml2xpath/tests/resources/html5.html | \
sed -nEe '{ :a; $!N;s/^([0-9]{1,5}) *ELEMENT *([^ ]*)\n +(default)? ?namespace ([a-z]+)? ?href=([^=]+)/  =/;ta; s/^([0-9]{1,5}) *ELEMENT *([^ ]*)/ /; /^[1-9]/ P;D }'

1 html default=http://www.w3.org/1999/xhtml
2 head
3 title
4 link
5 link
6 link
7 link
8 body
9 h1
10 h2
...

问题
通过文件描述符将行发送回子 shell 不会正确连接行,namespace 信息出现在 arrns 数组(下一个代码示例)内它自己的项目上。
因此,从文件描述符读取并使用 sed 处理以填充数组未按预期工作。此外,尽量避免 post-在此阶段处理或解析文件超过 1 次。

目前最好的方法是:

#!/bin/bash
wget --no-clobber "https://www.w3.org/TR/XHTMLplusMathMLplusSVG/sample.xhtml" -O html5.html

fname='xff'
[ ! -p "$fname" ] && mkfifo "$fname"
exec 3<>"$fname"

cat /dev/null > tmp.log

stop='dir xxxxxxx'

function parse_line(){
    while read -r -u 3 xline; do 
        printf "%s\n" "$xline"
        if [ "$xline" == "/ > $stop" ]; then 
            break 
        fi
    done | sed -nEe '{ :a; $!N;s/^([0-9]{1,5}) *ELEMENT *([^ ]*)\n +(default)? ?namespace ([a-z]+)? ?href=([^=]+)/  =/;ta; s/^([0-9]{1,5}) *ELEMENT *([^ ]*)/ /; /^[1-9]|namespace/ P;D }'
}

(
    echo 'xpath //*'
    echo "$stop"
    IFS=$'\n' read -r -d '' -a arrns < <(parse_line && printf '[=13=]')
    
    # print to file temporarily for debugging and avoid sending to xmllint shell 
    printf "%s\n" "${arrns[@]}" >> tmp.log
    echo "OUT OF LOOP 1 ${#arrns[@]}" >> tmp.log
    echo "bye"
) | xmllint --shell html5.html >&3

exec 3>&-
rm xff
cat tmp.log

将 fd 3 中的所有行解析为一个变量,然后应用 sed 得到相同的结果。

tmp.log 上显示 arrns 的内容(几乎正确):

1 html
default namespace href=http://www.w3.org/1999/xhtml
2 head
3 title
4 link
5 link
...
239 math
default namespace href=http://www.w3.org/1998/Math/MathML
...
OUT OF LOOP 1 354

示例中的第 1 行和第 239 行应该看起来

239 math default=http://www.w3.org/1998/Math/MathML

这可能允许通过一些处理将此命令从同一个子 shell 转发到 xmllint 以设置名称空间,因为它们出现在文档中。

setns default=http://www.w3.org/1998/Math/MathML

出于某种原因,此 while read 循环不模拟 xmllint 输出并使原始 sed 命令失败

while read -r -u 3 xline; do ... ; done

修复了循环后的 sed 命令,现在输出正确了

sed -E -e :a -e '/^[1-9]/,/^(default|namespace)/ { $!N;s/\n(default|namespace)/ /;ta }' \
-e 's/^([0-9]{1,5}) *ELEMENT *([^ ]*)/ /' \
-e 's/(default)? ?namespace( [a-z0-9]+)? ?href=([^=]+)/=/g' \
-e '/^[1-9]/ P;D'

这里的顺序似乎很重要

1- 按预期加入行
sed -E -e :a -e '/^[1-9]/,/^(default|namespace)/ { $!N;s/\n(default|namespace)/ /;ta }'

1  ELEMENT html
default namespace href=http://www.w3.org/1999/xhtml

变成

1  ELEMENT html default namespace href=http://www.w3.org/1999/xhtml

2-处理*ELEMENT*
-e 's/^([0-9]{1,5}) *ELEMENT *([^ ]*)/ /'

之前:1 ELEMENT html
之后:1 html

3- 处理第 1 步结果的 *namespace* 部分
-e 's/(default)? ?namespace( [a-z0-9]+)? ?href=([^=]+)/=/g'

之前:1 html default namespace href=http://www.w3.org/1999/xhtml
之后:1 html default=http://www.w3.org/1999/xhtml

4- 打印以数字开头的行
-e '/^[1-9]/ P;D'

错误结果:

1 html
default namespace href=http://www.w3.org/1999/xhtml
2 head
...
191 svg:svg
namespace svg href=http://www.w3.org/2000/svg
...

修复后的正确结果

1 html default=http://www.w3.org/1999/xhtml
2 head
...
191 svg:svg svg=http://www.w3.org/2000/svg
...