61

Is there a one-liner that will list all executables from $PATH in Bash?

Matthias Braun
  • 7,797
  • 7
  • 45
  • 54
jcubic
  • 9,612
  • 16
  • 54
  • 75

8 Answers8

79

This is not an answer, but it's showing binary, a command which you could run

compgen -c

(assuming bash)

Other useful commands

compgen -a # will list all the aliases you could run.
compgen -b # will list all the built-ins you could run.
compgen -k # will list all the keywords you could run.
compgen -A function # will list all the functions you could run.
compgen -A function -abck # will list all the above in one go.
Prvt_Yadav
  • 5,732
  • 7
  • 32
  • 48
Rahul Patil
  • 24,281
  • 25
  • 80
  • 96
  • 2
    Good to know this command, and I actually need executables for completion. Maybe I use this command instead. – jcubic Mar 21 '14 at 18:02
  • @jcubic, the shells already do it for command completion Why do it by hand? – vonbrand Mar 22 '14 at 22:50
  • 1
    @vonbrand I'm working on the shell in javascript/php and I'm executing shell in non interactive mode. – jcubic Mar 23 '14 at 09:04
  • 1
    Pipe to `sort -V` for sorted output. Ex: to see all executables in your PATH on your system, *and* all aliases, all sorted, run: `compgen -c | sort -V`. – Gabriel Staples Sep 05 '22 at 04:58
  • More details on compgen over at [→Q151118](/q/151118 "Understand \`compgen\` builtin command") – cachius Sep 07 '22 at 11:39
25

With zsh:

whence -pm '*'

Or:

print -rC1 -- $commands

(note that for commands that appear in more than one component of $PATH, they will list only the first one).

If you want the commands without the full paths, and sorted for good measure:

print -rC1 -- ${(ko)commands}

(that is, get the keys of that associative array instead of the values).

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • I didn't mention that I'm using bash. – jcubic Mar 22 '14 at 14:41
  • `whence -pm '*'` will also print directories. – Vladimir Panteleev Mar 18 '22 at 14:50
  • 1
    @VladimirPanteleev, Same for the other ones. One may argue it's as expected. If you have a directory in a component of `$PATH`, you may expect it to be executed. `zsh` will actually try to execute the `/var` directory if you enter `PATH=/; var` for instance (and likely fail as I don't think any system allows executing directories). – Stéphane Chazelas Mar 18 '22 at 19:10
  • 1
    `compgen -c` will not include directories, so this seems like a zsh bug. If it's checking if entries are executable, it might as well also check that they're not directories. – Vladimir Panteleev Mar 19 '22 at 21:15
  • 2
    @VladimirPanteleev, in `zsh`, that's controlled by the `hashexecutablesonly` option. Checking the type of files is costly especially on network filesystems as you need one `stat()` per file in `$PATH` directories, which is why it's not enabled by default. That's generally good enough in the usual case where there's no garbage in `$PATH` directories, but you can set that option if `$PATH` dirs are only on fast filesystems. – Stéphane Chazelas Mar 20 '22 at 07:31
  • With `hashexecutablesonly` and a `rehash` it does seem to skip directories. You should add that to the answer, it seems very useful (and brings the answer closer to answering the question as stated)! – Vladimir Panteleev Mar 21 '22 at 04:42
13

In any POSIX shell, without using any external command (assuming printf is built in, if not fall back to echo) except for the final sorting, and assuming that no executable name contains a newline:

{ set -f; IFS=:; for d in $PATH; do set +f; [ -n "$d" ] || d=.; for f in "$d"/.[!.]* "$d"/..?* "$d"/*; do [ -f "$f" ] && [ -x "$f" ] && printf '%s\n' "${f##*/}"; done; done; } | sort

If you have no empty component in $PATH (use . instead) nor components beginning with -, nor wildcard characters \[?* in either PATH components or executable names, and no executables beginning with ., you can simplify this to:

{ IFS=:; for d in $PATH; do for f in $d/*; do [ -f $f ] && [ -x $f ] && echo ${f##*/}; done; done; } | sort

Using POSIX find and sed:

{ IFS=:; set -f; find -H $PATH -prune -type f -perm -100 -print; } | sed 's!.*/!!' | sort

If you're willing to list the rare non-executable file or non-regular file in the path, there's a much simpler way:

{ IFS=:; ls -H $PATH; } | sort

This skips dot files; if you need them, add the -A flag to ls if yours has it, or if you want to stick to POSIX: ls -aH $PATH | grep -Fxv -e . -e ..

There are simpler solutions in bash and in zsh.

Vladimir Panteleev
  • 1,596
  • 15
  • 29
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • That assumes that `$PATH` is set and doesn't contain empty components, and that components don't look like find predicates (or ls options). Some of those will also ignore dot files. – Stéphane Chazelas Mar 22 '14 at 10:14
  • @StephaneChazelas Yeah, ok. Apart from empty components, this falls squarely under the “don't do this” category — PATH is under your control. – Gilles 'SO- stop being evil' Mar 22 '14 at 16:41
  • It still doesn't work if the empty element is last (as it usually is). (except in `yash` and `zsh` in sh emulation). – Stéphane Chazelas Mar 22 '14 at 17:50
  • In your `find` one. `-prune` will prevent listing directories. You probably want `-L` instead of `-H` as you want to include symlinks (common for executables). `-perm -100` gives no guarantee that the file be executable by you (and might (unlikely) exclude executable files). – Stéphane Chazelas Mar 22 '14 at 18:06
8

How about this

find ${PATH//:/ } -maxdepth 1 -executable

The string substitution is used with Bash.

  • 4
    That assumes that `$PATH` is set, doesn't contain wildcard or blank characters, doesn't contain empty components. That assumes GNU find as well. Note that `${var//x/y}` is `ksh` syntax (also supported by zsh and bash). Strictly speaking that also assumes that $PATH components are not `find` predicates either. – Stéphane Chazelas Mar 22 '14 at 10:06
  • 1
    That also assumes that `$PATH` components are not symlinks. – Stéphane Chazelas Mar 22 '14 at 10:12
  • @StephaneChazelas: Thanks! In other words, usual case. –  Mar 22 '14 at 15:18
  • Setting `IFS=:` is more robust than doing this substitution. Paths with spaces aren't that uncommon on Windows. Symbolic links are fairly common, but that's easily solved with `-H`. – Gilles 'SO- stop being evil' Mar 22 '14 at 16:43
  • @Gilles: of course. however I don't see any reasonable use-case for this question, therefore is no need for a bullet-proof answer. –  Mar 22 '14 at 20:35
  • It also doesn't work if PATHs contain spaces, like `/Applications/Visual` `Studio` are treated as separate args. – ahmet alp balkan Feb 10 '22 at 06:25
6

I came up with this:

IFS=':';for i in $PATH; do test -d "$i" && find "$i" -maxdepth 1 -executable -type f -exec basename {} \;; done

EDIT: It seems that this is the only command that don't trigger SELinux alert while reading some of the files in bin directory by apache user.

jcubic
  • 9,612
  • 16
  • 54
  • 75
  • 5
    Why the `for`? `IFS=:; find $PATH -maxdepth 1 -executable -type f -printf '%f\n'` – manatwork Mar 21 '14 at 15:07
  • @manatwork will it work for non existing paths? – jcubic Mar 21 '14 at 15:09
  • @manatwork didn't know that you can do that. need to read more about IFS. – jcubic Mar 21 '14 at 15:10
  • Well, `find` just outputs messages on stderr for each missing directory. If you redirect stderr to /dev/null, should be fine. (That will also remove Permission denied messages, for existing directories (passing your `test -d`) for which you have no execute permission.) – manatwork Mar 21 '14 at 15:12
  • 3
    That assumes that `$PATH` is set and doesn't contain wildcard characters and doesn't contain empty components. That also assumes the GNU implementation of `find`. – Stéphane Chazelas Mar 22 '14 at 10:00
  • 2
    Because of `-type f` instead of (GNU specific) `-xtype f`, that will also omit symlinks. That will also not list the content of `$PATH` components that are symlinks. – Stéphane Chazelas Mar 22 '14 at 11:44
  • It would be nice if you would wrap this copy-pastable code in a subshell so that the IFS doesn't stay changed after executing this. – mxmlnkn Jun 11 '22 at 18:13
3

If you can run Python 2 in your shell, the following (ridiculously long) one-liner can be used as well:

python2 -c 'import os;import sys;output = lambda(x) : sys.stdout.write(x + "\n"); paths = os.environ["PATH"].split(":") ; listdir = lambda(p) : os.listdir(p) if os.path.isdir(p) else [ ] ; isfile = lambda(x) : True if os.path.isfile(os.path.join(x[0],x[1])) else False ; isexe = lambda(x) : True if os.access(os.path.join(x[0],x[1]), os.X_OK) else False ; map(output,[ os.path.join(p,f) for p in paths for f in listdir(p) if isfile((p,f)) and isexe((p,f)) ])'

This was mostly a fun exercise for myself to see if it could be done using one line of Python code without resorting to using the exec function. In a more readable form, and with some comments, the code looks like this:

import os
import sys

# This is just to have a function to output something on the screen.
# I'm using Python 2.7 in which 'print' is not a function and cannot
# be used in the 'map' function.
output = lambda(x) : sys.stdout.write(x + "\n")

# Get a list of the components in the PATH environment variable. Will
# abort the program is PATH doesn't exist
paths = os.environ["PATH"].split(":")

# os.listdir raises an error is something is not a path so I'm creating
# a small function that only executes it if 'p' is a directory
listdir = lambda(p) : os.listdir(p) if os.path.isdir(p) else [ ]

# Checks if the path specified by x[0] and x[1] is a file
isfile = lambda(x) : True if os.path.isfile(os.path.join(x[0],x[1])) else False

# Checks if the path specified by x[0] and x[1] has the executable flag set
isexe = lambda(x) : True if os.access(os.path.join(x[0],x[1]), os.X_OK) else False

# Here, I'm using a list comprehension to build a list of all executable files
# in the PATH, and abusing the map function to write every name in the resulting
# list to the screen.
map(output, [ os.path.join(p,f) for p in paths for f in listdir(p) if isfile((p,f)) and isexe((p,f)) ])
Lucas
  • 119
  • 4
brm
  • 991
  • 6
  • 7
0
#!/usr/bin/env python
import os
from os.path import expanduser, isdir, join, pathsep

def list_executables():
    paths = os.environ["PATH"].split(pathsep)
    executables = []
    for path in filter(isdir, paths):
        for file_ in os.listdir(path):
            if os.access(join(path, file_), os.X_OK):
                executables.append(file_)
    return executables
Steven
  • 101
  • 1
0

On MacOS with zsh you can do this:

find ${(s/:/)PATH}

or, if you want to be fancy:

find ${(s/:/)PATH} -maxdepth 1 -perm -u+x -type f | xargs basename
Jake
  • 231
  • 2
  • 6