Share your file-opener script - Programming On Unix

Users browsing this thread: 1 Guest(s)
Long time nixers
Most of us have an handy script for opening files and/or links given as arguments. This is, sometimes, one of our most used scripts. Use this thread to share your file-opener or link-opener scripts and steal ideas from others.

Here is mine, I call it xopen.

# xopen: open file with proper command; use file in $MIMEFILE as rules file
# this file in public domain

usage() {
    echo "usage: xopen [-c mimefile] filename" >&2
    exit 1

# get mimetype of a file using the file(1) utility
getfilemime() {
    file -ib "$@" | cut -d';' -f1

# get lowercase extension of a file
getextension() {
    echo "${1##*.}" | tr '[:upper:]' '[:lower:]'

# get mimetype of a url
geturlmime() {
    protocol=$(printf "%s" "$@" | sed 's/^\([a-zA-Z]\+\):.*/\1/')

    case "$protocol" in
        echo text/html
        echo application/x-bittorrent
        echo x-scheme-handler/irc

    unset protocol

# get command to open file or url
getcmd() {
    while read -r line
        line=${line%%#*}                # remove comments
        [ -z "$line" ] && continue      # ignore blank lines

        pattern="$(echo $line | cut -s -d: -f1 | sed -E 's/[     ]*$//; s/\|/ /g')"
        cmd="$(echo $line | cut -s -d: -f2 | sed -E 's/^[     ]*//')"
        cmd="$(eval echo $cmd)"

        # using case for matching patterns to avoid forking
        for i in $pattern
            case $1 in
            $i) echo "$cmd" && return ;;
    done < "$MIMEFILE"

# execute command $1 to open file or url $2
xopen() {
    trap "" 1 15
    echo xopen: opening $search with $1 >&2
    exec $1 "${2}" &

while getopts 'c:' c
    case "$c" in
shift $((OPTIND -1))

[ "$#" -ne 1 ] && usage

# test whether MIMEFILE is not set
[ -z "$MIMEFILE" ] && echo 'xopen: $MIMEFILE is not set' >&2 && exit 1

# get mimetype and file extension
if [ -e "$@" ];
then            # it's a file
    mime=$(getfilemime "$@")
    extension=$(getextension "$@")
else            # it's a url
    mime=$(geturlmime "$@")
[ -z "$extension$mime" ] && echo "xopen: unknown file or url type" >&2 && exit 1

# get command to open file or url
cmd=$(getcmd "$extension")              # try first to open by file extension
[ -z "$cmd" ] && cmd=$(getcmd "$mime")  # then try to open by mimetype
[ -z "$cmd" ] && echo "xopen: cannot find an command to open $@ ($mime)" >&2 && exit 1

# open file or url with $cmd
xopen "$cmd" "$@"

It gets its configuration from a file specified in the environment variable $MIMEFILE (or after the -c option). Each line in this file is composed by a mimetype or a file extension followed by a colon, followed by the command that opens a file with that mimetype or extension. Here is my $MIMEFILE:

# web
text/html:  palemoon

# text
text/*:                     $TERMCMD -e $EDITOR
application/x-empty:        $TERMCMD -e $EDITOR
application/x-shellscript:  $TERMCMD -e $EDITOR
inode/x-empty:              $TERMCMD -e $EDITOR
inode/directory:            $TERMCMD -e fm

# Docs
application/pdf:        zathura
application/postscript: zathura
application/epub*:      zathura
image/*djvu:            zathura

# media
application/octet-stream: mpv
video/*:                  mpv
audio/*:                  mpv

# image
image/*:                  sxiv

# Man pages
0|1|2|3|4|5|6|7: $TERMCMD -e man

Now share yours!
Grey Hair Nixers
Mine is self-contained, though I like your way to use the mailcap entry… I'll look this out later.
I cleaned up mine a bit. The cool stuff about it (I think) is that you can pipe data to it and open it. Not only URIs:

curl -s 'https://random.tld/image.png' | plumb -s

This would cache the file locally, and open it with the according mime entry.
Here is the full script:

# open things from uri


usage() {
    echo "usage: $(basename $0) [-h] [uri]" >&2

peekmime() {
    curl -sSfL "$1" | cut -b-16 | file -ib - | cut -d\; -f1

# return a uri to a local file (download if necessary)
localuri() {
    if [ ! -f "$1" ]; then
        curl -o $tmp -sSfL "$1"
        local=$(cachefile "$tmp" "${mime##*/}")
        rm $tmp
    printf '%s\n' "$local"

# copy file to cache
cachefile() {
    mkdir -p $cachedir
    hash=$(sha256sum "$1"|cut -d' ' -f1)
    cp "$1" $cachedir/${hash}.$2
    printf '%s/%s.%s\n' "$cachedir" "$hash" "$2"

# prompt for a command to open uri
prompt() {
    dmenu_path | dmenu -p "$1" -l 8

clip="$(xsel -o 2>/dev/null)"

while getopts "hs" OPT; do
    case $OPT in
    s) slurp=$(mktemp -p $cachedir) ;;
    h) usage; exit 0 ;;
    *) usage; exit 1 ;;
shift $((OPTIND - 1))

# fallback to clipboard when not uri given

# special arg "-" read uri(s) from stdin
if [ "$uri" = "-" ]; then
    cat | xargs -P$(nproc) -n1 $0

# can read file _content_ from stdin, then process it
if [ -n "$slurp" ]; then
    cat > $slurp

[ -z "$uri" ] && exit 1

if [ -f "$uri" ]; then
    mime=$(file -ib "$uri" | cut -d\; -f1)
    case $proto in
    mailto)     mime='application/x-mail' ;;
    magnet)     mime='application/x-bittorrent' ;;
    *)          mime="$(peekmime $uri)" ;;

if [ -n "$slurp" ]; then
    uri="$(cachefile $slurp ${mime##*/})"
    rm "$slurp"

case "$mime" in
    text/html) exec firefox "$uri" ;;
    video/*)   exec mplayer -cache 512 "$uri" ;;
    image/gif) exec mplayer -loop 0 $(localuri "$uri") ;;
    image/*)   exec meh $(localuri "$uri") ;;
    text/*)    exec st -e less $(localuri "$uri") ;;
    application/x-mail) exec st -e mutt $uri ;;
    application/x-bittorrent) exec st -e rtorrent $uri ;;
    *) exec $(prompt) "$uri" ;;
I rely on the freedesktop utility xdg-open as it already has full integration with mime types and desktop files. I kind of pointed it a bit in this discussion about default programs.

So what I usually do is xdg-open, and if it doesn't call the program I want then I can switch it using mimeopen -a. In the case where my program isn't listed, I create a desktop file in ~/.local/share/applications, then update-desktop-database. Most of the time, this solves my issues, otherwise I can dive into adding my own mimetype in ~/.local/share/mime/application.
Long time nixers
Almost on-topic: In a C++ software of mine (which I really should develop further... some day), I wrote a function to open a text file with the default editor. I would guess that, if one removes the VISUAL/EDITOR checks, it fulfills this thread’s needs as well.

Note that sanitizing the filename needs to be done beforehand. Better don’t make this function publicly callable. :)

inline bool openWithEditor(const char* filename) {
    // Opens <filename> in your default editor.
#ifdef _WIN32
    // Windows version, using the Windows API.
    return reinterpret_cast<int>(ShellExecuteA(NULL, "open", filename, NULL, NULL, SW_SHOW)) > 32;
    // Other system, other ways...
    string editor = "";

    if (getenv("VISUAL") != NULL) {
        editor = getenv("VISUAL");
    else if (getenv("EDITOR") != NULL) {
        editor = getenv("EDITOR");
    else {
#ifdef __linux__
        editor = "xdg-open";
        // No standard editor found. Sorry!
        return false;

    return system(string(editor + " \"" + filename + "\"").c_str()) == 0;

<mort> choosing a terrible license just to be spiteful towards others is possibly the most tux0r thing I've ever seen
Grey Hair Nixers
Here is my current "go to what I'm looking at" emacs function:

The goal is to handle various fallbacks, through org links, files, urls, and code definitions.
There's a discussion on lobsters related to this. An interesting custom solution is this one from vermaden.