查找不正确的 YAML 标头

查找不正确的 YAML 标头

我正在尝试确定项目中的哪些文件具有不正确的标头。文件都是这样开始的

---
header:
.
.
.
title: 
some header:
.
.
.
more headers:
level: 
.
.
.
---

在哪里 。 。 。只代表更多的标头。标题不包含缩进。使用以下表达式,我能够从每个文件中提取 YAML 标头。

grep -Przo --include=\*.md "^---(.|\n)*?---" .

现在我想列出不正确的 YAML 标头。

  • 每个 YAML 标头必须有一个title: some text
  • 每个 YAML 标头必须具有language: [a-z]{2}
  • 它必须包含 aexternal: .*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。您也可以使用findwith-exec来搜索文件,而不是使用 bash 。

一般流程是:

  1. *.md递归地向 bash 询问文件列表
  2. 传递每个文件以sed提取 YAML 标头
  3. 将该 YAML 标头传递给 awk 进行验证
  4. 如果标头验证失败,则打印文件名

剧本:

#!/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 globstarin启用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

相关内容