ZetCode

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.

FlagDescription
-f, --fileRead HTML from a file
-u, --urlFetch HTML from a URL
--textPrint only the text content
--attr <name>Print the value of an attribute
--jsonPrint the matched nodes as JSON
-n, --numberPrint the number of matched elements
-l, --limitLimit the number of levels printed
-i, --indentNumber of spaces, or a character, used for indentation
-r, --rawRaw output without added newlines and indentation
-p, --plainDon't escape HTML entities
--prePreserve the whitespace of preformatted text
--charsetCharacter set of the input
-c, --colorColour the output
-v, --versionPrint the version

Selectors

Standard CSS selectors are supported, plus the extensions in the second part of the table.

SelectorDescription
tagElements with the tag name
#idThe element with the id
.classElements with the class
[attr], [attr="v"], [attr^="v"]Elements with the attribute, an exact value, or a value prefix
a bDescendant: b anywhere inside a
a > bChild: b directly inside a
a + bAdjacent sibling: b right after a
sel1, sel2Elements 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.

page.html
<!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 &amp; 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 &amp; 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 &amp;, 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 &amp; 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

monk GitHub repository

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.