因此,我正在尝试编写一个简单的脚本,该脚本将解析 xml 文件并根据在其下找到的类别名称将输出重定向到新文件。例如,这就是 XML 文件的样子。
<category> Music </Category>
<url>https://www.youtube.com/watch?v=waAlgFq9Xq8</url>
<category> Movies </Category>
<url>https://www.youtube.com/watch?v=g4U4BQW9OEk</url>
我的脚本看起来像这样:
for i in *.xml; do
name=$(grep -i "<category>" $i | awk '{print $1}')
line=$(grep -i -A1 "<category>" $i)
echo "$line" >> $filename
done
例如,Movies.log 将包含在“电影”类别下找到的所有链接,而 Music.log 将包含在“音乐”类别下找到的所有链接。
答案1
您是否考虑过循环遍历每个类别?像这样:
for i in *.xml; do
for category in $(sed -rn '/^<category>/{s/[^>]*> *([^ <]*).*/\1/p}' "$i"); do
sed -rn "/^<category> *$category/,/^<category>/{s/<url> *([^ <]*).*/\1/p}" "$i" > "$category.log"
done
done
更新:使用awk
awk -v 'RS=<' -v 'cat=none' -F '>' \
'$1 ~ /^category$/ {gsub(/^ *| *$/,"",$2); cat=$2} \
$1 ~ /^url$/ {print $2 >> cat".log"}' \
*.xml
这可以避免循环输入文件,并将附加到
.log
任何类别的文件中。使用 awk 的记录分隔符分配
-v 'RS=<'
意味着可以在任何地方找到类别/url 标记(而不仅仅是在行的开头)。换行符可能出现在 xml 数据中的任何位置。将此与将字段分隔符设置为 相结合
'>'
,意味着每个记录的第一个字段将相当于 xml 标记名称。每次 awk 遇到第一个字段是“类别”的记录时,都会将变量
cat
设置为该类别的名称。当 awk 遇到第一个字段是“url”的记录时,它会将该 url 附加到文件中
cat.log
。cat
将被定义为none
开始。这可以防止在<url>
遇到没有任何前面的a 的情况下出现故障<category>
。替换
gsub(/^ *| *$/,"",$2)
是删除示例输入文件中出现的类别名称的前导/尾随空格.xml
。
笔记:
以上都不是万无一失的。对于正确的 xml 输入文件,实际的 xml 解析器会更好 – 就像xmlstarlet。但这还需要正确形成的 xml 文件(<category>
例如,示例输入没有匹配的标签)。
答案2
我准备了这个解决方案:
grep -hP "<category.*>|<url.*>" *.xml | cut -d ">" -f 2 | cut -d "<" -f 1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | gawk 'BEGIN { category = ""; } { if (!length($0)) { next; } if (length(category)) { printf("\necho -e \"%s\" >> \"%s.log\"", $0, category); category = ""; } else { category = $0; } } END { printf("\n"); }' | bash
它搜索当前目录中的所有 .xml 文件并追加以 URL 前面的行中找到的类别命名的文件的 URL(您可以通过删除末尾的 | bash 来检查输出)。
仅提取我们感兴趣的数据的 XML 节点
通过让 grep (例如)在名为的文件中搜索模式*.xml,我们不必迭代文件名。选项-Hto grep 抑制输出中的文件名。提供给 grep 的模式是 Perl 兼容的正则表达式 (-P)
提取我们感兴趣的节点的值
grep 命令返回的行显然如下所示:
<category> MyMusic </category>
<url>https://www.youtube.com/watch?v=waAlgFq9Xq8123</url>
<category> MyMovies </category>
<url>https://www.youtube.com/watch?v=g4U4BQW9OEk456</url>
<category>Music</category>
<url>https://www.youtube.com/watch?v=waAlg</url>
<category> Music </category>
<url>https://www.youtube.com/watch?v=waAlgFq9Xq8</url>
<category> Movies </category>
<url>https://www.youtube.com/watch?v=g4U4BQW9OEk</url>
我们刚刚过滤掉了不需要的数据行。现在我们需要提取节点内的值,这归结为提取开始和结束标记之间的数据,即符号之间的数据>和<(我们不关心它是哪个节点,所以我们使用“通用”方法)。
这可以很容易地实现| cut -d ">" -f 2 | cut -d "<" -f 1
这实际上意味着取符号 > 右侧 (-f 2) 的所有内容,然后根据我们得到的新结果,取符号 < 左侧 (-f 1) 的所有内容
这给我们留下了下面的内容
MyMusic
https://www.youtube.com/watch?v=waAlgFq9Xq8123
MyMovies
https://www.youtube.com/watch?v=g4U4BQW9OEk456
Music
https://www.youtube.com/watch?v=waAlg
Music
https://www.youtube.com/watch?v=waAlgFq9Xq8
Movies
https://www.youtube.com/watch?v=g4U4BQW9OEk
现在我们需要修剪这些值,这里有一个小的纠正步骤。
修剪值
修剪前导和尾随空格| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
和-e,sed 可以按照给定的顺序执行脚本,而无需通过管道传输额外的 sed 命令(或其他情况下的多个 sed 命令)。
传递给 sed 的第一个脚本会修剪前导空格(即字符串开头的任何 [:space:] 字符(@ 每行)),而第二个脚本会修剪尾随空格(即结尾之前的任何 [:space:] 字符)字符串(@每一行)。
现在我们有了类似下面的东西,我们就快完成了:
MyMusic
https://www.youtube.com/watch?v=waAlgFq9Xq8123
MyMovies
https://www.youtube.com/watch?v=g4U4BQW9OEk456
Music
https://www.youtube.com/watch?v=waAlg
Music
https://www.youtube.com/watch?v=waAlgFq9Xq8
Movies
https://www.youtube.com/watch?v=g4U4BQW9OEk
将文件附加命令写入标准输出
就像我们编写 echo 命令将数据附加到文件一样,我们需要一些能够自动执行该过程的东西。我选择继续呆呆。 gawk 逐行读取数据,并将类别抓取到变量中。当它读取另一行时,如果类别变量不为空,则该行包含 URL。使用这种技术,我们可以简单地发出 echo -e "current url" >> current_category.log 这样的命令
注意至关重要的使用 >> 将新数据附加到文件中。使用 > 只会写出最后一个 URL,最终每个类别都会有一行!
结果,我们刚刚将如下数据写入标准输出:
echo -e "https://www.youtube.com/watch?v=waAlgFq9Xq8123" >> "MyMusic.log"
echo -e "https://www.youtube.com/watch?v=g4U4BQW9OEk456" >> "MyMovies.log"
echo -e "https://www.youtube.com/watch?v=waAlg" >> "Music.log"
echo -e "https://www.youtube.com/watch?v=waAlgFq9Xq8" >> "Music.log"
echo -e "https://www.youtube.com/watch?v=g4U4BQW9OEk" >> "Movies.log"
将数据附加命令传递给 bash 执行
管道中的最后一个元素| bash
确保 echo 命令传递到 bash 执行。
请注意,gawk 能够将数据写入/附加到文件。但我有意想要拥有尽可能最小的 gawk 脚本。