我正在尝试确定项目中的哪些文件具有不正确的标头。文件都是这样开始的
---
header:
.
.
.
title:
some header:
.
.
.
more headers:
level:
.
.
.
---
在哪里 。 。 。只代表更多的标头。标题不包含缩进。使用以下表达式,我能够从每个文件中提取 YAML 标头。
grep -Przo --include=\*.md "^---(.|\n)*?---" .
现在我想列出不正确的 YAML 标头。
- 每个 YAML 标头必须有一个
title: some text
- 每个 YAML 标头必须具有
language: [a-z]{2}
- 它必须包含 a
external: .*
或author: .*
。 title:
、level:
、external:
和的位置language:
各不相同。
我尝试做类似的事情
grep -L --include=\*.md -e "external: .*" -e "author: .* ."
然而,这样做的问题是它搜索整个文件,而不仅仅是 YAML 标头。因此,我想解决上述问题可以归结为如何将之前搜索的 YAML 标头结果再次输入到 grep 中。我试过
grep -Przo --include=\*.md "^---(.|\n)*?---" . | xargs -0 grep "title:";
然而,这给了我一个错误“没有这样的文件或目录”,所以我有点不确定如何继续。
例子:
---
title: Rull-en-ball
level: 1
author: Transkribert og oversatt fra [Unity3D](http://unity3d.com)
translator: Bjørn Fjukstad
license: Oversatt fra [unity3d.com](https://unity3d.com/learn/tutorials/projects/roll-ball-tutorial)
language: nb
---
正确的 YAML,有作者、语言和标题。
---
title: Mini Golf
level: 2
language: en
external: http://appinventor.mit.edu/explore/ai2/minigolf.html
---
正确的 YAML,具有标题、语言和外部而不是作者。
---
title: 'Stjerner og galakser'
level: 2
logo: ../../assets/img/ccuk_logo.png
license: '[Code Club World Limited Terms of Service](https://github.com/CodeClub/scratch-curriculum/blob/master/LICENSE.md)'
translator: 'Ole Andreas Ramsdal'
language: nb
---
YAML 标头不正确,缺少作者。
答案1
这是一种方法。我假设您有 bash(递归地循环遍历文件)、sed 和 awk。您也可以使用find
with-exec
来搜索文件,而不是使用 bash 。
一般流程是:
*.md
递归地向 bash 询问文件列表- 传递每个文件以
sed
提取 YAML 标头 - 将该 YAML 标头传递给 awk 进行验证
- 如果标头验证失败,则打印文件名
剧本:
#!/bin/bash
shopt -s globstar
for file in **/*.md
do
# use sed for the header
sed -n /^---$/,/^---$/p "$file" |
awk '
BEGIN {
good_title=0
good_lang=0
good_extaut=0
}
/^title: .*/ { good_title=1 }
/^language: [a-z][a-z]$/ { good_lang=1 }
/^author: .*/ { good_extaut=1 }
/^external: .*/ { good_extaut=1 }
END {
if (good_title && good_lang && good_extaut)
exit 0
else
exit 1
}
' \
|| printf "Incorrect header found in %s\n" "$file"
done
您可以轻松地将 awk 脚本中的正则表达式匹配模式调整为更严格或更宽松,具体取决于您的具体要求(也许您需要字母数字字符而不是“any”,如.
示例中的当前字符)。
该sed
语句通过以下方式提取 YAML 标头:
- 禁止默认打印 (
-n
) - 请求与以下模式匹配的一行地址:行首、
---
行尾;第二个模式必须出现在第一个模式之后。 p
然后打印该地址范围
该awk
脚本有点过度构建,但为了清楚起见,我想把它拼写出来。每次调用 awk 时,它都会将三个标志变量设置为零或 false。如果我们看到符合我们标准的行,我们会将相应的标志设置为 one/true。一旦看到所有的行,我们就会根据这些标志的状态返回成功或失败——它们必须全部为真才能“通过”验证。
将这些适当命名的示例文件分散到当前目录和子目录中:
$ tree .
.
├── bad1.md
├── good1.md
├── good2.md
└── subdir
├── bad1.md
└── good1.md
1 directory, 5 files
...脚本输出:
Incorrect header found in bad1.md
Incorrect header found in subdir/bad1.md
答案2
要提取文件头,我们可以sed
像这样使用:
sed -e '1,/^---$/!d' -e '/^---$/d' filename
这会从文件中删除除第 1 行和下一行之间的所有内容(恰好是---
.第二个表达式还会删除---
数据中的所有行,以便您只剩下 YAML 标头。
我将使用基于 Python 的yq
实用程序安德烈·基斯柳克。由于这是多功能 JSON 解析器的方便包装器jq
,因此我们可以轻松检测与键对应的值是null
、非null
还是特定字符串等。
在jq
语法中,我们可以测试键 , 是否keyname
存在于带有 的对象中has("keyname")
。我们还可以RE
使用测试键的值是否与特定正则表达式 匹配.keyname | test("RE")
。
问题中提到的测试可以翻译成以下jq
表达式:
has("title") and
(.title | test(".")) and
has("language") and
(.language | test("[a-z]{2}")) and
(has("external") or has("author"))
或者,更短但表达力较差,
(.title? != null) and
(.language? | test("[a-z]{2}")) and
(has("external") or has("author"))
这确保了每个键都存在,并且需要具有非null
值的键的值是正确的。
在三个示例文件上运行此命令,并在脚本文件中进行测试validate
:
$ sed -e '1,/^---$/!d' -e '/^---$/d' file1.md | yq -f validate
true
$ sed -e '1,/^---$/!d' -e '/^---$/d' file2.md | yq -f validate
true
$ sed -e '1,/^---$/!d' -e '/^---$/d' file3.md | yq -f validate
false
我们可以将其推广到测试.md
当前目录或下面的所有文件,find
如下所示:
find . -name '*.md' -type f -exec sh -c '
for pathname do
if ! sed -e "1,/^---\$/!d" -e "/^---\$/d" "$pathname" |
yq -e -f validate >/dev/null
then
printf "Invalid YAML header: %s\n" "$pathname"
fi
done' sh {} +
或者,使用任何支持**
通配模式的 shell(通过shopt -s globstar
in启用bash
):
for pathname in ./**/*.md
do
if ! sed -e '1,/^---$/!d' -e '/^---$/d' "$pathname" |
yq -e -f validate >/dev/null
then
printf 'Invalid YAML header: %s\n' "$pathname"
fi
done
在这里,我们另外丢弃了输出yq
,而是使用该工具及其-e
选项。这使得实用程序的退出状态反映最后计算的表达式的值,即零真的,并且非零错误的在这种情况下。这使得直接在语句中使用我们的sed
+yq
管道变得很容易if
。
使用我们的三个测试文件运行它,我们得到
Invalid YAML header: ./file3.md