<span class="mw-page-title-main">Showme</span>
Fabrice P. Laussđ•Ș’s â„€-gaty Web

showme

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.

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.

Looking harder

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.

When there is no pdf

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.

Versions

  1. v°0.1 on 31 Dec (2023)
  2. v°1.5 on 19 August (2025) — --with option
  3. v°2.1 on 17 September (2025) — -2 option
  4. v°2.2 on 21 July (2026) — -p option
  5. v°2.3 on 1 August (2026) — falls back to ~/Downloads
  6. v°2.4 on 7 August (2026) — downloads are filed into ~/bib/transit rather than straight into ~/bib/sci
  7. v°3.0 on 10 August (2026) — -v option; a missing pdf displays its bibTeX entry instead, with an offer to see sci, huh?
  8. v°3.1 on 11 September (2026) — [Y/n]: Enter now says yes to sci, huh? and <key>.pdf goes to the clipboard for the save box
  9. v°3.2 on 12 September (2026) — takes a doi as well as a key
  10. v°3.3 on 13 September (2026) — takes a wiki citation as it stands on the page, {{bromberg09a}} or {{cite|bromberg09a}}; several keys are shown one after the other

Source

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
#         -@@+=:.....+@      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

# 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"
      OPTS+=(-w "$2")
      shift 2
      ;;
    -2|--two)
      TWO_FILES=true
      OPTS+=(-2)
      shift
      ;;
    -p|--path)
      PATH_ONLY=true
      OPTS+=(-p)
      shift
      ;;
    -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

# Check if a search term is provided
if [ -z "$1" ]; then
  echo "Error: No search term provided" >&2
  exit 1
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")

# 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

# 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

  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
    # 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

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