5

I am using Ubuntu 20.04, and I usually need to find the final executable path for a given command. Let's take awk as an example:

file $(which awk)
# Output: /usr/bin/awk: symbolic link to /etc/alternatives/awk

file /etc/alternatives/awk

Output: /etc/alternatives/awk: symbolic link to /usr/bin/gawk

My question is:

Is there a command (or a flag for file command) that will return directly the final path of a given command? in the example above I would like it to return the path of gawk

Thank you

Kusalananda
  • 333,661

4 Answers4

10

You can use readlink:

$ readlink -f -- "$(which awk)"
/usr/bin/gawk

From man readlink:

-f, --canonicalize canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist

  • 1
    I think readlink -e is better, because using -f will return the current path if the given command doesn't exist. – Amazigh_05 Dec 23 '21 at 16:34
  • 1
    @OK-Validation I don't think readlink -e offers any advantages, since $(which ...) shouldn't print nonexistent paths in the first place. Additionally, readlink -f is slightly more portable; readlink -e is GNU-specific. (I know you specified Ubuntu, but people using e.g. Alpine could stumble across this and be confused when it doesn't work.) – anonymoose Dec 25 '21 at 03:33
8

I found another command realpath:

realpath -- "$(which awk)"
#Output: /usr/bin/gawk

5

In zsh, if you have a command name in a variable $cmd, $cmd:c will expand to the path of the command¹ (via a look-up of $PATH / $path), and the :P qualifier can be used to get the corresponding realpath (a symlink-free canonical absolute path to the file as when using the realpath() standard C function):

$ cmd=awk
$ print -r -- $cmd:c
/usr/bin/awk
$ print -r -- $cmd:c:P
/usr/bin/gawk
$ file -- $cmd:c:P
/usr/bin/gawk: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b863ebf57d3cc33d8e7fed3be0e0b5d235489b46, for GNU/Linux 3.2.0, stripped
$ namei -l -- $cmd:c
f: /usr/bin/awk
drwxr-xr-x root root /
drwxr-xr-x root root usr
drwxr-xr-x root root bin
lrwxrwxrwx root root awk -> /etc/alternatives/awk
drwxr-xr-x root root   /
drwxr-xr-x root root   etc
drwxr-xr-x root root   alternatives
lrwxrwxrwx root root   awk -> /usr/bin/gawk
drwxr-xr-x root root     /
drwxr-xr-x root root     usr
drwxr-xr-x root root     bin
-rwxr-xr-x root root     gawk

To avoid the intermediary variable, you can use an anonymous function:

$ (){print -r -- $1:c:P} awk
/usr/bin/gawk

Or a normal function:

$ canonical-path-of() print -rC1 -- $@:c:P
$ canonical-path-of awk ls
/usr/bin/gawk
/usr/bin/ls

You can also use the =cmd operator which is a form of filename expansion like tilde expansion but that expands to the path of the corresponding command, and still apply the :P modifier by this time as a glob qualifier²:

$ print -r -- =awk
/usr/bin/awk
$ print -r -- =awk(:P)
/usr/bin/gawk
$ print -r -- =blah
zsh: blah not found

¹ beware however that if the command in not found in $PATH, $cmd:c will expand to the same thing as $cmd, and $cmd:c:P would expand to the path of $cmd relative to the current working directory.

² this time when the command can't be found, it triggers a fatal error. That =cmd expansion can be disabled with set +o equals

5

If you want to tell the file command on Linux to return information about the awk command, and to resolve all symbolic links while doing so, then use file with its -L option:

$ file -L -- "$(command -v awk)"
/bin/awk: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b863ebf57d3cc33d8e7fed3be0e0b5d235489b46, for GNU/Linux 3.2.0, stripped

To resolve all links to get the final absolute path of a command, use readlink -e:

$ readlink -e -- "$(command -v awk)"
/usr/bin/gawk
Kusalananda
  • 333,661