
This page is among our best articles.
showme is a bash script to show me what I got.
Invoked on a keyword, it will figure out what the most likely thing this keyword refers to, and show it to me in the corresponding viewer. It will offers alternatives if there is doubt, and can even retrieve content from the web if it's not found locally.
It is used mainly with material which I tend to watch a lot. This includes: 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 (?!).
Since v°4.0, it opens any other file as well, found by its name alone: see Any file.
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.
The option -h (or --help), since v°4.2, prints a summary of what is on this page.
With nothing to show at all, since v°4.1, showme alone has the cromulon ask for something: the head of the banner is drawn and its mouth says Show me what you got, a shape for each sound (sh-ow, m-ee, wh-a-t, y-ou, g-o-t), the jaw dropping as it opens, while the words of the logo come out one by one as they are said. Off a terminal, or on one too narrow, the head is just printed, words and all.
A doi does as well as a key, since v°3.2, in whichever form it comes pastedâbare, with its doi: prefix, as a doi.org address, or with the reference list's period still attached:
$ showme 10.1038/1781449a0
showme: 10.1038/1781449a0 is purcell56a
The bibliography is asked which entry carries this DOI in its url or doi field, and that entry's key takes over from there: this is showme purcell56a, whatever follows includedâthe pdf is opened, or fetched back from ~/Downloads, or the entry is displayed with the sci, huh? offer. The match is case-insensitive, as DOIs are, and must fill the whole field, so that 10.1038/nphys327 does not pass for 10.1038/nphys3272. A DOI that no entry carries is an error, which quotes the doi2bib line that would add it.
A citation pasted from the wiki does as well, since v°3.3: showme {{bromberg09a}} is showme bromberg09a, and so are {{cite|bromberg09a}}, {{onlinecite|âŠ}}, {{citer|âŠ}}, {{citet|âŠ}} and {{doi|âŠ}}, which goes on to the DOI lookup above. The braces go, the template name goes, and so does a period or comma left stuck to the closing braces. A citation holding several keys, {{cite|bromberg09a,he08b}} or {{cite|bromberg09a|he08b}}, shows them one after the other, with the same options:
$ showme '{{cite|bromberg09a,he08b}}'
showme: 2 keys in '{{cite|bromberg09a,he08b}}', one after the other: bromberg09a he08b
Quote anything with a pipe in it: to the shell, {{cite|bromberg09a}} unquoted is a pipeline, and the script says so when it is handed the {{cite that is left of it. Bare {{bromberg09a}} needs no quotes.
A reference as a paper prints it does as well, since v°3.4, so that a reference list leads straight to the pdf:
$ showme 'Nano Letters 22, 2023â2029 (2022).'
showme: Nano Letters 22, 2023â2029 (2022) is chen22a
The volume and first page find the candidates in the bibliography, and the journal must then be the entry's own, under any of its spellings: the @string of its key, a name that iso4 sends to it, or the key itself (PRL 98, 117402 (2007)). The names are compared word by word the way j2bib compares them, so that Nano Letters is Nano Lett. and Proc. R. Soc. A 242 is a Proc. Roy. Soc filed with volume A 242; a match that needed a contraction (Natl. for National) is taken, but said to be loose. The ACS order, Nano Lett. 2022, 22, 2023â2029, is understood too, and so is a whole reference with its authors and title in front, an issue number, an article number (eaau6969, L041101, 241306(R)) or no year at all. When two entries share journal, volume and page, the year decides, and if it cannot they are listed; when the bibliography has the paper under another year, it says so. A reference that no entry matches is an error, which lists the entries with the same volume and first page in another journal, if there are any. Quote the reference: to the shell, (2022) is a subshell. Without the year, the words are put back together and showme Nano Lett. 22, 2023 works as it stands.
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. Since v°4.0, this is done only for a key the bibliography has: a report-0Ksfub.pdf lying in ~/Downloads is no paper to be filed away in transit, but a file (see Any file).
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]
showme: agarwal90b.pdf is in the clipboard, for the save box
The last two lines appear when the entry carries a doi (fished out of the url or doi field): the question, and what a bare Enter (or anything but n) does about itâopen chrome on the paper at the alternative supplier, https://sci-hub.ru/10.1103/PhysRevA.42.686 in this case, and put agarwal90b.pdf in the clipboard, which is the name the download should be saved under, so that the save box is a paste away and the next showme agarwal90b finds the file in ~/Downloads and files it by itself. The key is spelled as the bibliography has it, whatever the case it was typed in.
Since v°4.0, what is not a paper is a file, and showme finds it by its name alone, wherever it is, and opens it with what shows its kind: gwenview for a picture, okular for a pdf, emacs for text (a script, a .bib, a .tex, some json), dolphin for a folder, and whatever the desktop opens it with for the rest (a Mathematica notebook, a video, a web page, a Word file). --with still has the last word.
showme last-report-0Ksfub.pdf
The paper has priority: a name is taken for a file only when no pdf is filed under it and the bibliography has no such keyâor, for words with spaces in them, when they are no reference either, so that showme 'my notes.txt' works too. A name with an extension must then be the whole name of the paper's file: showme chen22a.pdf is the paper, but report.pdf is not taken for some annual-report.pdf in transit.
The name is looked up in the locate database: the file's exact name, a glob ('report-*.pdf', quoted), or the end of a path (kgon/report.pdf). A path that exists, ./fig.png, is opened as it is, and a file of that name in the current folder is found even if it was made today. Without an extension, showme report-0Ksfub takes any report-0Ksfub.*, but not the .aux, .log and other leftovers of LaTeX, unless that is all there is. A name the database does not knowâthe file is newer than last night's rebuildâhas the database rebuilt with sudo updatedb, as my lls does, and is looked up once more.
Several files of that name are versions: the newest is opened, and the others are listed, newest first, with how to open another one:
$ showme MZI.pdf
showme: 4 files are called MZI.pdf, the newest first; another one: showme MZI.pdf N
â 1 2022-03-29 22:16 25K ~/publi/books/lectures-of-physics/10--electrodynamics/21-22/figs/12/MZI.pdf
2 2022-03-29 22:16 25K ~/publi/books/lectures-of-physics/10--electrodynamics/22-23/figs/12/MZI.pdf
3 2022-03-29 22:16 25K ~/publi/books/lectures-of-physics/10--electrodynamics/23-24/figs/12/MZI.pdf
4 2022-03-29 22:16 25K ~/publi/books/lectures-of-physics/10--electrodynamics/24-25/figs/12/MZI.pdf
and then
showme MZI.pdf 3
opens the third. Links to the same file count onceâthe many sci.bib of my projects are links to ~/bib/sci.bib, which is the one listedâand copies in hidden folders (the Trash, .git, .ocr-backup) come after the others. A long list shows its first fifteen; -p prints all the paths, in the same order, so that showme -p report.pdf | cat -n numbers them all (with a number, -p prints that one only). The number works for a paper too, among its pdfs as -p lists them: showme alhilfy85a 2 opens the copy in transit.
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
# **=-=::.......:.@ 3.1 Fri Sep 11 2026
# *=:-=.:::::::: @ 3.2 Sat Sep 12 2026
# %@.::.::::::.+# 3.3 Sun Sep 13 2026
# -@@+=:.....+@ 3.4 Tue Oct 6 2026
# 4.0 Thu Oct 15 2026
# 4.1 Thu Oct 15 2026
# 4.2 Thu Oct 15 2026
# laussy.org/wiki/showme
# F.P. Laussy
# Default program
PROGRAM="okular"
# Parse options with getopt
PARSED=$(getopt -o w:2pvh -l with:,two,path,version,help -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
# Whether the program was chosen with -w: a file's kind chooses it otherwise
WITH=false
# The options as given, to hand them on when one call is several keys
OPTS=()
# Process options
while true; do
case "$1" in
-w|--with)
PROGRAM="$2"
WITH=true
OPTS+=(-w "$2")
shift 2
;;
-2|--two)
TWO_FILES=true
OPTS+=(-2)
shift
;;
-p|--path)
PATH_ONLY=true
OPTS+=(-p)
shift
;;
-h|--help)
cat <<'HELP'
Usage: showme [-w PROG] [-2] [-p] WHAT [N]
Show me what you got: the paper, or else the file, WHAT.
A paper first, as one has it at hand:
chen22a its key in sci.bib, arXiv.bib, Books.bib
10.1021/acs.nanolett.1c04800 its DOI, also as doi:... or https://doi.org/...
'{{cite|chen22a}}' a wiki citation, as on the page
'Nano Lett. 22, 2023 (2022)' a reference, as printed
With no pdf filed, its BibTeX entry, and sci-hub offered.
Else a file, found by its name alone wherever it is (sudo updatedb, as
lls, when the locate database does not know it yet):
last-report-0Ksfub.pdf that file name
report-0Ksfub no extension: report-0Ksfub.pdf, .tex, ...
'report-*.pdf' kgon/report.pdf a glob; the end of a path
Several of that name: the newest opens and the others are listed, numbered;
showme WHAT N opens the N-th
Pictures open in gwenview, pdfs in okular, text in emacs, the rest in what
the desktop opens them with.
Options:
-w, --with PROG open with PROG
-2, --two a second window too, on the next desktop
-p, --path say where, open nothing
-v, --version the cromulon, and the versions
-h, --help this
Quote what has a | ( or space in it. With no WHAT, the cromulon asks.
laussy.org/wiki/showme
HELP
exit 0
;;
-v|--version)
# Show me what you got: the cromulon head is its own version banner
sed -n '/^# #@=/,/^# *F\.P\. Laussy/p' "$0" | cut -c2-
exit 0
;;
--)
shift
break
;;
*)
echo "Error: Invalid option" >&2
exit 1
;;
esac
done
# A number after the name picks one of several files of that name, numbered
# as they are listed: showme last-report-0Ksfub.pdf 3 opens the third. Two
# words only: a reference ends on a number too, but has three words at least
PICK=
if [ $# -eq 2 ] && [[ "$2" =~ ^[0-9]+$ ]]; then
PICK=$((10#$2))
set -- "$1"
fi
# A reference typed without quotes arrives in pieces: put it back together
[ $# -gt 1 ] && set -- "$*"
# Show me what you got: called with nothing to show, the cromulon says it.
# Its head is the banner's, the one -v prints, with the mouth redrawn for
# each sound, and the words of the logo come as they are said.
cromulon() {
local -a art holes
local -A logo=()
local r s m h t out dur n prev=0 eol=$'\n' cw cy cg
mapfile -t art < <(sed -n '/^# #@=/,/^# *F\.P\. Laussy/p' "$0" | cut -c2-)
# A mouth: its upper lip (13 columns from column 11 of row 11), the rows
# it is open by, and its lower lip (11 columns from column 11, between the
# cheeks of row 12). The jaw drops as many rows as the mouth opens.
local -A lip=([ee]='@%*=-----=*%@' [ah]='@%*=-----=*%@' [oh]='@++=*%@%*=++@' [oo]='@++==*@*==++@')
local -A hole=([ee]='@.:.:.:.:.@' [ah]='@.:.:.:.:.@;% %; # # '
[oh]=' @* *@ ; @ @ ' [oo]=' @ @ ')
local -A low=([ee]='%=+=====+=%' [ah]=' .%=+=+=%. ' [oh]=' .%=+=%. ' [oo]=' .%+%. ')
# The logo right of the head, rows 2-10, as it stands once s words are
# said: Show, Me, what, you, got
t=${art[8]%%what*}; cw=$((${#t} - 26))
t=${art[9]%%you*}; cy=$((${#t} - 26))
t=${art[9]%%got*}; cg=$((${#t} - 26))
for ((s = 0; s <= 5; s++)); do
for ((r = 2; r <= 10; r++)); do
t=${art[r]:26}
if [ $r -le 6 ]; then
[ $s -ge 1 ] || t=
else
local me=${t:0:cw} rest=${t:cw}
[ $s -ge 2 ] || me=${me//?/ }
if [ $r -eq 8 ]; then
[ $s -ge 3 ] || rest=
elif [ $r -eq 9 ]; then
local you=${t:cy:cg-cy} got=${t:cg}
[ $s -ge 4 ] || you=${you//?/ }
[ $s -ge 5 ] || got=
rest=$you$got
fi
t=$me$rest
fi
logo[$s,$r]=$t
done
done
# The head saying a sound, with s words of the logo out
draw() {
out=
for ((r = 0; r <= 10; r++)); do out+="${art[r]:0:26}${logo[$s,$r]}$eol"; done
if [ "$1" = closed ]; then
for ((r = 11; r <= 17; r++)); do out+="${art[r]:0:26}$eol"; done
n=18
else
out+="${art[11]:0:11}${lip[$1]}${art[11]:24:2}$eol"
IFS=';' read -ra holes <<< "${hole[$1]}"
for h in "${holes[@]}"; do out+="${art[12]:0:11}$h${art[12]:22:2}$eol"; done
out+="${art[12]:0:11}${low[$1]}${art[12]:22:2}$eol"
for ((r = 13; r <= 17; r++)); do out+="${art[r]:0:26}$eol"; done
n=$((18 + ${#holes[@]}))
fi
[ $prev -gt 0 ] && printf '\e[%dA\r' $prev
printf '%s' "$out"
[ $prev -gt 0 ] && printf '\e[J'
prev=$n
}
# Not on a terminal, or too narrow a one, to move in: the head, said
if ! [ -t 1 ] || [ "$(tput cols 2>/dev/null || echo 80)" -lt 56 ]; then
s=5; prev=0; draw closed
return
fi
eol=$'\e[K\n'
printf '\e[?25l'
trap 'printf "\e[?25h"' EXIT
trap 'exit 130' INT TERM
s=0; draw closed; sleep 0.5
# Each word: how many are out once it is said, then its sounds and how
# long each lasts. SHOW (sh-ow) ME (m-ee) WHAT (wh-a-t) YOU (y-ou) GOT (g-o-t)
for t in '1 oo .07 oh .26 oo .08 closed .07' '2 closed .09 ee .24 closed .06' \
'3 oo .07 ah .24 ee .07 closed .06' '4 ee .07 oo .26 closed .06' \
'5 ah .09 oh .30 closed .1'; do
set -- $t
s=$1; shift
while [ $# -gt 0 ]; do
draw "$1"; sleep "$2"
shift 2
done
done
}
# Check if a search term is provided: with none, the cromulon asks for one
if [ -z "$1" ]; then
if [ "$PATH_ONLY" = true ]; then
echo "Error: No search term provided" >&2
exit 1
fi
cromulon
exit 0
fi
# Where papers live, where they land when the download goes wrong, and where
# they are on record even without a pdf
SCIDIR="$HOME/bib/sci"
TRANSITDIR="$HOME/bib/transit"
DOWNLOADS="$HOME/Downloads"
BIBFILES=("$HOME/bib/sci.bib" "$HOME/bib/arXiv.bib" "$HOME/bib/Books.bib")
# The bibliography's entry for a key, in ENTRY, and the file it is in, in
# BIBFILE. The key is compared as a string, not a pattern: a file name with
# a [ or a ( in it may come this way.
find_entry() {
ENTRY=
for BIBFILE in "${BIBFILES[@]}"; do
[ -r "$BIBFILE" ] || continue
ENTRY=$(awk -v key="$1" '
BEGIN { key = tolower(key) }
!found && /^@[A-Za-z]+[{(]/ {
k = $0
sub(/^@[A-Za-z]+[{(][ \t]*/, "", k); sub(/[ \t]*,.*$/, "", k)
if (tolower(k) == key) found = 1
}
found { print }
found && /^[})]/ { exit }
' "$BIBFILE")
[ -n "$ENTRY" ] && return 0
done
return 1
}
# Not a paper, then: a file, any file, found by its name alone wherever the
# locate database has it, and opened with what shows its kind: gwenview for
# a picture, okular for a pdf, emacs for text, the desktop's choice for the
# rest. A name the database does not know (the file is newer than last
# night's rebuild) has the database rebuilt, as lls does, and is looked up
# again. Several files of that name are versions: the newest is opened, the
# others are listed, numbered, and showme NAME N opens the N-th.
# The paths the database has for a name: its file name exactly; or a glob
# (report-*.pdf), which plocate matches whole; or, with a / in it, a path
# ending so (kgon/report.pdf)
locate_name() {
local name="${1#./}"
if [[ "$name" == */* ]]; then
locate -0 -- "*/${name#/}"
elif [[ "$name" == *[*?[]* ]]; then
locate -0 -b -- "$name"
else
# plocate's \name is no exact match, as mlocate's is: rule.png found
# Gema-enslaving-rule.png. Take what has it, keep what is it.
local f
locate -0 -b -- "$name" | while IFS= read -r -d '' f; do
[ "${f##*/}" = "$name" ] && printf '%s\0' "$f"
done
fi
}
# The files that exist (the database may be a day old), each once, newest
# first: in FOUND, with MTIME and SIZE alongside. Once means once, however
# many links lead to it: the 69 sci.bib of the projects are ~/bib/sci.bib,
# and that path is the one kept.
sort_files() {
local -A seen=() pos=()
local m s id f
FOUND=(); MTIME=(); SIZE=()
[ $# -eq 0 ] && return
while IFS=$'\t' read -r -d '' m s id f; do
[ -n "${seen[$f]}" ] && continue
seen[$f]=1
if [ -n "${pos[$id]}" ]; then
[ -L "${FOUND[pos[$id]]}" ] && [ ! -L "$f" ] && FOUND[pos[$id]]=$f
continue
fi
pos[$id]=${#FOUND[@]}
FOUND+=("$f"); MTIME+=("$m"); SIZE+=("$s")
done < <(printf '%s\0' "$@" | xargs -0 stat -L --printf '%Y\t%s\t%d:%i\t%n\0' -- 2>/dev/null \
| sort -z -t$'\t' -k1,1nr -k4)
}
# What shows a file: gwenview a picture, okular a pdf, emacs text, the
# desktop's own choice everything else (a notebook, a video, a web page)
viewer_for() {
local f="$1" mime def
local editors='^(vim|nvim|gvim|emacs|emacsclient|kate|kwrite|org\.kde\.kate|org\.kde\.kwrite|gedit|org\.gnome\.gedit|nano|code)\.desktop$'
if [ -d "$f" ]; then echo dolphin; return; fi
if [ ! -s "$f" ]; then echo emacs; return; fi
mime=$(xdg-mime query filetype "$f" 2>/dev/null)
[ -z "$mime" ] && mime=$(file -b -L --mime-type -- "$f")
case "$mime" in
application/pdf|application/postscript|application/x-dvi|image/vnd.djvu*|application/epub+zip)
echo okular ;;
image/*)
echo gwenview ;;
text/html|application/xhtml+xml)
echo xdg-open ;;
text/*)
echo emacs ;;
*)
# Text all the same, a script or some json, unless a program of its
# own is set for it (a Mathematica notebook is text too)
def=$(xdg-mime query default "$mime" 2>/dev/null)
if [ "$(file -b -L --mime-encoding -- "$f")" != binary ] && [[ -z "$def" || "$def" =~ $editors ]]; then
echo emacs
else
echo xdg-open
fi ;;
esac
}
# One line of the list: number, date, size, where
list_line() {
local i=$1 mark=" " on="" off="" size
if [ "$i" -eq "${PICK:-1}" ]; then
mark=" â "
[ -t 2 ] && { on=$'\e[1m'; off=$'\e[0m'; }
fi
if [ -d "${FILES[i-1]}" ]; then size=dir; else size=$(numfmt --to=iec "${SIZE[i-1]}"); fi
printf '%s%s%3d %(%Y-%m-%d %H:%M)T %6s %s%s\n' "$mark" "$on" "$i" "${MTIME[i-1]}" \
"$size" "${FILES[i-1]/#$HOME/\~}" "$off" >&2
}
# Find the file called $1 and make it FILES, with PROGRAM to show it.
# False when there is no such file, even with the database rebuilt.
show_file() {
local name="$1" pass i n shown=15 q
local -a raw=() order=() byproducts=()
for pass in 1 2; do
raw=()
if [[ "$name" == */* ]] && [ -e "$name" ]; then
# a path, ./notes.txt or ~/kgon/fig.png: that very file
raw=("$(realpath -s -- "$name")")
else
mapfile -d '' raw < <(locate_name "$name")
# made here today, too new for the database
[[ "$name" != */* ]] && [ -e "$name" ] && raw+=("$(pwd -P)/$name")
# no extension given: report-0Ksfub is report-0Ksfub.pdf or .tex, but
# not the .aux or .log LaTeX leaves beside them unless that is all
if [ ${#raw[@]} -eq 0 ] && [[ "${name##*/}" != ?*.* ]]; then
mapfile -d '' raw < <(locate_name "$name.*")
byproducts=()
for i in "${!raw[@]}"; do
if [[ "${raw[i]}" =~ \.(aux|log|out|toc|lof|lot|bbl|blg|bcf|run\.xml|fls|fdb_latexmk|synctex\.gz|synctex|xdv|nav|snm|vrb|idx|ilg|ind)$ ]]; then
byproducts+=("$i")
fi
done
if [ ${#byproducts[@]} -lt ${#raw[@]} ]; then
for i in "${byproducts[@]}"; do unset 'raw[i]'; done
fi
fi
fi
sort_files "${raw[@]}"
[ ${#FOUND[@]} -gt 0 ] && break
[ $pass -eq 2 ] && return 1
# Not in the database: rebuild it (lls), then look again
echo "showme: nothing called '$name' in the locate database, updating it (lls)..." >&2
if [ -t 0 ]; then
sudo updatedb
else
sudo -n updatedb 2>/dev/null
fi || { echo "showme: the update failed (sudo), lls and try again" >&2; return 1; }
done
# Copies in hidden folders (.git, .cache, the Trash, .ocr-backup) go after
# the others, the newest of those that are in plain sight opened first
order=()
for i in "${!FOUND[@]}"; do [[ "${FOUND[i]%/*}/" != */.* ]] && order+=("$i"); done
local nvis=${#order[@]}
for i in "${!FOUND[@]}"; do [[ "${FOUND[i]%/*}/" == */.* ]] && order+=("$i"); done
FILES=(); local -a mt=() sz=()
for i in "${order[@]}"; do FILES+=("${FOUND[i]}"); mt+=("${MTIME[i]}"); sz+=("${SIZE[i]}"); done
MTIME=("${mt[@]}"); SIZE=("${sz[@]}")
n=${#FILES[@]}
# The name as it would be typed again
q="$name"
[[ "$q" =~ [^A-Za-z0-9._/+~-] ]] && q="'$name'"
if [ -n "$PICK" ] && { [ "$PICK" -lt 1 ] || [ "$PICK" -gt $n ]; }; then
if [ $n -eq 1 ]; then
echo "Error: one file only is called $q, no number $PICK: ${FILES[0]/#$HOME/\~}" >&2
else
echo "Error: $n files are called $q, no number $PICK:" >&2
local p=$PICK; PICK=0
for ((i = 1; i <= n; i++)); do list_line $i; done
PICK=$p
fi
exit 1
fi
# Several: say so, and how to open another one
if [ $n -gt 1 ] && [ "$PATH_ONLY" != true ]; then
echo "showme: $n files are called $q, the newest first; another one: showme $q N" >&2
for ((i = 1; i <= n && i <= shown; i++)); do
[ $i -eq $((nvis + 1)) ] && [ $nvis -gt 0 ] && echo " in hidden folders:" >&2
list_line $i
done
if [ $n -gt $shown ]; then
[ "${PICK:-1}" -gt $shown ] && { echo " ..." >&2; list_line "$PICK"; }
echo " and $((n - shown)) more: showme -p $q | cat -n lists them all" >&2
fi
fi
[ "$WITH" = true ] || PROGRAM=$(viewer_for "${FILES[${PICK:-1} - 1]}")
return 0
}
# Show FILES with PROGRAM: the one picked, the first one by default (or with
# -p, say where they are)
open_files() {
local f="${FILES[${PICK:-1} - 1]}"
if [ "$PATH_ONLY" = true ]; then
# Just print the location(s) of the matching file(s), don't open anything
if [ -n "$PICK" ]; then printf '%s\n' "$f"; else printf '%s\n' "${FILES[@]}"; fi
elif [ "$TWO_FILES" = true ]; then
# Open the first instance on the current desktop
$PROGRAM "$f" &>/dev/null &
# Open the same file again in a second instance
$PROGRAM "$f" &>/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 "$f" &>/dev/null &
fi
}
# A wiki citation does as well as a key, pasted as it stands on the page:
# showme {{bromberg09a}} is showme bromberg09a, and so are {{cite|bromberg09a}},
# {{onlinecite|âŠ}}, {{citer|âŠ}}, {{citet|âŠ}} and {{doi|âŠ}}, which goes on to the
# DOI lookup below. The braces go, the template name goes, a period or comma
# stuck to the closing braces goes. Several keys in one citation, {{cite|a,b}}
# or {{cite|a|b}}, are shown one after the other, the options handed on.
# Quote anything with a pipe in it: to the shell, {{cite|a}} is a pipeline.
if [[ "$1" == *"{{"* ]]; then
if [[ "$1" != *"}}"* ]]; then
echo "Error: '$1' has no closing braces: quote the citation, the shell took its | for a pipe" >&2
exit 1
fi
INNER=$(printf '%s' "$1" | sed -E 's/^[[:space:]]*\{\{//; s/\}\}[[:space:]]*[.,;:]*[[:space:]]*$//')
case "$INNER" in
[Cc]ite\|*|[Oo]nlinecite\|*|[Cc]iter\|*|[Cc]itet\|*|[Cc]ite/mark\|*|[Dd]oi\|*) INNER="${INNER#*|}" ;;
esac
KEYS=()
while IFS= read -r k; do
k="${k#"${k%%[![:space:]]*}"}"; k="${k%"${k##*[![:space:]]}"}"
[ -n "$k" ] && KEYS+=("$k")
done < <(printf '%s\n' "$INNER" | tr '|,' '\n\n')
if [ ${#KEYS[@]} -eq 0 ]; then
echo "Error: no key in '$1'" >&2
exit 1
fi
if [ ${#KEYS[@]} -gt 1 ]; then
echo "showme: ${#KEYS[@]} keys in '$1', one after the other: ${KEYS[*]}" >&2
STATUS=0
for k in "${KEYS[@]}"; do
"$0" "${OPTS[@]}" "$k" || STATUS=$?
done
exit $STATUS
fi
set -- "${KEYS[0]}" "${@:2}"
fi
# A DOI does as well as a key: showme 10.1038/1781449a0 is showme purcell56a.
# The bibliography knows which entry carries it (in its url or doi field) and
# that entry's key takes over from here, since the pdf is filed under the key.
# The forms a DOI is pasted in are accepted too: doi:âŠ, a doi.org address, the
# reference list's trailing period.
LOOKUP="$1"
LOOKUP=$(printf '%s' "$LOOKUP" | sed -E 's#^(doi:|https?://(dx\.)?doi\.org/)##I; s/[.,;]+$//')
if [[ "$LOOKUP" =~ ^10\.[0-9]{4,9}/. ]]; then
KEY=
for BIBFILE in "${BIBFILES[@]}"; do
[ -r "$BIBFILE" ] || continue
KEY=$(awk -v doi="$LOOKUP" '
BEGIN { doi = tolower(doi); n = length(doi) }
# the key of the entry we are in
/^[ \t]*@[A-Za-z]+[{(]/ {
key = $0
sub(/^[ \t]*@[A-Za-z]+[{(][ \t]*/, "", key); sub(/[ \t]*,.*$/, "", key)
next
}
# DOIs are case-insensitive, and this one must fill its field up to the
# closing brace, quote or comma: 10.1038/nphys327 is not 10.1038/nphys3272
key != "" && tolower($0) ~ /^[ \t]*(doi|url)[ \t]*=/ {
line = tolower($0)
i = index(line, doi)
if (i && substr(line, i + n, 1) ~ /^[}",; \t]?$/) { print key; exit }
}
' "$BIBFILE")
[ -n "$KEY" ] && break
done
if [ -z "$KEY" ]; then
echo "Error: no entry in the bibliography carries the DOI $LOOKUP (doi2bib -w $LOOKUP would add one)" >&2
exit 1
fi
echo "showme: $LOOKUP is $KEY" >&2
set -- "$KEY" "${@:2}"
fi
# A reference as a paper prints it does as well: showme 'Nano Letters 22,
# 2023â2029 (2022).' is showme chen22a. So does the ACS order, 'Nano Lett.
# 2022, 22, 2023â2029', and a whole reference with its authors and title in
# front. Volume and first page find the entries, and the journal must be the
# entry's own under one of its spellings: the @string, a name iso4 sends to
# it, the key itself (PRL) -- words compared the way j2bib compares them,
# "Lett." for "Letters". Keys and DOIs never hold a space; a reference always
# does. Quote it: to the shell, (2022) is a subshell. What is no reference,
# or none the bibliography has, may still be a file: my notes.txt.
if [[ "$1" =~ [[:space:]] ]]; then
REFERR=$(mktemp)
KEY=$(perl - "$1" "$HOME/bib/doi2bib/iso4" "${BIBFILES[@]}" <<'PERL' 2>"$REFERR"
use strict;
use warnings;
use Encode qw(decode_utf8);
use Unicode::Normalize qw(NFKD);
binmode(STDERR, ':utf8');
my ($given, $iso4, @bibs) = @ARGV;
$given = decode_utf8($given);
(my $shown = $given) =~ s/^\s+|[\s.,;]+$//g;
# j2bib's small words, dropped when two journal names are compared
my %small = map { $_ => 1 } qw(of the and in on for an at to
de du der die das des la le les et fur und di del della);
# Cut the reference into journal, volume, first page and year
my $r = $given;
$r =~ s/'{2,}|\*//g; # wiki and markdown emphasis
$r =~ s/[\x{2010}-\x{2015}\x{2212}]/-/g; # en and em dashes, minus signs
$r =~ s/\s+/ /g;
$r =~ s/^ | ?[.,;]+ ?$//g;
my $vl = qr/[A-Za-z]?\d+[A-Za-z]?|[IVXLC]+/; # 22, 35A, T123, XVII
my $pg = qr/[A-Za-z0-9]*\d[A-Za-z0-9]*(?: ?\([A-Za-z]\))?/; # 2023, L041101, eaau6969, 241306(R)
my ($pre, $vol, $first, $year);
if ($r =~ /^(.*?)[ ,]+(\d{4}) ?, ?($vl)(?: ?\([^()]*\))? ?, ?($pg)(?: ?-+ ?$pg)?$/) {
($pre, $year, $vol, $first) = ($1, $2, $3, $4); # Nano Lett. 2022, 22, 2023-2029
} elsif ($r =~ /^(.*?)[ ,]+($vl)(?: ?\([^()]*\))? ?[,:] ?($pg)(?: ?-+ ?$pg)?(?: ?\((?:[^()]* )?(\d{4})\))?$/) {
($pre, $vol, $first, $year) = ($1, $2, $3, $4); # Nano Lett. 22, 2023-2029 (2022)
} else {
print STDERR "Error: '$shown' is no key, DOI or reference showme can read "
. "(Journal Volume, Page (Year))\n";
exit 1;
}
# Volumes and pages compared on what matters: 22 = {22}, 35A = 35,
# {A 242} = 242, L041101 = 041101, 241306(R) = 241306, 2023--2029 starts at
# 2023
sub volume_of {
my $v = shift // '';
return lc $1 if $v =~ /^[{"\s]*([IVXLC]+)[}"\s]*$/;
return $v =~ /(\d+)/ ? $1 : '';
}
sub page_of {
my $p = lc(shift // '');
$p =~ s/^[{"\s]+//;
$p =~ s/\s*[-\x{2013}].*//s;
$p =~ s/[^a-z0-9]//g;
$p =~ s/^[a-z]+|[a-z]+$//g;
return $p;
}
# j2bib's words_of and word_match: a name cut into the words that carry it,
# and two words for the same one -- equal, a truncation (Lett. for Letters),
# a contraction keeping the letters in order (Natl. for National). A lone
# letter at a name's end is a series letter, equal only to itself: Phys. Rev.
# A is not Phys. Rev. Applied. Before that it may be a word: J. for Journal,
# R. for Roy. (j2bib allows the front only, it has no volume to go by.)
sub words_of {
my $s = NFKD(shift);
$s =~ s/\p{Mn}//g;
$s =~ s/&/ /g;
$s =~ s/\\[A-Za-z]+//g;
$s =~ s/[\\'"`^~]//g;
$s = lc $s;
$s =~ s/[^a-z0-9]+/ /g;
return grep { $_ ne '' && !$small{$_} } split ' ', $s;
}
sub word_match {
my ($a, $b, $first) = @_;
return 1 if $a eq $b;
my ($short, $long) = length $a <= length $b ? ($a, $b) : ($b, $a);
return 0 if length($short) == 1 && !$first;
return 1 if index($long, $short) == 0;
return 0 unless length($short) >= 3 && substr($short, 0, 1) eq substr($long, 0, 1);
my $i = 0;
for my $c (split //, $short) {
$i = index($long, $c, $i);
return 0 if $i < 0;
$i++;
}
return 0.5;
}
# Do these words, what stands before the volume, end on this spelling of the
# journal? Their last words are compared, so authors and title in front do no
# harm. 1 when every word is equal or truncated, 0.5 when one needed a
# contraction ("Res." would do for "Reviews": only looks alike), 0 when not.
my @pre = words_of($pre);
sub ends_on {
my ($w, $spelling) = @_;
my @s = words_of($spelling);
return 0 unless @s && @$w;
if (@$w >= @s) {
my @tail = @$w[$#$w - $#s .. $#$w];
my $score = 1;
for my $i (0 .. $#s) {
my $m = word_match($tail[$i], $s[$i], $i < $#s);
$score = $m if $m < $score;
last unless $m;
}
return $score if $score;
}
# an acronym: PRL for Phys. Rev. Lett.
return (@s >= 2 && $w->[-1] =~ /^[a-z]{2,}$/
&& $w->[-1] eq join('', map { substr($_, 0, 1) } @s)) ? 1 : 0;
}
# The spellings each journal key goes by: its @string, and every name iso4
# sends straight to it
my (%string, %spell);
if (open my $fh, '<:encoding(UTF-8)', $bibs[0]) {
while (<$fh>) {
$string{$1} = $2 if /^\@string\s*\{\s*([^\s=]+)\s*=\s*"(.*)"\s*\}/i;
}
}
if (open my $fh, '<:encoding(UTF-8)', $iso4) {
while (<$fh>) {
next unless m{^s/(.*)/([^/]*)/\s*$};
my ($name, $k) = ($1, $2);
$name =~ s/\\//g;
push @{ $spell{$k} }, $name if exists $string{$k};
}
}
# Every entry with a volume and pages
my @entries;
for my $bib (@bibs) {
open my $fh, '<:encoding(UTF-8)', $bib or next;
(my $file = $bib) =~ s{.*/}{};
my $e;
while (<$fh>) {
if (/^\s*\@[A-Za-z]+\s*[{(]\s*([^\s,=]+)\s*,/) {
$e = { key => $1, file => $file };
push @entries, $e;
} elsif ($e && /^\s*(journal|volume|pages|year)\s*=\s*(.*?)\s*,?\s*$/i) {
$e->{lc $1} //= $2;
}
}
}
# A journal is a key (nl) or written out, braced: {Opt. Express}. Only the
# outer braces go, Preu{\ss}ischen keeps its own.
sub journal_of {
my $j = shift // '';
return $j =~ /^[{"]/ ? $j =~ s/^[{"]|[}"]$//gr : $string{$j} // $j;
}
sub spellings_of {
my $j = shift // '';
return ($j =~ /^[{"]/) ? (journal_of($j)) : ($string{$j} // (), @{ $spell{$j} || [] });
}
# The entry's journal, under any of its spellings or its key (PRL). A series
# letter the bibliography keeps in the volume, {A 242} in a Proc. Roy. Soc,
# stands before the volume in the reference: Proc. R. Soc. A 242.
sub journal_ok {
my $e = shift;
my $j = $e->{journal} // '';
my @w = (\@pre);
my ($letter) = $e->{volume} =~ /^[{"\s]*([A-Za-z]+)\s*\d/;
push @w, [ @pre[0 .. $#pre - 1] ] if $letter && @pre > 1 && lc $letter eq $pre[-1];
my $best = 0;
for my $w (@w) {
return 1 if $j !~ /^[{"]/ && @$w && $w->[-1] eq lc $j;
for (spellings_of($j)) {
my $s = ends_on($w, $_);
$best = $s if $s > $best;
}
}
return $best;
}
sub cited {
my $e = shift;
my $y = ($e->{year} // '') =~ s/[{}"]//gr;
return "$e->{key} = " . journal_of($e->{journal}) . " " . ($e->{volume} =~ s/[{}"]//gr)
. ", " . ($e->{pages} =~ s/[{}"]//gr) . ($y ne '' ? " ($y)" : "")
. ($e->{file} eq 'sci.bib' ? "" : " in $e->{file}");
}
my @same = grep { defined $_->{volume} && defined $_->{pages}
&& volume_of($_->{volume}) eq volume_of($vol)
&& page_of($_->{pages}) eq page_of($first) } @entries;
$_->{score} = journal_ok($_) for @same;
my @hits = grep { $_->{score} } @same;
# The same paper entered twice, or another with its volume and page by
# mistake: the year may still tell which
if (@hits > 1 && defined $year) {
my @y = grep { ($_->{year} // '') =~ s/[{}"\s]//gr eq $year } @hits;
@hits = @y if @y == 1;
}
if (@hits == 1) {
my $e = $hits[0];
print STDERR "showme: $shown is $e->{key}\n";
print STDERR "showme: matched loosely, to ", cited($e) =~ s/^\S+ = //r, "\n"
if $e->{score} < 1;
my $y = ($e->{year} // '') =~ s/[{}"]//gr;
print STDERR "showme: but the bibliography has it in $y\n"
if defined $year && $y ne '' && $y ne $year;
print "$e->{key}\n";
exit 0;
}
if (@hits > 1) {
print STDERR "Error: several entries are $shown, say which:\n";
print STDERR " ", cited($_), "\n" for @hits;
exit 3;
}
print STDERR "Error: no entry in the bibliography is $shown\n";
if (@same) {
print STDERR " the same volume and first page, but another journal:\n";
print STDERR " ", cited($_), "\n" for @same;
}
exit 2;
PERL
)
STATUS=$?
REFMSG=$(cat "$REFERR"); rm -f "$REFERR"
# 0: the paper; 2: a reference, but to no paper the bibliography has; 3:
# several papers, say which; 1: no reference, perhaps a file
[ -n "$REFMSG" ] && [ $STATUS -ne 1 ] && printf '%s\n' "$REFMSG" >&2
[ $STATUS -ge 2 ] && exit 1
if [ $STATUS -eq 1 ]; then
show_file "$1" && { open_files; exit 0; }
printf '%s\n' "$REFMSG" >&2
echo " and no file is called that either" >&2
exit 1
fi
set -- "$KEY" "${@:2}"
fi
# Get matching PDF files
FILES=($(locate "$1" | grep -i ".pdf$" | grep -i bib))
# A name with a dot is a file's whole name (keys have none): chen22a.pdf is
# the paper, but report.pdf is no part of annual-report.pdf in transit
if [[ "$1" == *.* ]]; then
KEEP=()
for f in "${FILES[@]}"; do [ "${f##*/}" = "$1" ] && KEEP+=("$f"); done
FILES=("${KEEP[@]}")
fi
# 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
# No pdf filed under that name, and no entry in the bibliography either: no
# paper, then. A file of that name, anywhere?
if [ ${#FILES[@]} -eq 0 ] && ! find_entry "$1"; then
show_file "$1" && { open_files; exit 0; }
echo "Error: '$1' is no paper (no PDF, no BibTeX entry) and no file either" >&2
exit 1
fi
# Still nothing: it may have been downloaded to the wrong place. Fetch it back.
# Only for a key the bibliography has: report-0Ksfub.pdf in Downloads is no
# paper to file away in transit.
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
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
# The download will want the key's name: park it in the clipboard so the
# save box is a paste away. The key as the bibliography spells it, not as
# it was typed (the lookup above is case-insensitive).
KEY=$(printf '%s\n' "$ENTRY" | head -1 | sed -E 's/^@[A-Za-z]+[{(][[:space:]]*//; s/[[:space:]]*,.*$//')
[ -z "$KEY" ] && KEY="$1"
read -r -p "See sci huh? [Y/n] " ANSWER
case "$ANSWER" in
[nN]*) ;;
*)
if printf '%s' "$KEY.pdf" | xclip -selection clipboard &>/dev/null; then
echo "showme: $KEY.pdf is in the clipboard, for the save box"
fi
google-chrome "https://sci-hub.ru/$DOI" &>/dev/null &
;;
esac
fi
exit 0
fi
# A number picks one of several pdfs, as showme -p lists them
if [ -n "$PICK" ] && { [ "$PICK" -lt 1 ] || [ "$PICK" -gt ${#FILES[@]} ]; }; then
echo "Error: no PDF number $PICK for '$1', ${#FILES[@]} only:" >&2
for i in "${!FILES[@]}"; do printf ' %3d %s\n' $((i + 1)) "${FILES[i]}" >&2; done
exit 1
fi
open_files
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