Skip to content

Repository files navigation

Manage bookmarks with dmenu and a basic file.

The file is Tab Separated Values, one bookmark per line:

    URL<tab>description<tab>tag tag tag

For example:

    https://equwal.com	Spenser Truex's website.	users

Lines starting with # are ignored. bm is one way to work with the file;
cut, grep, awk and your editor are others.

== Design ==
bm owns the bookmark file and talks to the menu. Everything else is a small
filter that reads or writes bookmark lines, so the pieces combine like any
other Unix tools, and you install only the ones you use:

    bm          pick, open, copy, add, edit, delete, list, merge
    bm-import   browser bookmarks        -> bookmark lines
    bm-check    bookmark lines           -> the dead links among them
    bm-html     bookmark lines           -> a searchable web page
    bm-migrate  the old sbm file format  -> bookmark lines
    bm-title    a URL                    -> the page's title
    bm-page     a URL                    -> the page as text
    bm-commit   commit the bookmark file to git
    bm-watch    browser bookmarks        -> bm, now and after each change
    bm-sync     the bookmark file        <-> your other devices

    bm-import bookmarks.html | bm --merge
    bm --tag code --list | bm-check -
    bm --list | grep -i wiki | bm-html -t Wikis - > wikis.html

Everything is POSIX sh and POSIX utilities: no bash, no mktemp, no GNU
options. make check runs shellcheck -s sh over all of it. Where the usual
tool is not in POSIX, the script says what stands in for it: noclobber for
mktemp, a FIFO of tokens for xargs -P, dd for head -c, awk's srand() for
date +%s.

== Requirements ==
dmenu (or fzf), one of xclip, xsel or wl-clipboard, sh, awk, make.
curl for bm-title, bm-page, bm-check and bm-sync, jq for bm-import of
Chromium files, git for bm-commit.

== Install ==
Edit TOOLS in config.mk to choose what gets installed, then

    make install                        # to /usr/local/bin
    make PREFIX="$HOME/.local" install

Each release on GitHub also has an installer for Windows and a portable app
for macOS. See Windows and macOS below.

== Setup ==
Nothing is required. Bookmarks are kept in ~/.local/share/sbm/bookmarks
($XDG_DATA_HOME is honoured) and the file is created on first use.

Tag choices are read from ~/.local/share/sbm/usertags:

    <tag><space>|<space><description><newline>

Start from the included example, or from nothing: a tag typed into the tag
menu that is not in the file yet is added to it.

    mkdir -p ~/.local/share/sbm
    cp usertags engines ~/.local/share/sbm/

== Usage ==
    bm                   browse (see below)
    bm -o, --open        open a bookmark, an address, or a web search
                         (-p, --plumb is the same)
    bm -c, --copy        copy a bookmark's URL to the clipboard
    bm --print           print a bookmark's URL
    bm -a, --add [url]   add a bookmark
    bm -e, --edit        open the file in $VISUAL/$EDITOR at a bookmark
    bm -d, --delete      delete a bookmark
    bm -l, --list        print the bookmarks
    bm -m, --merge       add the bookmark lines on stdin, skipping known ones
    -t, --tag TAG        restrict any of the above to one tag
    -S, --sort ORDER     used (default), recent, url, desc or tag

Bind "bm" and "bm -a" to keys in your window manager.

== Browsing ==
Plain "bm" lists the bookmarks. Pick one and a second menu shows it in full
(dmenu cannot preview while you scroll, so this is its preview) and asks what
to do:

    open
    copy
    edit
    delete
      https://suckless.org/
      software that sucks less
      tags: code org
      used: 12

Closing the second menu goes back to the list, closing the list leaves.
[add] at the top of the list adds a bookmark.

With fzf (in a terminal, with no display, or SBM_MENU=fzf) it is one screen:
the bookmark under the cursor is previewed live, and keys do the rest. With
bm-page installed, the preview also shows the text of the page: its title,
then its main part. bm-page fetches the page with curl (5 second limit, at
most 512k; SBM_FETCH=0 turns it off). make install also adds "sbm" to the
app menu of a Linux desktop: it opens bm with fzf in a terminal.

    ENTER   open            ctrl-a  add            ctrl-s  next sort order
    ctrl-y  copy            ctrl-d  delete (asks)
    ESC     leave           ctrl-e  edit

To see the bookmarks of a tag, type the tag. fzf searches the tags too.

== Web search ==
In the list, and in "bm -o", text that is not a bookmark is not wasted:

    suckless.org/dwm          one word with a dot or "://": opened as an address
    posix sh printf           anything else: searched for on the web
    w dynamic menu            first word is a keyword: that engine is used

So one key gives you bookmarks first and the web behind them. The default
engine is DuckDuckGo; set another with SBM_SEARCH='https://.../?q=%s'.
Keywords are read from ~/.local/share/sbm/engines:

    <keyword><space>|<space><url with %s><newline>

See the included engines file.

== Adding ==
Without a url, bm offers the X11 primary selection, or the clipboard, when
it holds a URL. With bm-title installed the description defaults to the
page's title (curl, 3 second limit; SBM_FETCH=0 turns it off). In dmenu the
default is the one entry in the menu: Return accepts it, Shift-Return sends
exactly what you typed.

Tags are chosen one at a time until "done" is picked or the menu is closed
(fzf: TAB marks, ENTER confirms). Typing a tag that does not exist creates it.

A URL that is already bookmarked is refused (exit status 3). URLs are
compared without their scheme, a leading www. and trailing slashes.

== Sorting ==
    used     most opened or copied first, then by url (the default)
    recent   most recently added first
    url, desc, tag

Set a default with SBM_SORT. Use counts live in "$BOOKMARKS.usage", so the
bookmark file itself stays a plain list.

== A web page of your bookmarks ==
    bm-html > ~/public_html/bookmarks.html

One self-contained file: no server side, nothing loaded from elsewhere.
Bookmarks are listed under each of their tags, with the tags linked at the
top. A dozen lines of inline script add a filter box; without script the page
is still the complete list, and Ctrl-F works. page.html?q=words starts out
filtered, so the page can be a search keyword in your browser, or in bm:

    b | https://example.org/bookmarks.html?q=%s

Links that would run script (javascript:, data:) are never written.

== Importing ==
    bm-import bookmarks.html | bm --merge     # the HTML export of any browser
    bm-import vivaldi | bm --merge            # or chrome, brave, edge, chromium
    bm-import path/to/Bookmarks | bm --merge  # a Chromium JSON file (needs jq)

A browser name imports the bookmarks of all profiles of that browser. The
profiles are in ~/.config, in ~/Library/Application Support on macOS, or in
%LOCALAPPDATA% on Windows. Folder names become tags ("Dev Tools" becomes
dev-tools). bm --merge skips what you already have, so importing twice adds
nothing. To sync new browser bookmarks into bm, run the same command again.
Look before you merge: bm-import prints plain bookmark lines.

bm-watch does this import at once, and again each time that the bookmark
file of a profile changes. It only adds bookmarks, and it never writes to
the browser:

    bm-watch brave &

== Dead links ==
    bm-check

prints status<tab>url for links that fail, and status<tab>url<tab>new-url for
ones that moved for good. It exits 1 if it printed anything.

== History and sync with git ==
With bm-commit installed, every change bm makes is committed, provided the
directory holding the bookmark file is a git repository of its own. It never
pushes or pulls.

    cd ~/.local/share/sbm && git init
    printf '%s\n' '*.usage' '*.bak*' > .gitignore

A file that merely sits inside a bigger repository (your dotfiles) is left
alone unless SBM_GIT=1. SBM_GIT=0 turns commits off.

== Sync ==
bm-sync keeps the bookmark file the same on all your computers, the sbm app
for Android and the sbm add-on for Firefox and Chrome.

    bm-sync login        # once on each computer: email and password
    bm-sync              # sync now

When bm-sync is installed and signed in, bm syncs after each change, in the
background. Bookmarks that you add or remove on one device are added or
removed on the others. The file stays a plain file, and bm works without a
network. The sign-in token is kept in ~/.config/sbm/sync.

An empty or missing bookmark file removes nothing on the other devices:
bm-sync gets all the bookmarks back from the server.

bm and bm-sync lock the file for each change, with the directory
$BOOKMARKS.lock, so that neither writes over a change of the other. bm -e
holds the lock until the editor ends; bm-sync waits for it, and bm syncs
the edit after. A lock left by a process that is gone is taken over.

The server is sbm-sync (https://github.com/equwal/sbm-sync). bm-sync uses
https://sbmsync.com unless you give another server: create an account
there. The server is free software, so you can run your own:

    bm-sync login https://sbm.example.org

The sbm app for Android is at https://github.com/equwal/sbm-android. A demo of
sync between bm and the browser add-on (80 seconds):
https://github.com/equwal/sbm-sync#sbm-sync

== Migrating from the old format ==
Earlier versions wrote "URL description | tag tag".

    bm-migrate                  # $BOOKMARKS, or the default file
    bm-migrate -n file          # only show what it would become
    bm-migrate - < old > new    # as a filter

The original is kept as .bak, and an existing .bak is never overwritten.

== Configuration ==
All through the environment, all optional.

    BOOKMARKS     bookmark file
    USERTAGS      tag choices file
    DMENULINES    lines shown by dmenu (default 15)
    SBM_MENU      dmenu, fzf, or a custom command
    SBM_FZF_OPTS  extra options for every fzf call
    SBM_SORT      default sort order
    SBM_SEARCH    default search engine, with %s for the query
    SBM_ENGINES   search keywords file
    SBM_COPY      command that reads the new clipboard contents from stdin
    SBM_PASTE     command that writes the clipboard contents to stdout
    SBM_OPEN      command that opens the URL given as its argument
    SBM_LOCK_WAIT seconds that bm and bm-sync wait for the lock (default 10)

With a display bm uses dmenu, unless bm runs in a terminal: there it uses
fzf, whose preview shows the page. fzf is also used when dmenu is missing or
there is no X11 or Wayland display. SBM_MENU=dmenu keeps dmenu in a
terminal. A custom SBM_MENU is called with the
choices on stdin as one of:

    <command> pick  <prompt>     print the chosen line, or the typed text
    <command> multi <prompt>     print every chosen line
    <command> ask   <prompt>     print free text; stdin holds a default, if any

Printing nothing means the user cancelled. test/fakemenu is a small example.

== Windows ==
bm runs under Cygwin. Get sbm-VERSION-setup.exe from a release on GitHub and
start it. The installer puts bm and the bm-* tools in /usr/local/bin. It
makes a Start menu entry and a desktop shortcut that open bm in a mintty
window, and Esc closes the window. The key of the desktop shortcut is
Ctrl+Alt+Shift+F24. Almost no keyboard has an F24 key, so keyboard firmware
can use this key combination without conflicts.

The installer imports the bookmarks of all profiles of the Chromium browsers
(Brave, Chrome, Chromium, Edge and Vivaldi). A Startup entry, sbm-watch, runs
bm-watch at each login, so new browser bookmarks come into bm a few seconds
after you add them. The import needs the jq package of Cygwin.

The installer also puts fzf.exe, the Windows build of fzf, in
/usr/local/libexec/sbm. Do not use the fzf package of Cygwin. That package is
version 0.11 from 2016, and it has no --preview and no --expect. The wrapper
/usr/local/bin/fzf lets fzf.exe run in mintty. /etc/profile.d/sbm.sh sets
SBM_OPEN, SBM_COPY, SBM_PASTE and SBM_FZF_OPTS for Cygwin. Settings in
~/.bash_profile win.

For bm-title, bm-page, bm-check and bm-import, install the curl and jq
packages of Cygwin. The Windows builds of curl and jq do not understand
Cygwin paths, such as /dev/null. To remove sbm, use Apps in the Windows settings. The
uninstaller keeps your bookmarks. contrib/windows/build-installer.sh makes
the installer with NSIS.

== macOS ==
Get sbm-VERSION.dmg from a release on GitHub, and drag sbm.app to
Applications. The app contains bm, the bm-* tools and fzf, so it needs no
other software. sbm.app opens bm in a Terminal window. The first time, macOS
asks if sbm can control Terminal.

The app has no signature from a registered developer, so macOS stops it the
first time. To open it, use Open Anyway in System Settings, Privacy &
Security. Or remove the quarantine attribute:

    xattr -dr com.apple.quarantine /Applications/sbm.app

To open sbm with a key, make a shortcut with the action Open App in the
Shortcuts app, and give it a keyboard shortcut. macOS has function keys up
to F20. To use the tools in a shell, add these lines to ~/.zprofile:

    PATH=/Applications/sbm.app/Contents/Resources/bin:$PATH
    export SBM_OPEN=open SBM_COPY=pbcopy SBM_PASTE=pbpaste

contrib/macos/build-dmg.sh makes the image on a Mac.

== Tests ==
    make check

runs shellcheck, when installed, and test/run.sh, under sh. Run the tests
under another shell with SBM_SH=dash sh test/run.sh. They script dmenu, fzf
and curl, so they need no display and no network.

== Releases ==
Set VERSION in config.mk. Tag the release as vVERSION. make dist makes the
tarball. contrib/windows/build-installer.sh makes the Windows installer. On
a Mac, contrib/macos/build-dmg.sh makes the image. Attach the three files to
the GitHub release of the tag.

== Credits ==
Some ideas taken from Karl Voit:
https://karl-voit.at/2022/01/29/How-to-Use-Tags/

== License ==
AGPL-3.0: see LICENSE. If you run a changed version for other people over a
network, give them its source code.

If sbm is useful to you, you can support it on Ko-fi:
https://ko-fi.com/truex

About

dmenu bookmarks. Instantly fuzzy search thousands of bookmarks and plumb them. LOOKING FOR SUCKLESS SOFTWARE EDITION? GO TO sbm-suckless INSTEAD

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages