showme is a bash script to visualize the pdf file of a paper I've got in my collection:
showme delvalle12a
opens the pdf file. It works if the file is in the bibTeX database (i.e., in ~/bib somewhere) and has .pdf extension. There is bash autocompletion, which is welcome for zubi... papers (?!).
One can use another viewer or application (e.g., gimp):
showme delvalle12a --with evince
the --with can be put anywhere.
The option -2 also opens another copy in the right window manager (which I often need to work on two copies simultaneously):
showme delvalle12a -2
The option -p (or --path) prints where the file is instead of opening it, which is what one wants when feeding the path to something else:
showme delvalle12a -p
The option -v (or --version) displays the cromulon headâthe script prints its own header, so the version and its date come straight from the horse's mouth.
Three things are looked at, in order, and the first that yields a match wins.
First locate, as always. Then, if that came up empty, ~/bib/sci and ~/bib/transit are searched directly: locate's database is rebuilt once a night, so a paper filed this morning is invisible to it until tomorrow and would otherwise be declared missing.
Finally, if the paper is in neither place, ~/Downloads is searchedâbecause sometimes I download a paper and forget to file it. If it is there, it is moved into ~/bib/transit and then opened, so the mistake repairs itself the first time I go looking for the paper:
$ showme basov25a
showme: filed basov25a.pdf from Downloads into /home/laussy/bib/transit
An exact <key>.pdf wins outright and only then is a looser *key* match tried. A browser's duplicate suffix is undone on the wayârau14a (1).pdf is filed as rau14a.pdfâbut only when the rest of the name is exactly the search term, so that showme rau14 can never truncate rau14a.pdf to rau14.pdf. Nothing in ~/bib/transit is ever overwritten: if the paper turns out to be filed already, that copy is opened and the download is left where it is. And if several candidates match loosely, they are listed and nothing is moved, since guessing which one I meant is worse than not guessing.
If there is no pdf anywhere but the key is in the bibTeX database (sci.bib, arXiv.bib or Books.bib), the entry is displayed instead of an error, both raw and nicely formattedâthe journal @string abbreviations expanded, the TeX accents turned into Unicode and the title set in italics:
$ showme agarwal90b
showme: no PDF for 'agarwal90b', but the bibliography knows it (sci.bib):
@Article{agarwal90b,
author = {G. S. Agarwal},
title = {Dressed-state lasers and masers},
journal = pra,
year = 1990,
volume = 42,
pages = 686,
url = {doi:10.1103/PhysRevA.42.686}
}
G. S. Agarwal,
âDressed-state lasers and masersâ,
Phys. Rev. A 42, 686 (1990).
See sci huh? [y/N]
The last line appears when the entry carries a doi (fished out of the url or doi field): answering y opens chrome on the paper at the alternative supplier, https://sci-hub.ru/10.1103/PhysRevA.42.686 in this case.
The first version that served me well for almost 600 days was a simple hack:
#!/bin/bash
# v°0.1 Sun 31 Dec 2023
okular `locate $1 | grep -i ".pdf$" | grep -i bib`
Last version:
#!/bin/bash
# #@=----===@%:
# :#:::-=---===+=@=-
# +=%* :-+=:.... .:@- ____ _
# @-+#-*%+**+#=+*#@@@@#@ / ___|| |__ _____ _
# @=%.% # @ .# %= \___ \| '_ \ / _ \ \ /\ / /
# #:.. @ @# * @ @# ## ___) | | | | (_) \ V V /
# #.=:% .%. @-=* @% |__ __| |_|_|\___/ \_/\_/
# %@.*@ :#@@@-. @ +%@%- @ | \/ | ___
# *@@% #@ .. =+.. ...@ | |\/| |/ _ \ what
# @:#-.:-= .:.+#-: .. : @ | | | | __/ you got
# @.:---::@. ..:-++=:.-%.% |_| |_|\___|
# @=-:-:@ @++==-:-=++-@%.
# @-::. : . . .. . :@
# @:--:...........=* v2.1 Wed Sep 17 2025
# **=-=::.......:.@ 2.4 Fri Aug 7 2026
# *=:-=.:::::::: @ 3.0 Mon Aug 10 2026
# %@.::.::::::.+# laussy.org/wiki/showme
# -@@+=:.....+@ F.P. Laussy
# Default program
PROGRAM="okular"
# Parse options with getopt
PARSED=$(getopt -o w:2pv -l with:,two,path,version -n 'showme' -- "$@")
if [ $? -ne 0 ]; then
echo "Error: Invalid option" >&2
exit 1
fi
eval set -- "$PARSED"
# Initialize two-files flag
TWO_FILES=false
# Initialize path-only flag
PATH_ONLY=false
# Process options
while true; do
case "$1" in
-w|--with)
PROGRAM="$2"
shift 2
;;
-2|--two)
TWO_FILES=true
shift
;;
-p|--path)
PATH_ONLY=true
shift
;;
-v|--version)
# Show me what you got: the cromulon head is its own version banner
sed -n '/^# #@=/,/^# -@@/p' "$0" | cut -c2-
exit 0
;;
--)
shift
break
;;
*)
echo "Error: Invalid option" >&2
exit 1
;;
esac
done
# Check if a search term is provided
if [ -z "$1" ]; then
echo "Error: No search term provided" >&2
exit 1
fi
# Where papers live, and where they land when the download goes wrong
SCIDIR="$HOME/bib/sci"
TRANSITDIR="$HOME/bib/transit"
DOWNLOADS="$HOME/Downloads"
# Get matching PDF files
FILES=($(locate "$1" | grep -i ".pdf$" | grep -i bib))
# locate's database is rebuilt once a night, so a paper filed today is invisible
# to it. Look at the real directories before giving up on them.
if [ ${#FILES[@]} -eq 0 ]; then
while IFS= read -r f; do
FILES+=("$f")
done < <(find "$SCIDIR" "$TRANSITDIR" -maxdepth 1 -iname "*$1*.pdf" 2>/dev/null | sort)
fi
# Still nothing: it may have been downloaded to the wrong place. Fetch it back.
if [ ${#FILES[@]} -eq 0 ] && [ -d "$DOWNLOADS" ]; then
DL=()
# an exact <key>.pdf wins outright; only then try a looser match
while IFS= read -r f; do
DL+=("$f")
done < <(find "$DOWNLOADS" -maxdepth 1 -iname "$1.pdf" 2>/dev/null)
if [ ${#DL[@]} -eq 0 ]; then
while IFS= read -r f; do
DL+=("$f")
done < <(find "$DOWNLOADS" -maxdepth 1 -iname "*$1*.pdf" 2>/dev/null | sort)
fi
if [ ${#DL[@]} -gt 1 ]; then
echo "Error: several candidates in $DOWNLOADS, file the right one yourself:" >&2
printf ' %s\n' "${DL[@]}" >&2
exit 1
fi
if [ ${#DL[@]} -eq 1 ]; then
SRC="${DL[0]}"
NAME="${SRC##*/}"
# "rau14a (1).pdf" downloaded twice is still rau14a.pdf
case "$NAME" in
"$1 ("*").pdf") NAME="$1.pdf" ;;
esac
DEST="$TRANSITDIR/$NAME"
if [ -e "$DEST" ]; then
echo "showme: $DEST already exists, leaving $SRC alone" >&2
FILES=("$DEST")
elif mv -n "$SRC" "$DEST"; then
echo "showme: filed $NAME from Downloads into $TRANSITDIR"
FILES=("$DEST")
else
echo "Error: could not move $SRC to $TRANSITDIR" >&2
exit 1
fi
fi
fi
# No PDF anywhere: maybe the paper is only on record in the bibliography.
# Show its BibTeX entry instead, and offer the alternative supplier.
if [ ${#FILES[@]} -eq 0 ]; then
if [ "$PATH_ONLY" = true ]; then
echo "Error: No matching PDF files found" >&2
exit 1
fi
BIBFILES=("$HOME/bib/sci.bib" "$HOME/bib/arXiv.bib" "$HOME/bib/Books.bib")
ENTRY=
for BIBFILE in "${BIBFILES[@]}"; do
[ -r "$BIBFILE" ] || continue
ENTRY=$(awk -v key="$1" '
BEGIN { IGNORECASE = 1 }
!found && $0 ~ ("^@[A-Za-z]+[{(][ \t]*" key "[ \t]*,") { found = 1 }
found { print }
found && /^[})]/ { exit }
' "$BIBFILE")
[ -n "$ENTRY" ] && break
done
if [ -z "$ENTRY" ]; then
echo "Error: No matching PDF files found, and no BibTeX entry for '$1' either" >&2
exit 1
fi
echo "showme: no PDF for '$1', but the bibliography knows it (${BIBFILE##*/}):"
echo
printf '%s\n' "$ENTRY"
echo
# A one-line field: strip "name = " and the {âŠ}/"âŠ" wrapping
bibfield() {
printf '%s\n' "$ENTRY" | grep -i -m1 "^[[:space:]]*$1[[:space:]]*=" \
| sed -E 's/^[^=]*=[[:space:]]*//; s/^[{"]//; s/[}"]?,?[[:space:]]*$//'
}
# De-TeX the common accents and braces for terminal display
detex() {
sed -E 's/\\"([aouAOUei])/\1\xcc\x88/g; s/\\'"'"'([aeiouyAEIOU])/\1\xcc\x81/g;
s/\\`([aeiouAEIOU])/\1\xcc\x80/g; s/\\\^([aeiouAEIOU])/\1\xcc\x82/g;
s/\\~([nNaAoO])/\1\xcc\x83/g; s/\\c\{?c\}?/c\xcc\xa7/g; s/\\ss\{?\}?/\xc3\x9f/g;
s/\\\.\{?([a-zA-Z])\}?/\1\xcc\x87/g; s/\\u\{?([a-zA-Z])\}?/\1\xcc\x86/g;
s/\\v\{?([a-zA-Z])\}?/\1\xcc\x8c/g; s/\\H\{?([a-zA-Z])\}?/\1\xcc\x8b/g;
s/\\o\{?\}?([^a-zA-Z])/\xc3\xb8\1/g; s/\\l\{?\}?([^a-zA-Z])/\xc5\x82\1/g;
s/\\aa\{?\}?/\xc3\xa5/g; s/\\ae\{?\}?/\xc3\xa6/g;
s/\\([A-Za-z])/\1/g;
s/[{}]//g; s/~/ /g; s/---/\xe2\x80\x94/g; s/--/\xe2\x80\x93/g'
}
AUTHOR=$(bibfield author | detex | sed 's/ and /, /g')
TITLE=$(bibfield title | detex)
JOURNAL=$(bibfield journal)
YEAR=$(bibfield year)
VOLUME=$(bibfield volume)
PAGES=$(bibfield pages)
# journal = prl and friends are @string abbreviations defined in sci.bib
if [ -n "$JOURNAL" ]; then
EXPANDED=$(grep -i -m1 "^@string{[[:space:]]*$JOURNAL[[:space:]]*=" "$HOME/bib/sci.bib" 2>/dev/null \
| sed -E 's/^[^=]*=[[:space:]]*"//; s/"[[:space:]]*}.*$//')
[ -n "$EXPANDED" ] && JOURNAL="$EXPANDED"
fi
BOLD=$'\e[1m'; ITAL=$'\e[3m'; OFF=$'\e[0m'
printf ' %s,\n' "$AUTHOR"
printf ' %sâ%sâ,%s\n' "$ITAL" "$TITLE" "$OFF"
REF=" ${BOLD}${JOURNAL}${OFF}"
[ -n "$VOLUME" ] && REF="$REF ${BOLD}${VOLUME}${OFF}"
[ -n "$PAGES" ] && REF="$REF, $PAGES"
[ -n "$YEAR" ] && REF="$REF ($YEAR)"
printf '%s.\n\n' "$REF"
# The DOI hides as doi:10.xxxx/⊠inside the url (or doi) field
DOI=$(printf '%s\n' "$ENTRY" | grep -o -m1 'doi:10\.[^},"]*' | head -1 | sed 's/^doi://')
[ -z "$DOI" ] && DOI=$(bibfield doi | grep -o '10\.[^},"]*')
if [ -n "$DOI" ] && [ -t 0 ]; then
read -r -p "See sci huh? [y/N] " ANSWER
case "$ANSWER" in
[yY]*) google-chrome "https://sci-hub.ru/$DOI" &>/dev/null & ;;
esac
fi
exit 0
fi
if [ "$PATH_ONLY" = true ]; then
# Just print the location(s) of the matching file(s), don't open anything
printf '%s\n' "${FILES[@]}"
elif [ "$TWO_FILES" = true ]; then
# Open the first instance on the current desktop
$PROGRAM "${FILES[0]}" &>/dev/null &
# Open the same file again in a second instance
$PROGRAM "${FILES[0]}" &>/dev/null &
# Wait briefly for the second window to appear
sleep 0.5
# Get the window ID of the second instance
WINDOW_ID=$(wmctrl -l | grep -i "$PROGRAM" | tail -n 1 | awk '{print $1}')
# Get the current desktop number
CURRENT_DESKTOP=$(wmctrl -d | grep '*' | awk '{print $1}')
# Calculate the next desktop number
TOTAL_DESKTOPS=$(wmctrl -d | wc -l)
NEXT_DESKTOP=$(( (CURRENT_DESKTOP + 1) % TOTAL_DESKTOPS ))
# Move the second window to the next desktop
wmctrl -i -r "$WINDOW_ID" -t "$NEXT_DESKTOP"
else
# Original behavior: open the first file
$PROGRAM "${FILES[0]}" &>/dev/null &
fi
Bash autocompletion is taken care of in showme-completion.bash in /etc/bash_completion.d/. This can work for other similar commands, such as bib2wiki by registering them on the last line.
#!/bin/bash
_showme_completion() {
local cur prev
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# Directories to search for PDFs
local search_dirs=("$HOME/bib/transit" "$HOME/bib/sci")
# Handle options
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "-w --with" -- "$cur") )
return 0
fi
# If previous word is -w or --with, suggest programs
if [[ "$prev" == "-w" || "$prev" == "--with" ]]; then
COMPREPLY=( $(compgen -W "okular evince acroread" -- "$cur") )
return 0
fi
# Complete PDF filenames from both ~/bib/transit and ~/bib/sci
local files=""
for dir in "${search_dirs[@]}"; do
if [ -d "$dir" ]; then
# Get list of PDFs in the directory, strip .pdf extension
files+=$(find "$dir" -maxdepth 1 -type f -iname "*.pdf" -printf "%f\n" | sed 's/\.pdf$//' | sed "s|^$dir/||")
files+=" "
fi
done
# Generate completions if files were found
if [ -n "$files" ]; then
COMPREPLY=( $(compgen -W "$files" -- "$cur") )
fi
return 0
}
# Register the completion function for multiple commands
complete -F _showme_completion showme
complete -F _showme_completion bib2wiki