用正则表达式重写 YAML frontmatter

Rewrite YAML frontmatter with regular expression

我想使用 Jekyll 将我的 WordPress 网站转换为 GitHub 上的静态网站。

我使用了一个插件,可以将我的 62 篇文章作为 Markdown 导出到 GitHub。我现在有这些帖子,每个文件的开头都有额外的前言。它看起来像这样:

---
ID: 51
post_title: Here's my post title
author: Frank Meeuwsen
post_date: 2014-07-03 22:10:11
post_excerpt: ""
layout: post
permalink: >
  https://myurl.com/slug
published: true
sw_timestamp:
  - "399956"
sw_open_thumbnail_url:
  - >
    https://myurl.com/wp-content/uploads/2014/08/Featured_image.jpg
sw_cache_timestamp:
  - "408644"
swp_open_thumbnail_url:
  - >
    https://myurl.com/wp-content/uploads/2014/08/Featured_image.jpg
swp_open_graph_image_data:
  - '["https://i0.wp.com/myurl.com/wp-content/uploads/2014/08/Featured_image.jpg?fit=800%2C400&ssl=1",800,400,false]'
swp_cache_timestamp:
  - "410228"
---

这个块没有被 Jekyll 正确解析,而且我不需要所有这些 frontmatter。我想将每个文件的 frontmatter 转换为

---
ID: 51
post_title: Here's my post title
author: Frank Meeuwsen
post_date: 2014-07-03 22:10:11
layout: post
published: true
---

我想用正则表达式来做这个。但是我对正则表达式的了解并不多。在这个论坛和大量 Google 搜索的帮助下,我并没有取得太大进展。我知道如何找到完整的 frontmatter,但如何用上面指定的一部分替换它?

我可能必须分步执行此操作,但我不知道如何执行此操作。

我使用 Textwrangler 作为编辑器来进行搜索和替换。

编辑我的 post 因为我第一次误解了这个问题,我没能理解实际的 post 在同一个文件中,就在 ---

使用 egrep 和 GNU sed,而不是内置的 bash,相对容易:

# create a working copy
mv file file.old
# get only the fields you need from the frontmatter and redirect that to a new file
egrep '(---|ID|post_title|author|post_date|layout|published)' file.old > file
# get everything from the old file, but discard the frontmatter
cat file.old |gsed '/---/,/---/ d' >> file
# remove working copy
rm file.old

如果您想要一次性完成:

for i in `ls`; do mv $i $i.old; egrep '(---|ID|post_title|author|post_date|layout|published)' $i.old > $i; cat $.old |gsed '/---/,/---/ d' >> $i; rm $i.old; done

为了更好的衡量,以下是我作为第一回复所写的内容:

============================================= ==============

我认为你把这种方式搞得太复杂了。

一个简单的 egrep 将做你想做的事:

egrep '(---|ID|post_title|author|post_date|layout|published)' file

重定向到新文件:

egrep '(---|ID|post_title|author|post_date|layout|published)' file > newfile

一次整个目录:

for i in `ls`; do egrep '(---|ID|post_title|author|post_date|layout|published)' $i > $i.new; done

在像您这样的情况下,最好使用实际的 YAML 解析器和一些脚本语言。将每个文件的元数据截断为独立文件(或字符串),然后使用 YAML 库加载元数据。加载元数据后,您可以毫无问题地安全地修改它们。然后使用同一个库中的序列化方法创建一个新的元数据文件,最后将这些文件重新组合在一起。

像这样:

<?php
list ($before, $metadata, $after) = preg_split("/\n----*\n/ms", file_get_contents($argv[1]));
$yaml = yaml_parse($metadata);
$yaml_copy = [];
foreach ($yaml as $k => $v) {
    // copy the data you wish to preserve to $yaml_copy
    if (...) {
        $yaml_copy[$k] = $yaml[$k];
    }
}
file_put_contents('new/'.$argv[1], $before."\n---\n".yaml_emit($yaml_copy)."\n---\n".$after);

(这只是一个未经测试的草稿,没有错误检查。)

你可以像这样用 gawk 来做:

gawk 'BEGIN {RS="---"; FS="[=10=]0" } (FNR == 2) { print "---"; split(, fm, "\n");  for (line in fm) { if ( fm[line] ~ /^(ID|post_title|author|post_date|layout|published):/) {print fm[line]}  }  print "---"   } (FNR > 2) {print}' post1.html > post1_without_frontmatter_fields.html

您基本上是想编辑文件。这就是 sed(流编辑器)的用途。

sed -e s/^ID:(*)$^post_title:()$^作者:( )$^postdate:()$^layout:()$^published:()$ /ID:\npost_title:\nauthor:\npostdate:\nlayout:\npublished:\6/g

YAML(以及其他相对自由的格式,如HTML、JSON、XML)最好不要使用正则表达式进行转换,很容易为一个例子工作并打破下一个有额外的空格、不同的缩进等。

在这种情况下使用 YAML 解析器并非易事,因为许多人要么期望文件中有一个 YAML 文档(并且 barf 在 Markdown 部分是无关的东西),要么期望文件中有多个 YAML 文档(并且 barf 因为Markdown 不是 YAML)。此外,大多数 YAML 解析器会丢弃有用的东西,例如注释和重新排序映射键。

我多年来一直为我的 ToDo 项目使用类似的格式(YAML header,然后是 reStructuredText),并使用一个小的 Python 程序来提取和更新这些文件。给出这样的输入:

---
ID: 51     # one of the key/values to preserve
post_title: Here's my post title
author: Frank Meeuwsen
post_date: 2014-07-03 22:10:11
post_excerpt: ""
layout: post
permalink: >
  https://myurl.com/slug
published: true
sw_timestamp:
  - "399956"
sw_open_thumbnail_url:
  - >
    https://myurl.com/wp-content/uploads/2014/08/Featured_image.jpg
sw_cache_timestamp:
  - "408644"
swp_open_thumbnail_url:
  - >
    https://myurl.com/wp-content/uploads/2014/08/Featured_image.jpg
swp_open_graph_image_data:
  - '["https://i0.wp.com/myurl.com/wp-content/uploads/2014/08/Featured_image.jpg?fit=800%2C400&ssl=1",800,400,false]'
swp_cache_timestamp:
  - "410228"
---
additional stuff that is not YAML
  and more
  and more

这个程序 ¹:

import sys
import ruamel.yaml

from pathlib import Path


def extract(file_name, position=0):
    doc_nr = 0
    if not isinstance(file_name, Path):
        file_name = Path(file_name)
    yaml_str = ""
    with file_name.open() as fp:
        for line_nr, line in enumerate(fp):
            if line.startswith('---'):
                if line_nr == 0:  # don't count --- on first line as next document
                    continue
                else:
                    doc_nr += 1
            if position == doc_nr:
                yaml_str += line
    return ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)


def reinsert(ofp, file_name, data, position=0):
    doc_nr = 0
    inserted = False
    if not isinstance(file_name, Path):
        file_name = Path(file_name)
    with file_name.open() as fp:
        for line_nr, line in enumerate(fp):
            if line.startswith('---'):
                if line_nr == 0:
                    ofp.write(line)
                    continue
                else:
                    doc_nr += 1
            if position == doc_nr:
                if inserted:
                    continue
                ruamel.yaml.round_trip_dump(data, ofp)
                inserted = True
                continue
            ofp.write(line)


data = extract('input.yaml')
for k in list(data.keys()):
    if k not in ['ID', 'post_title', 'author', 'post_date', 'layout', 'published']:
        del data[k]

reinsert(sys.stdout, 'input.yaml', data)

你得到这个输出:

---
ID: 51     # one of the key/values to preserve
post_title: Here's my post title
author: Frank Meeuwsen
post_date: 2014-07-03 22:10:11
layout: post
published: true
---
additional stuff that is not YAML
  and more
  and more

请注意 ID 行的注释已妥善保留。


¹ 这是使用 ruamel.yaml YAML 1.2 解析器完成的,它试图在 round-trips 上保留尽可能多的信息,我是作者。

您也可以使用 python-frontmatter:

import frontmatter
import io
from os.path import basename, splitext
import glob

# Where are the files to modify
path = "*.markdown"

# Loop through all files
for fname in glob.glob(path):
    with io.open(fname, 'r') as f:
        # Parse file's front matter
        post = frontmatter.load(f)
        for k in post.metadata:
           if k not in ['ID', 'post_title', 'author', 'post_date', 'layout', 'published']:
        del post[k]

        # Save the modified file
        newfile = io.open(fname, 'w', encoding='utf8')
        frontmatter.dump(post, newfile)
        newfile.close()

如果您想查看更多示例,请访问 this page

希望对您有所帮助。