我正在尝试编写一个基本脚本来编译HTML文件包括。 前提是这样的:
我有3个文件
test.html
<div>
@include include1.html
<div>content</div>
@include include2.html
</div>
include1.html
<span>
banana
</span>
include2.html
<span>
apple
</span>
我想要的输出是:
output.html
<div>
<span>
banana
</span>
<div>content</div>
<span>
apple
</span>
</div>
我尝试了以下操作:
sed "s|@include \(.*)|$(cat \1)|" test.html >output.html
这将返回cat: 1: No such file or directory
sed "s|@include \(.*)|cat \1|" test.html >output.html
这可以运行,但给出:
output.html
<div>
cat include1.html
<div>content</div>
cat include2.html
</div>
关于如何使用组替换在cat
中运行sed
的任何想法?也许是另一种解决方案。
答案 0 :(得分:3)
我写这个15-20 years ago是为了递归地包含文件,它被包含在the article I wrote about how/when to use getline中的“应用程序”下,然后是“ d)”下。我现在对其进行了调整,以与您的特定“ @include”指令一起使用,提供缩进以匹配“ @include”缩进,并增加了防止无限递归的保护措施(例如,文件A包括文件B,文件B包括文件A):>
$ cat tst.awk
function read(file,indent) {
if ( isOpen[file]++ ) {
print "Infinite recursion detected" | "cat>&2"
exit 1
}
while ( (getline < file) > 0) {
if ($1 == "@include") {
match($0,/^[[:space:]]+/)
read($2,indent substr($0,1,RLENGTH))
} else {
print indent $0
}
}
close(file)
delete isOpen[file]
}
BEGIN{
read(ARGV[1],"")
exit
}
。
$ awk -f tst.awk test.html
<div>
<span>
banana
</span>
<div>content</div>
<span>
apple
</span>
</div>
请注意,如果include1.html
本身包含一个@include ...
指令,那么它也会被接受,依此类推。看:
$ for i in test.html include?.html; do printf -- '-----\n%s\n' "$i"; cat "$i"; done
-----
test.html
<div>
@include include1.html
<div>content</div>
@include include2.html
</div>
-----
include1.html
<span>
@include include3.html
</span>
-----
include2.html
<span>
apple
</span>
-----
include3.html
<div>
@include include4.html
</div>
-----
include4.html
<span>
grape
</span>
。
$ awk -f tst.awk test.html
<div>
<span>
<div>
<span>
grape
</span>
</div>
</span>
<div>content</div>
<span>
apple
</span>
</div>
对于非GNU awk,我希望它在经过约20个级别的递归后会失败,并显示“打开的文件过多”错误,因此如果您需要更深入的了解,或者要写出自己的代码,请搞清楚自己的文件管理代码。
答案 1 :(得分:0)
您可以使用以下bash
脚本,该脚本使用正则表达式来检测以@include
开头的行,并使用捕获组来抓取包含文件名:
re="@include +([^[:space:]]+)"
while IFS= read -r line; do
[[ $line =~ $re ]] && cat "${BASH_REMATCH[1]}" || echo "$line"
done < test.html
<div>
<span>
banana
</span>
<div>content</div>
<span>
apple
</span>
</div>
或者,您可以使用以下awk
脚本执行相同的操作:
awk '$1 == "@include"{system("cat " $2); next} 1' test.html
答案 2 :(得分:0)
如果已使用GNU sed,则可以对s
命令使用the e
flag,该命令将当前模式空间作为shell命令执行,并将其替换为输出:
$ sed 's/@include/cat/e' test.html
<div>
<span>
banana
</span>
<div>content</div>
<span>
apple
</span>
</div>
请注意,这不会处理缩进,因为所包含的文件没有任何缩进。像Tidy这样的HTML修饰词可以进一步帮助您:
$ sed 's/@include/cat/e' test.html | tidy -iq --show-body-only yes
<div>
<span>banana</span>
<div>
content
</div><span>apple</span>
</div>
GNU拥有读取文件r
的命令,但无法即时生成文件名。
Ed在他的评论中指出,这很容易被shell命令注入:如果您有类似的东西
@include $(date)
您会注意到date
命令实际上已在运行。可以避免这种情况,但是如果原始解决方案不在窗外,则简洁:
sed 's|@include \(.*\)|cat "$(/usr/bin/printf "%q" '\''\1'\'')"|e' test.html
这仍将@include
替换为cat
,但另外将行的其余部分包装成用printf "%q"
替换的命令,因此一行诸如
@include include1.html
成为
cat "$(/usr/bin/printf "%q" 'include1.html')"
在作为命令执行之前。扩展为
cat include1.html
但是如果文件名为$(date)
,它将变成
cat '$(date)'
(请注意单引号),以防止执行注入的命令。
由于s///e
似乎使用/bin/sh
作为其外壳,因此您不能依赖%q
中Bash的printf
格式规范来存在,因此, printf
二进制。为了提高可读性,我将/
命令的s
分隔符更改为|
(因此我不必转义\/usr\/bin\/printf
)。
最后,\1
周围的引号是将单引号括入一个带引号的字符串中:'\''
变为'
。