<span class="mw-page-title-main">Showme</span>
Fabrice P. Laussđ•Ș’s â„€hun 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.

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]

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.

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?

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