shell 如何使用xmlint --xpath在每个匹配项后追加一个换行符

6jjcrrmo  于 2023-01-09  发布在  Shell
关注(0)|答案(5)|浏览(166)

我有下面的HTML代码:

<textarea name="command" class="setting-input   fixed-width" rows="9">1</textarea><textarea name="command" class="setting-input   fixed-width" rows="5">2</textarea>

我想解析它以接收这样的输出:

1
2

目前我正在使用:

xmllint --xpath '//textarea[@name="command"]/text()' --html

但是它不在每个匹配后附加换行符。

dfuffjeb

dfuffjeb1#

来自2020年的你好!
从libxml的v2.9.9开始,此行为has been fixed in xmllint itself.
然而,如果你使用的是比这更老的版本,并且不想从源代码构建libxml来获得修复的xmllint,那么你需要其他的解决方法之一。例如,在撰写本文时,最新的CentOS 8仍然使用的是OP描述的libxml版本(2.9.7)。
我从this SO answer中了解到,从理论上讲,可以将一个命令输入到旧版本的--shell选项中(〈2.9.9)版本的xmllint,这将在单独的行上生成每个节点。最后不得不使用sedgrep对其进行后处理,以去除shell模式(面向人的)输出中的视觉碎片,这并不理想。
XMLStarlet(如果可用)提供了另一种解决方案,但是在使用xmlstarlet sel提取节点之前,您确实需要使用xmlstarlet fo将HTML片段格式化为有效的XML:

echo '
<textarea name="command" class="setting-input fixed-width"
 rows="9">1</textarea>
<textarea name="command" class="setting-input fixed-width"
 rows="5">2</textarea>' \
  | xmlstarlet fo -H -R \
  | xmlstarlet sel -T -t -v '//textarea[@name="command"]' -n

如果来自第二个xmlstarlet调用的Attempt to load network entity消息令您烦恼,只需在最后添加2>/dev/null以抑制它(有可能抑制打印到标准错误的其他消息)。
XMLStarlet选项说明(另请参见user's guide):

  • fo -H -R-format输出,预期HTML输入,并尽可能多地恢复错误输入
  • 这将添加一个<html>根节点,使OP示例中的片段成为有效的XML
  • sel -T -t -v //xpath -n-选择基于XPath //xpath的节点
  • 输出纯文本(-T)而不是XML
  • 使用给定的模板(-t)返回节点的值(-v),而不是节点本身(允许您放弃在XPath表达式中使用text()
  • 最后,添加一个新行(-n
    编辑:删除了实现了一半的xmllint --shell解决方案,因为它很糟糕。添加了一个XMLStarlet示例,它实际上可以处理OP的数据。
3vpjnl9f

3vpjnl9f2#

尝试this patch,它提供2个选项:

  • --xpath:与旧的--xpath相同,节点之间用\n分隔。
  • --xpath0:与旧的--xpath相同,节点之间用\0分隔。

测试输入(a.html):

<textarea name="command" class="setting-input   fixed-width" rows="9">1</textarea><textarea name="command" class="setting-input   fixed-width" rows="5">2</textarea>

测试命令1:

# xmllint --xpath '//textarea[@name="command"]/text()' --html a.html

测试输出1:

1
 2

测试命令2:

# xmllint --xpath0 '//textarea[@name="command"]/text()' --html a.html | xargs -0 -n1

测试输出2:

1
 2
ugmeyewa

ugmeyewa3#

我做了以下,丑陋的伎俩,请随时提供一个更好的解决方案。
通过使用以下命令将</textarea>替换为\n</textarea>,更改了HTML代码:

sed 's/\<\/textarea/\'$'\n\<\\/textarea/g' f
7tofc5zh

7tofc5zh4#

下面是一个 Package 器脚本,完全用于换行符分隔的输出(对于xmllint的旧版本)。
创建一个包含内容的文件xmllint2.sh,然后执行chmod u+x xmllint2.sh,最后像这样运行:
./xmllint2.sh --xpath --html '//textarea[@name="command"]/text()' 2>/dev/null
(the命令的最后一部分是隐藏使用html发生的警告的输出)

#!/bin/bash

# wrapper script to
# - have newline delimited output on Xpath querys
# - implements --xpath on very old releases

/usr/bin/xmllint --xpath &>/dev/null
implements_xpath=$?

newlines_delimited_xmllint_version=20909
current_version=$(xmllint --version |& awk 'NR==1{print $NF;exit}')

args=( "$@" )
if [[ $@ == *--xpath* ]]; then
    # iterate over positional parameters
    for ((i=0; i<${#args}; i++)); do
        if [[ ${args[i]} == --xpath ]]; then
            xpath="${args[i+1]}"
            unset args[i+1]
            unset args[i]
            break
        fi
    done
    if [[ ($implements_xpath==0 && $current_version>=20909) || $file == - || $file == /dev/stdin || $xpath == / || $xpath == string\(* ]]
    then
        exec /usr/bin/xmllint "$@"
    else
        exec /usr/bin/xmllint "${args[@]}" --shell <<< "cat $xpath" | sed '1d;$d;s/^ ------- *$//;/^$/d'
    fi
else
    exec /usr/bin/xmllint "$@"
fi

检查最新版本:https://github.com/sputnick-dev/xmllint
Debian Buster于2020年6月29日发布了2.9.4版本,已经4年了。
Debian测试/实验版有2.9.10,这是修复版本。
另一种用Debian最新稳定版安装2.9.10的方法:https://serverfault.com/a/1022826/120473(无需承担apt系统崩溃的风险)

i1icjdpr

i1icjdpr5#

换行符可以合法地出现在XML数据中。一个更健壮的方法是用一个保证不会出现在XML数据中的字符来分隔xpath结果。Null character,即Universal Coded Character Set中的U +0000,就是这样一个字符。
注意,赋给空控制字符的代码点U +0000是唯一一个以Unicode和ISO/IEC 10646编码的字符,它在任何XML 1.0和1.1文档中总是无效的。

xml小星星

与此同时,另一个xpath命令行工具xmlstarlet现在也可以实现这一目标。xmlstarlet目前不直接支持NUL的输出,但是我们可以让它输出U+FFFF,与NUL一样,XML数据(源代码)中不会出现,我们只需要将U+FFFF转换为U+0000,得到的xpath结果为NUL分隔。
在下面的例子中,我将使用下面的部分html文件。它与OP的问题中的例子相同,除了我添加了换行符用于测试目的。

cat >data.html <<'EOF'
<textarea name="command" class="setting-input fixed-width" rows="9">1 
 newline</textarea>
<textarea name="command" class="setting-input fixed-width" rows="5">2 
 newline</textarea>
EOF

下面是如何使用xmlstarletsed来用NULL分隔xpath结果:

xmlstarlet fo -H -R data.html \
| xmlstarlet sel -t -m '//textarea[@name="command"]' -v '.' -o $'\uffff' \
| sed s/$'\uFFFF'/\\x00/g

如果您愿意,可以使用perl代替sedperl -CS -0xFFFF -l0 -pe ''
注意:我通过xmlstarlet fo -H -R运行HTML,如@TheDudeAbides答案所示。
现在xpath结果由NULL分隔,我们可以在xargs -0的帮助下处理结果。

xmlstarlet fo -H -R data.html \
| xmlstarlet sel -t -m '//textarea[@name="command"]' -v '.' -o $'\uffff' \
| sed s/$'\uFFFF'/\\x00/g \
| xargs -0 -n 1 printf '%q\n'

结果:

'1 '$'\n'' newline'
'2 '$'\n'' newline'

或者将其加载到bash数组中:

mapfile -t -d '' a < <(
 xmlstarlet fo -H -R data.html \
 | xmlstarlet sel -t -m '//textarea[@name="command"]' -v '.' -o $'\uffff' \
 | sed s/$'\uFFFF'/\\x00/g
)

declare -p a

结果:

declare -a a=([0]=$'1 \n newline' [1]=$'2 \n newline')

撒克逊人
使用saxon而不是xmlstarlet的相同技术:

xmllint --html data.html --dropdtd --xmlout \
| java -cp "$CP" net.sf.saxon.Query -s:- -qs:'//textarea[@name="command"]' !method=text !item-separator=$'\uFFFF' \
| sed s/$'\uFFFF'/\\x00/g \
| xargs -0 -n 1 printf '%q\n'

相关问题