monk command tool
last modified October 23, 2026
monk is a command-line tool for querying HTML with CSS
selectors. It reads HTML from a file, from standard input, or directly from
a URL, selects elements with one or more selectors, and prints them as an
indented tree, as plain text, as attribute values, or as JSON. This makes it
handy for web scraping, for extracting data from saved pages, and for
inspecting the structure of a document from the terminal.
monk is written in Go. It uses the
goquery library for
standard CSS selectors and adds a few extensions, such as matching by text,
by regular expression, and keeping the first or last matches. The examples
use version 0.1.0.
Installation
The tool is installed with go install, which puts the binary
in ~/go/bin. That directory must be in your
PATH.
go install github.com/janbodnar/monk@latest which monk monk --version
Alternatively, clone the repository with git clone
https://github.com/janbodnar/monk and build it with go build
-o monk . in the project directory.
$ go install github.com/janbodnar/monk@latest $ which monk /home/jano/go/bin/monk $ monk --version 0.1.0
Flags
The usage is monk [flags] [selectors]. Running
monk without a selector prints the help. The following table
lists the flags.
| Flag | Description |
|---|---|
-f, --file | Read HTML from a file |
-u, --url | Fetch HTML from a URL |
--text | Print only the text content |
--attr <name> | Print the value of an attribute |
--json | Print the matched nodes as JSON |
-n, --number | Print the number of matched elements |
-l, --limit | Limit the number of levels printed |
-i, --indent | Number of spaces, or a character, used for indentation |
-r, --raw | Raw output without added newlines and indentation |
-p, --plain | Don't escape HTML entities |
--pre | Preserve the whitespace of preformatted text |
--charset | Character set of the input |
-c, --color | Colour the output |
-v, --version | Print the version |
Selectors
Standard CSS selectors are supported, plus the extensions in the second part of the table.
| Selector | Description |
|---|---|
tag | Elements with the tag name |
#id | The element with the id |
.class | Elements with the class |
[attr], [attr="v"], [attr^="v"] | Elements with the attribute, an exact value, or a value prefix |
a b | Descendant: b anywhere inside a |
a > b | Child: b directly inside a |
a + b | Adjacent sibling: b right after a |
sel1, sel2 | Elements that match either selector |
:first-child, :last-child, :nth-child(n) | Elements by position among their siblings |
:contains("text") | Elements whose own text contains text |
:matches("regex") | Elements whose own text matches the regular expression |
:parent-of(sel) | Elements with a direct child that matches sel |
head(n), tail(n) | Keep the first or last n matches |
Selecting elements from a file
Most examples use the following page of a small bookstore. It has a
navigation bar, a list of books with classes and data-id
attributes, a table, and a footer.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bookstore</title>
</head>
<body>
<h1>Bookstore</h1>
<nav>
<a href="/">Home</a>
<a href="/books/">Books</a>
<a href="https://example.com/contact">Contact</a>
</nav>
<h2>New books</h2>
<p class="intro">Our latest arrivals & bestsellers.</p>
<ul id="books">
<li class="book" data-id="101"><span class="title">The Go Programming Language</span> <span class="price">34.99</span></li>
<li class="book sale" data-id="102"><span class="title">Learning SQL</span> <span class="price">29.50</span></li>
<li class="book" data-id="103"><span class="title">Linux Pocket Guide</span> <span class="price">19.99</span></li>
<li class="book sale" data-id="104"><span class="title">Grokking Algorithms</span> <span class="price">39.00</span></li>
</ul>
<h2>Opening hours</h2>
<table>
<tr><th>Day</th><th>Hours</th></tr>
<tr><td>Monday</td><td>9-17</td></tr>
<tr><td>Saturday</td><td>10-14</td></tr>
</table>
<footer>
<p>Contact: <a href="mailto:info@example.com">info@example.com</a></p>
</footer>
</body>
</html>
The -f option reads the file, and the argument is the
selector.
monk -f page.html h1 monk -f page.html title
By default, monk prints each matched element with its
attributes and children as an indented tree, with the text on separate
lines.
$ monk -f page.html h1 <h1> Bookstore </h1> $ monk -f page.html title <title> Bookstore </title>
Printing text
The --text option prints only the text content of the matched
elements.
monk -f page.html --text .title monk -f page.html --text .price
Each match is printed on its own line, which makes the output easy to
process with other tools, such as sort or
paste.
$ monk -f page.html --text .title The Go Programming Language Learning SQL Linux Pocket Guide Grokking Algorithms $ monk -f page.html --text .price 34.99 29.50 19.99 39.00
Printing attribute values
The --attr option prints the value of the given attribute.
monk -f page.html --attr href a monk -f page.html --attr data-id li
The first command lists all link targets of the page, the second the IDs
stored in the data-id attributes.
$ monk -f page.html --attr href a / /books/ https://example.com/contact mailto:info@example.com $ monk -f page.html --attr data-id li 101 102 103 104
Counting matches
The -n option prints the number of matched elements instead of
the elements.
monk -f page.html -n li monk -f page.html -n .sale
The page lists four books, two of them on sale.
$ monk -f page.html -n li 4 $ monk -f page.html -n .sale 2
Classes and IDs
A dot selects by class and a hash by ID. They can be combined with other selectors.
monk -f page.html .sale monk -f page.html --text '#books .sale .title'
An element can have several classes; class="book sale"
matches both .book and .sale. Quote selectors
that contain spaces, #, >, brackets, or
parentheses, so that the shell passes them unchanged; an unquoted
# starts a shell comment.
$ monk -f page.html .sale <li class="book sale" data-id="102"> <span class="title"> Learning SQL </span> <span class="price"> 29.50 </span> </li> <li class="book sale" data-id="104"> <span class="title"> Grokking Algorithms </span> <span class="price"> 39.00 </span> </li> $ monk -f page.html --text '#books .sale .title' Learning SQL Grokking Algorithms
Combinators
Combinators select elements by their relation to other elements.
monk -f page.html --text 'nav > a' monk -f page.html 'h2 + p' monk -f page.html --text 'h1, h2'
nav > a selects links that are direct children of the
navigation, so the mail link in the footer is not included.
h2 + p selects the paragraph right after a heading, and the
comma joins the results of two selectors.
$ monk -f page.html --text 'nav > a' Home Books Contact $ monk -f page.html 'h2 + p' <p class="intro"> Our latest arrivals & bestsellers. </p> $ monk -f page.html --text 'h1, h2' Bookstore New books Opening hours
Attribute selectors
Brackets select elements by their attributes and attribute values.
monk -f page.html --text '[data-id="103"] .title' monk -f page.html --attr href 'a[href^="http"]' monk -f page.html --text 'a[href^="mailto:"]'
^= matches values that start with the given text, which
distinguishes external links and e-mail links from relative ones. CSS also
defines $= (ends with) and *= (contains).
$ monk -f page.html --text '[data-id="103"] .title' Linux Pocket Guide $ monk -f page.html --attr href 'a[href^="http"]' https://example.com/contact $ monk -f page.html --text 'a[href^="mailto:"]' info@example.com
Position pseudo-classes
Pseudo-classes such as :nth-child select elements by their
position among their siblings.
monk -f page.html --text 'td:nth-child(2)' monk -f page.html --text 'tr:first-child th' monk -f page.html --text 'li:last-child .title'
td:nth-child(2) extracts the second column of the table, the
opening hours. The header row is the first child of the table, and the last
book is the last child of the list.
$ monk -f page.html --text 'td:nth-child(2)' 9-17 10-14 $ monk -f page.html --text 'tr:first-child th' Day Hours $ monk -f page.html --text 'li:last-child .title' Grokking Algorithms
Selecting by text
The :contains and :matches extensions select
elements by their text.
monk -f page.html --text 'span:contains("SQL")'
monk -f page.html --text 'li:contains("SQL")'
monk -f page.html --text 'span:matches("^[0-9]+\.99$")'
Both check only the element's own text nodes, not the text of its
children. That is why li:contains("SQL") finds nothing: the
title is inside a span, and the li itself holds
only a space between the two spans. The regular expression selects the
prices that end with .99.
$ monk -f page.html --text 'span:contains("SQL")'
Learning SQL
$ monk -f page.html --text 'li:contains("SQL")'
$ monk -f page.html --text 'span:matches("^[0-9]+\.99$")'
34.99
19.99
Selecting parents
The :parent-of extension selects elements that have a direct
child matching the inner selector.
monk -f page.html -n ':parent-of(a)' monk -f page.html --text 'p:parent-of(a)'
Two elements contain links: the navigation and the footer paragraph. Prefixing a tag name restricts the result to paragraphs.
$ monk -f page.html -n ':parent-of(a)' 2 $ monk -f page.html --text 'p:parent-of(a)' Contact: info@example.com
Keeping the first or last matches
The head(n) and tail(n) extensions, placed after
a selector, keep only the first or last n matches.
monk -f page.html --text '.title head(2)' monk -f page.html --text '.title tail(1)'
This is similar to piping the output to the head and
tail commands, but it works on elements, so it also works
with the tree and JSON output, where one element spans several lines.
$ monk -f page.html --text '.title head(2)' The Go Programming Language Learning SQL $ monk -f page.html --text '.title tail(1)' Grokking Algorithms
JSON output
The --json option prints the matched nodes as JSON, with the
tag name, attributes, text, and children.
monk -f page.html --json 'nav a' monk -f page.html --json .book | jq -r '.[] | [."data-id", .children[0].text, .children[1].text] | @tsv'
Attributes become keys of each object. Combined with jq, the
JSON output turns the book list into tab-separated records of ID, title,
and price, ready for a spreadsheet or a database import.
$ monk -f page.html --json 'nav a'
[
{
"href": "/",
"tag": "a",
"text": "Home"
},
{
"href": "/books/",
"tag": "a",
"text": "Books"
},
{
"href": "https://example.com/contact",
"tag": "a",
"text": "Contact"
}
]
$ monk -f page.html --json .book | jq -r '.[] | [."data-id", .children[0].text, .children[1].text] | @tsv'
101 The Go Programming Language 34.99
102 Learning SQL 29.50
103 Linux Pocket Guide 19.99
104 Grokking Algorithms 39.00
Limiting depth and indentation
The -l option limits how many levels of the tree are printed,
and -i sets the indentation.
monk -f page.html -l 1 ul monk -f page.html -i 4 nav
With -l 1, the children of the list are replaced by an
ellipsis, which gives a quick overview of a large element. -i
4 indents each level with four spaces instead of one.
$ monk -f page.html -l 1 ul
<ul id="books">
...
</ul>
$ monk -f page.html -i 4 nav
<nav>
<a href="/">
Home
</a>
<a href="/books/">
Books
</a>
<a href="https://example.com/contact">
Contact
</a>
</nav>
Raw and plain output
The -r option prints the elements without added newlines and
indentation. The -p option prints HTML entities unescaped.
monk -f page.html -r h2; echo monk -f page.html .intro monk -f page.html -p .intro
Raw output does not end with a newline, so echo adds one. By
default, the ampersand is printed as the entity &, as
in the source; with -p, it is printed as a plain
&.
$ monk -f page.html -r h2; echo <h2>New books</h2><h2>Opening hours</h2> $ monk -f page.html .intro <p class="intro"> Our latest arrivals & bestsellers. </p> $ monk -f page.html -p .intro <p class="intro"> Our latest arrivals & bestsellers. </p>
Preformatted text
The tree output re-indents text. For pre elements, where
whitespace is significant, the --pre option keeps it as it
is.
monk -f code.html pre monk -f code.html --pre pre
The file code.html contains a short shell loop in a
pre element. Without --pre, the first line is
re-indented; with it, the text is printed exactly as in the file.
$ monk -f code.html pre
<pre>
for i in 1 2 3
echo $i
</pre>
$ monk -f code.html --pre pre
<pre>for i in 1 2 3
echo $i
</pre>
Character sets
Pages without a charset declaration may be decoded wrongly. The
--charset option sets the encoding explicitly.
monk -f latin2.html --text p monk -f latin2.html --charset iso-8859-2 --text p
The file latin2.html contains Slovak text encoded in
ISO-8859-2, without a meta charset tag. Without the option,
the bytes are decoded as Windows-1252, the default for HTML, and the
accented letters are wrong. With --charset, they are correct.
$ monk -f latin2.html --text p ¾ivot èlovek $ monk -f latin2.html --charset iso-8859-2 --text p život človek
Fetching pages from the web
The -u option downloads a page from a URL. HTML can also be
piped to monk on standard input.
monk -u https://httpbin.org/html h1 monk -u https://httpbin.org/links/4/0 --attr href a curl -s https://httpbin.org/html | monk --text p | wc -w
The first command prints the heading of a sample page, and the second the
links of a generated page. Reading from standard input is useful when the
download needs options of curl, such as headers, cookies, or
authentication. The last command counts the words in the paragraphs of the
page.
$ monk -u https://httpbin.org/html h1 <h1> Herman Melville - Moby-Dick </h1> $ monk -u https://httpbin.org/links/4/0 --attr href a /links/4/1 /links/4/2 /links/4/3 $ curl -s https://httpbin.org/html | monk --text p | wc -w 601
Limitations
Version 0.1.0 has a few limitations that matter in scripts.
monk -f page.html 'article'; echo $? monk -f page.html -n 'article' monk -f page.html --text 'h2 ~ p'
When nothing matches, monk prints nothing and still exits with
status 0, so test for matches with -n instead of the exit
status. The general sibling combinator ~ is not supported and
silently returns nothing. Both are on the project's list of planned
features.
$ monk -f page.html 'article'; echo $? 0 $ monk -f page.html -n 'article' 0 $ monk -f page.html --text 'h2 ~ p'
Source
In this article, we have used the monk tool to select HTML
elements with CSS selectors and its extensions, and to print them as trees,
text, attribute values, and JSON, from files, standard input, and URLs.
Author
List all Linux tutorials.