実現したいこと

ブログの記事数や記事一覧を確認したい。hugo serverを起動して一覧ページを開く方法は手間がかかる。素のhugoでビルドするとpublic/が本番URLの生成物で上書きされ、起動中の開発サーバーの状態を壊してしまう。

hugo listはコンテンツを読み込んでCSVを標準出力へ返すコマンドである。生成物を一切書き出さないため、開発サーバーを起動したままでも安全に実行できる。

サブコマンド

hugo listは単体では動作せず、サブコマンドの指定が必要である。

サブコマンド対象
hugo list allすべてのコンテンツ
hugo list published公開状態のコンテンツ
hugo list draftsdraft: trueのコンテンツ
hugo list futurepublishDateが未来のコンテンツ
hugo list expiredexpiryDateを過ぎたコンテンツ

publishedは下書き・未来日付・期限切れのいずれにも該当しないコンテンツを返す。allはそれらをすべて含む。publishDateを指定していない場合はdateの値が使われるため、dateに未来の日付を書いた記事もfutureに含まれる。

出力はCSV形式

標準出力へ返るのは、先頭にヘッダー行が付いたCSVである。

$ hugo list published | head -3
path,slug,title,date,expiryDate,publishDate,draft,permalink,kind,section
content/hugo/list-command/index.md,,【Hugo】hugo listでサーバーを起動せずに記事一覧と件数を確認する,2026-08-11T16:00:47+09:00,0001-01-01T00:00:00Z,2026-08-11T16:00:47+09:00,false,https://example.com/hugo/list-command/,page,hugo
content/linux/dmesg-human-readable-timestamp/index.md,,【Linux】dmesgのタイムスタンプを日時で表示する(-T),2026-08-11T14:48:53+09:00,0001-01-01T00:00:00Z,2026-08-11T14:48:53+09:00,false,https://example.com/linux/dmesg-human-readable-timestamp/,page,linux

行の並び順はHugoの既定のページ順序に従う。weightを設定していなければ日付の新しい順である。

列は以下の10個である。

列名内容
pathプロジェクトルートからのファイルパス
slugfront matterのslug
titlefront matterのtitle
datefront matterのdate
expiryDatefront matterのexpiryDate(未設定時は0001-01-01T00:00:00Z)
publishDatefront matterのpublishDate(未設定時はdateと同値)
draft下書きかどうか
permalink公開URL
kindページの種類
section所属セクション

permalinkは設定ファイルのbaseURLから組み立てられる。hugohugo serverと違いhugo list-b(--baseURL)は無いため、ローカルのURLで確認したい場合は環境変数HUGO_BASEURLで上書きする。

$ HUGO_BASEURL=http://localhost:1313/ hugo list published | sed -n '2p' | cut -d',' -f8-
http://localhost:1313/hugo/list-command/,page,hugo

kindでブログ記事だけを数える

行数をそのまま数えると、ブログ記事以外のページも混ざる。kind列で種類を確認する。

$ hugo list published | tail -n +2 | awk -F',' '{print $(NF-1)}' | sort | uniq -c
 481 page
   2 section

kindpageのページが記事本体である。sectionの2件はcontent/contact/_index.mdcontent/privacy-policy/_index.mdで、お問い合わせページとプライバシーポリシーページに当たる。_index.mdで作った固定ページはセクションのトップページとして扱われるため、kindsectionになる。

参考: 【Hugo】sectionページを使ってプライバシーポリシーページを作る

記事の件数だけを取り出すにはkindで絞り込む。

$ hugo list published | awk -F',' '$(NF-1)=="page"' | wc -l
481

ヘッダー行のkind列は文字列kindであり条件に一致しないため、tail -n +2を省いてもヘッダーは数に入らない。

カテゴリ別に集計する

最終列のsectionを使うと、カテゴリごとの記事数が求められる。

$ hugo list published | awk -F',' '$(NF-1)=="page" {print $NF}' | sort | uniq -c | sort -rn
 192 linux
  73 android
  52 aws
  50 database
  32 terraform
  26 vscode
  26 mac
  11 java
   9 hugo
   5 ruby
   5 golang

公開されない記事を洗い出す

draft: trueのまま残っている書きかけの記事はhugo list draftsで確認できる。

$ hugo list drafts
path,slug,title,date,expiryDate,publishDate,draft,permalink,kind,section
content/hugo/sample-draft/index.md,,書きかけの記事,2026-08-11T09:00:00+09:00,0001-01-01T00:00:00Z,2026-08-11T09:00:00+09:00,true,https://example.com/hugo/sample-draft/,page,hugo

下書きが1本あるとallpublishedの件数に差が出る。

$ hugo list all | awk -F',' '$(NF-1)=="page"' | wc -l
482
$ hugo list published | awk -F',' '$(NF-1)=="page"' | wc -l
481

未来日付で予約公開した記事はhugo list futureexpiryDateを過ぎて公開が止まった記事はhugo list expiredで同様に確認できる。

タイトルのカンマに注意する

titleにカンマが含まれる行は、CSVの仕様どおりダブルクォートで囲まれる。

content/database/explain-analyze-buffers/index.md,,"【PostgreSQL】EXPLAIN (ANALYZE, BUFFERS)でキャッシュヒット率を確認する",2026-07-22T07:17:52+09:00,...

cutawkはクォートを解釈しないため、列番号を前から数えるとタイトルが途中で切れる。

$ hugo list published | grep '"' | head -2 | cut -d',' -f3
"【PostgreSQL】EXPLAIN (ANALYZE
"【Android

前述の集計で$(NF-1)$NFと末尾から数えているのは、この問題を避けるためである。titleより後ろの列にはカンマが入らないため、末尾からの位置指定であればずれない。

タイトルを含めて正しく扱う場合はCSVパーサーを使う。

$ hugo list published | python3 -c '
import csv, sys
for r in csv.DictReader(sys.stdin):
    if "," in r["title"]:
        print(r["title"])
' | head -2
【PostgreSQL】EXPLAIN (ANALYZE, BUFFERS)でキャッシュヒット率を確認する
【Android, Kotlin】AppBarに検索ボックスを設置する

Pythonのcsvモジュールはクォートを解釈するため、カンマを含むタイトルもそのまま取り出せる。

ファイルの実体を持つページだけが対象

hugo listはサイト内の全ページのうち、対応するファイルを持つページだけを出力する。トップページやタクソノミーページのようにHugoが自動生成するページは、content/にファイルがなければ出力されない。前述の集計でkindpagesectionしか現れないのはそのためである。

実際、ビルドするとpublic/tags/public/categories/にタクソノミーのページが生成されるが、対応するファイルがcontent/に無いためhugo listの出力には現れない。

一方でcontent/contact/_index.mdのようにファイルが存在する場合は、記事以外でも出力に含まれる。ファイルの有無で決まるため、kindでの絞り込みが必要になる。

public/を書き換えない

hugo listは生成物を出力しないため、public/の中身は変化しない。マーカーファイルを置いて、実行後に更新されたファイルを数えると0件である。

$ touch /tmp/marker
$ hugo list published > /dev/null
$ find public -newer /tmp/marker | wc -l
0

hugo serverを起動したまま実行しても開発サーバーの状態は壊れない。

参考: 【Hugo】-dオプションでビルド結果を別ディレクトリに出力する

別ディレクトリのサイトを対象にする

-sでプロジェクトのパスを指定すると、カレントディレクトリを移動せずに実行できる。

$ hugo list published -s ~/blog/my-hugo-site | awk -F',' '$(NF-1)=="page"' | wc -l
481

参考: hugo list | Hugo
参考: hugo/commands/list.go at master · gohugoio/hugo