これは、なにをしたくて書いたもの?
Markdownに対してjq的なことができるツールがあるようなので、少し試してみます。
対象はmdqとmqですね。
GitHub - yshavit/mdq: like jq but for Markdown: find specific elements in a md doc
mq - jq like tool for markdown processing
mdq
mdqはMarkdownに対してjq的な操作ができるツールです。
GitHub - yshavit/mdq: like jq but for Markdown: find specific elements in a md doc
インストール方法は、HomebrewかDockerイメージ、そしてバイナリーのダウンロードみたいです。
こちらは、Markdownをターゲットにしたjqというより、jq的な使い方をMarkdownに対して行えることを目指したツールのようですね。
つまり、クエリーの構文はMarkdownの書き方に近くなります。
mdq: jq for Markdown / Basic Usage
mq
mqはjqに似た構文でMarkdownを扱えるツールです。
mq - jq like tool for markdown processing
インストール方法はcurl+bashですね。
Install - mq - jq like tool for markdown processing
バイナリーをダウンロードしてもよさそうです。
使い方については、リファレンスはこちらなのですが。
Reference - mq - jq like tool for markdown processing
サンプルを見た方がまずは頭に入りやすそうではあります。
Example - mq - jq like tool for markdown processing
それぞれ軽く試してみましょう。
環境
今回の環境はこちら。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.4 LTS Release: 24.04 Codename: noble $ uname -srvmpio Linux 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
mdqを使う
まずはmdqから試してみましょう。
バイナリーをダウンロードして使うことにします。
$ curl -LO https://github.com/yshavit/mdq/releases/download/v0.9.0/mdq-linux-x64.tar.gz $ tar xf mdq-linux-x64.tar.gz
バージョン。
$ ./mdq --version mdq 0.9.0
ヘルプ。
$ ./mdq --help Select and render specific elements in a Markdown document Usage: mdq [OPTIONS] [selectors] [MARKDOWN_FILE_PATHS]... Arguments: [selectors] The selectors string [MARKDOWN_FILE_PATHS]... An optional list of Markdown files to parse, by path. If not provided, standard input will be used. If these are provided, mdq will act as if they were all concatenated into a single file. For example, if you use --link-pos=doc, the link definitions for all input files will be at the very end of the output. A path of "-" represents standard input. If these are provided, standard input will not be used unless one of the arguments is "-". Files will be processed in the order you provide them. If you provide the same file twice, mdq will process it twice, unless that file is "-"; all but the first "-" paths are ignored. Options: --link-pos <LINK_POS> Where to put link references. For links and images of style `[description][1]`, this flag controls where to put the `[1]: https://example.com` definition. Possible values: - section: Show link definitions in the first section that uses the link - doc: Show link definitions at the bottom of the document [default: section] --footnote-pos <FOOTNOTE_POS> Where to put footnote references. Defaults to be same as --link-pos Possible values: - section: Show link definitions in the first section that uses the link - doc: Show link definitions at the bottom of the document -l, --link-format <LINK_FORMAT> Possible values: - keep: Keep links as they were in the original - inline: Turn all links into inlined form: `[link text](https://example.com)` - never-inline: Never output `[inline](https://example.com)` links[default: never-inline] --renumber-footnotes <RENUMBER_FOOTNOTES> [default: true] [possible values: true, false] -o, --output <OUTPUT> Specifies the output format. Defaults to markdown Possible values: - markdown: Output results as Markdown - md: Alias for markdown - json: Output results as JSON. Spans of inline elements (like within a single paragraph) will be rendered as a single string of Markdown, not as separate JSON elements - plain: Outputs just the plain text. This retrains the spacing between paragraphs and paragraph-like blocks (code blocks, block quotes, etc.) but removes all other formating, including inline formatting. Links are rendered as just their display text, and footnotes are removed entirely [default: markdown] --wrap-width <WRAP_WIDTH> The number of characters to wrap text at. This is only valid when the output format is markdown. Certain elements (like section headings and link definitions) will never be wrapped, and the wrapping will never break a word; it will only ever be along existing whitespace. In particular, this means the wrapping will never add hyphens, and it will never break URLs. -q, --quiet Quiet: do not print anything to stdout. The exit code will still be 0 if any elements match, and non-0 if none do --[no]-br Include breaks between elements in plain and markdown output mode. For plain, this will add a blank line between elements. For markdown, this will add a thematic break ("-----") between elements. This has no effect in JSON output mode. This defaults to true for Markdown output, and false for plain text output. -h, --help Print help (see a summary with '-h') -V, --version Print version
試しにということで、mdqのREADME.mdを対象にしてみます。
$ curl -LO https://raw.githubusercontent.com/yshavit/mdq/refs/tags/v0.9.0/README.md
Examplesセクションを抽出。
$ cat README.md | ./mdq '# Examples' ## Examples ### Ensuring that people have searched existing issues before submitting a bug report Many projects have bug report templates that ask the submitter to attest that they've checked existing issues for possible duplicates. In mdq, you can do: ```bash if echo "$ISSUE_TEXT" | mdq -q '- [x] I have searched for existing issues' ; then ... ``` (The `-q` option is like grep's: it doesn't output anything to stdout, but exits 0 if any items were found, or non-0 otherwise.) This will match: > - [x] I have searched for existing issues ... but will fail if the checkbox is unchecked: > - [ ] I have searched for existing issues ### Extracting a referenced ticket Some organizations use GitHub Actions to update their ticket tracker, if a PR mentions a ticket. You can use mdq to extract the link from Markdown as JSON, and then use jq to get the URL: ```bash TICKET_URL="$(echo "$PR_TEXT" | mdq --output json '# Ticket | [](^https://tickets.example.com/[A-Z]+-\d+$)' | jq -r '.items[].link.url')" ``` This will match Markdown like: > #### Ticket > > https://tickets.example.com/PROJ-1234 ### Whittling down a big table Let's say you have a table whose columns reference people in an on-call schedule, rows correspond to weeks in `YYYY-MM-DD` format: > | On-Call | Alice | Bob | Sam | Pat | > |:----------:|:-----:|:---:|:---:|:---:| > | 2024-01-08 | x | | | | > | 2024-01-15 | | | x | | > | 2024-01-22 | | x | | | To find out when Alice is on call: ```bash cat oncall.md | mdq ':-: /On-Call|Alice/:-: *' ``` ```markdown | On-Call | Alice | |:----------:|:-----:| | 2024-01-08 | x | | 2024-01-15 | | | 2024-01-22 | | ``` Or, to find out who's on call for the week of Jan 15: ```bash cat oncall.md | mdq ':-: * :-: 2024-01-15' ``` ```markdown | On-Call | Alice | Bob | Sam | Pat | |:----------:|:-----:|:---:|:---:|----:| | 2024-01-15 | | | x | | ```
セクションの構成がわかっていそうですね。
セクションのレベルを指定するにはどうするのかなと思ったのですが、#の数を合わせるわけではなさそうです。
$ cat README.md | ./mdq '## Examples' Syntax error in select specifier: --> 1:2 | 1 | ## Examples | ^--- | = expected space
もう少し絞り込んでみましょう。
$ cat README.md | ./mdq '# Ensuring that people have searched existing issues before submitting a bug report' ### Ensuring that people have searched existing issues before submitting a bug report Many projects have bug report templates that ask the submitter to attest that they've checked existing issues for possible duplicates. In mdq, you can do: ```bash if echo "$ISSUE_TEXT" | mdq -q '- [x] I have searched for existing issues' ; then ... ``` (The `-q` option is like grep's: it doesn't output anything to stdout, but exits 0 if any items were found, or non-0 otherwise.) This will match: > - [x] I have searched for existing issues ... but will fail if the checkbox is unchecked: > - [ ] I have searched for existing issues
ちなみに、部分一致でもOKかつ大文字・小文字の区別はなさそうです。
$ cat README.md | ./mdq '# extract' ### Extracting a referenced ticket Some organizations use GitHub Actions to update their ticket tracker, if a PR mentions a ticket. You can use mdq to extract the link from Markdown as JSON, and then use jq to get the URL: ```bash TICKET_URL="$(echo "$PR_TEXT" | mdq --output json '# Ticket | [](^https://tickets.example.com/[A-Z]+-\d+$)' | jq -r '.items[].link.url')" ``` This will match Markdown like: > #### Ticket > > https://tickets.example.com/PROJ-1234
箇条書きの抽出。
$ cat README.md | ./mdq '- quoted' - an `unquoted string` that starts with a letter; this is case-insensitive ----- - a `"quoted string"` (either single or double quotes); this is case-sensitive ----- - a string (quoted or unquoted) anchored by `^` or `$` (for start and end of string, respectively)
コードブロックも抽出できます。
$ cat README.md | ./mdq '```bash' ```bash docker pull yshavit/mdq echo 'My [example](https://github.com/yshavit/mdq) markdown' | docker run --rm -i yshavit/mdq '[]()' ``` ----- ```bash xattr -d com.apple.quarantine mdq ``` ----- ```bash if echo "$ISSUE_TEXT" | mdq -q '- [x] I have searched for existing issues' ; then ... ``` ----- ```bash TICKET_URL="$(echo "$PR_TEXT" | mdq --output json '# Ticket | [](^https://tickets.example.com/[A-Z]+-\d+$)' | jq -r '.items[].link.url')" ``` ----- ```bash cat oncall.md | mdq ':-: /On-Call|Alice/:-: *' ``` ----- ```bash cat oncall.md | mdq ':-: * :-: 2024-01-15' ``` ----- ```bash cargo build ``` ----- ```bash cargo test ```
あとはたぶん構文を見ればなんとなく使い方がわかると思います。
mdq: jq for Markdown / Basic Usage
mq
続いてmq。
今回はバイナリーをダウンロードして使います。
$ curl -LO https://github.com/harehare/mq/releases/download/v0.5.15/mq-x86_64-unknown-linux-gnu $ mv mq-x86_64-unknown-linux-gnu mq $ chmod +x mq
バージョン。
$ ./mq --version mq 0.5.15
ヘルプ。
$ ./mq --help mq is a markdown processor that can filter markdown nodes by using jq-like syntax. Usage: mq [OPTIONS] [QUERY OR FILE] [FILES]... [COMMAND] Commands: repl Start a REPL session for interactive query execution fmt Format mq files based on specified formatting options help Print this message or the help of the given subcommand(s) Arguments: [QUERY OR FILE] [FILES]... Options: -A, --aggregate Aggregate all input files/content into a single array -f, --from-file load filter from the file -I, --input-format <INPUT_FORMAT> Set input format [possible values: markdown, mdx, html, text, null, raw] -L, --directory <MODULE_DIRECTORIES> Search modules from the directory -M, --module-names <MODULE_NAMES> Load additional modules from specified files --args <NAME> <VALUE> Sets string that can be referenced at runtime --rawfile <NAME> <FILE> Sets file contents that can be referenced at runtime --stream Enable streaming mode for processing large files line by line --json Include the built-in JSON module --csv Include the built-in CSV module --fuzzy Include the built-in Fuzzy module --yaml Include the built-in YAML module --toml Include the built-in TOML module --xml Include the built-in XML module --test Include the built-in test module -F, --output-format <OUTPUT_FORMAT> Set output format [default: markdown] [possible values: markdown, html, text, json, none] -U, --update Update the input markdown (aliases: -i, --in-place, --inplace) --unbuffered Unbuffered output --list-style <LIST_STYLE> Set the list style for markdown output [default: dash] [possible values: dash, plus, star] --link-title-style <LINK_TITLE_STYLE> Set the link title surround style for markdown output [default: double] [possible values: double, single, paren] --link-url-style <LINK_URL_STYLE> Set the link URL surround style for markdown links [default: none] [possible values: none, angle] -S, --separator <QUERY> Specify a query to insert between files as a separator -o, --output <FILE> Output to the specified file -C, --color-output Colorize markdown output --list List all available subcommands (built-in and external) -P <PARALLEL_THRESHOLD> Number of files to process before switching to parallel processing [default: 10] -h, --help Print help -V, --version Print version # Examples: ## To filter markdown nodes: mq 'query' file.md ## To read query from file: mq -f 'file' file.md ## To start a REPL session: mq repl ## To format mq file: mq fmt --check file.mq
こちらも対象はmq自身のREADME.mdにしましょう。
$ curl -LO https://raw.githubusercontent.com/harehare/mq/refs/tags/v0.5.15/README.md
セクションの指定。
$ cat README.md | ./mq '.h' ## Why mq? ## Features ## Installation ### Quick Install ### Cargo ### Binaries ### Homebrew ### Docker ### mq-lsp (Language Server) #### Quick Install #### Cargo #### Binaries ### Visual Studio Code Extension ### Neovim ### Zed ### GitHub Actions ## Language Bindings ## MCP (Model Context Protocol) ## Usage ### Basic usage ### Advanced Usage ### Using with markitdown ## External Subcommands ### External Tools ## Support ## License
mdqとはだいぶ動きが違いますね。
こちらはセクションのレベルを指定できます。
$ cat README.md | ./mq '.h3' ### Quick Install ### Cargo ### Binaries ### Homebrew ### Docker ### mq-lsp (Language Server) ### Visual Studio Code Extension ### Neovim ### Zed ### GitHub Actions ### Basic usage ### Advanced Usage ### Using with markitdown ### External Tools
テキストに変換。
$ cat README.md | ./mq '.h3 | to_text()' Quick Install Cargo Binaries Homebrew Docker mq-lsp (Language Server) Visual Studio Code Extension Neovim Zed GitHub Actions Basic usage Advanced Usage Using with markitdown External Tools
bashのコードブロックのみを抽出。
$ cat README.md | ./mq '.code | select(.code.lang == "bash")' ```bash curl -sSL https://mqlang.org/install.sh | bash ``` ```bash curl -sSL https://mqlang.org/install_lsp.sh | bash ```
こちらは、どちらかというとマッチした要素のみを抽出する使い方のようですね。
おわりに
Markdownに対してjq的な使い方ができるmdq、mqを試してみました。
使うまでは構文が違うくらいの印象だったのですが、実際に使ってみると全然使用感が違いますね。
それぞれ用途に応じて使い分けていこうかなと思います。Markdownから情報を抽出したり、処理することは多くなっていくと
思いますので。