The Right Way
You should really be using gtk-launch
if it is available. It is usually part of the package libgtk-3-bin (this may vary by distro).
gtk-launch
is used as follows:
gtk-launch APPLICATION [URI...]
gtk-launch app-name.desktop
gtk-launch app-name
Please note that gtk-launch
requires the .desktop file to be installed (i.e. located in /usr/share/applications
or ~/.local/share/applications
).
So to get around this, we can use a hackish little Bash function that temporarily installs the desired .desktop file before launching it. The "correct" way to install a .desktop file is via desktop-file-install
but I'm going to ignore that.
launch(){
# Usage: launch PATH [URI...]
# NOTE: The bulk of this function is executed in a subshell, i.e. `(..)`
# This isn't strictly necessary, but it keeps everything
# out of the global namespace and lessens the likelihood
# of side effects.
(
# where you want to install the launcher to
appdir=$HOME/.local/share/applications
# the template used to install the launcher
template=launcher-XXXXXX.desktop
# ensure $1 has a .desktop extension, exists, is a normal file, is readable, has nonzero size
# optionally use desktop-file-validate for stricter checking
# desktop-file-validate "$1" 2>/dev/null || {
[[ $1 = *.desktop && -f $1 && -r $1 && -s $1 ]] || {
echo "ERROR: you have not supplied valid .desktop file" >&2
return 1
}
# ensure the temporary launcher is deleted upon exit
trap 'rm "$launcherfile" &>/dev/null' EXIT
# create a temp file to overwrite later
launcherfile=$(mktemp -p "$appdir" "$template")
launchername=${launcherfile##*/}
# overwrite temp file with the launcher file
if cp "$1" "$launcherfile" &>/dev/null; then
gtk-launch "$launchername" "${@:2}"
else
echo "ERROR: failed to copy launcher to applications directory" >&2
return 1
fi
)
}
You can use it like so (and also pass along additional arguments or URIs if you want):
launch PATH [URI...]
launch ./path/to/shortcut.desktop
The Manual Alternative
If you want to manually parse and execute a .desktop file, you can do so with the following awk
command:
awk '/^Exec=/ {sub("^Exec=", ""); gsub(" ?%[cDdFfikmNnUuv]", ""); exit system($0)}' app-name.desktop
If you want to treat the awk
command like an all-in-one script; we can even show an error message and exit with a return code of 1 in the event that an Exec command is not found:
awk 'BEGIN {command=""} /^Exec=/ {sub("^Exec=", ""); gsub(" ?%[cDdFfikmNnUuv]", ""); command=$0; exit} END {if (command!="") {exit system(command)} else {if (FILENAME == "-") {printf "ERROR: Failed to identify Exec line\n" > "/dev/stderr"} else {printf "ERROR: Failed to identify Exec line in \047%s\047\n", FILENAME > "/dev/stderr"} close("/dev/stderr"); exit 1}}'
The aforementioned commands will:
- Find the line starting with Exec=
- Remove Exec=
- Remove any Exec variables (e.g.
%f
, %u
, %U
). It is possible to replace these with positional arguments as the specification intends, but doing so would add significant complexity to the problem. See latest Desktop Entry Specification.
- Execute the command
- Immediately exit with the appropriate exit code (so as to not execute multiple Exec lines)
Note, this AWK script addresses a few edge cases that may or may not be properly addressed by some of the other answers. Specifically, this command removes multiple Exec variables (taking care not to otherwise remove the % symbol), will only execute a single Exec line command, and will behave as expected even if the Exec line command contains one or more equals sign (e.g. script.py --profile=name
).
Just a few other caveats... According to the specification, TryExec is:
Path to an executable file on disk used to determine if the program is actually installed. If the path is not an absolute path, the file is looked up in the $PATH environment variable. If the file is not present or if it is not executable, the entry may be ignored (not be used in menus, for example).
With that in mind, it doesn't make sense to execute it's value.
Some other concerns are Path and Terminal. Path consists of the working directory to run the program in. Terminal is a boolean that indicates whether the program is run in a terminal window. These can all be addressed, but there's no sense in reinventing the wheel as there are already implementations of the spec. If you do want to implement Path, keep in mind that system()
spawns a subprocess, so you can't change working directory by doing something like system("cd \047" working_directory "\047"); system(command)
. However you could presumably do something like system("cd \047" working_directory "\047 && " command)
. Note \047 are single quotes (so the command doesn't break on paths with spaces).
The Python Alternative
I'm stealing a page from Carlo here, who suggested creating a Python script to make use of the gi module. Here's a minimal way to execute the same code from the shell without having to create a file and worry about I/O.
launch(){
# Usage: launch PATH [URI...]
python - "$@" <<EOF
import sys
from gi.repository import Gio
Gio.DesktopAppInfo.new_from_filename(sys.argv[1]).launch_uris(sys.argv[2:])
EOF
}
Then execute the launcher function as follows:
launch ./path/to/shortcut.desktop
Note the use of URIs is optional. Also, no error checking is performed so you'll want to ensure the launcher exists and is readable (before using it) if you want your script to be durable.
exec
failed is because exec replaces your currently running process with the process you specify, so what you did was try to replace your shell with running the desktop as a compiled binary. The reason you couldn'tsudo exec
is because it's a shell builtin and not a binary command.