58

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

0

8 Answers 8

75

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.
5
  • 2
    Good to know this command, and I actually need executables for completion. Maybe I use this command instead.
    – jcubic
    Mar 21, 2014 at 18:02
  • @jcubic, the shells already do it for command completion Why do it by hand?
    – vonbrand
    Mar 22, 2014 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, 2014 at 9: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. Sep 5, 2022 at 4:58
  • More details on compgen over at →Q151118
    – cachius
    Sep 7, 2022 at 11:39
24

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).

6
  • I didn't mention that I'm using bash.
    – jcubic
    Mar 22, 2014 at 14:41
  • whence -pm '*' will also print directories. Mar 18, 2022 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). Mar 18, 2022 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. Mar 19, 2022 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. Mar 20, 2022 at 7:31
12

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.

4
  • 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. Mar 22, 2014 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. Mar 22, 2014 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). Mar 22, 2014 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). Mar 22, 2014 at 18:06
7

How about this

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

The string substitution is used with Bash.

6
  • 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. Mar 22, 2014 at 10:06
  • 1
    That also assumes that $PATH components are not symlinks. Mar 22, 2014 at 10:12
  • @StephaneChazelas: Thanks! In other words, usual case.
    – user55518
    Mar 22, 2014 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. Mar 22, 2014 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.
    – user55518
    Mar 22, 2014 at 20:35
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.

7
  • 5
    Why the for? IFS=:; find $PATH -maxdepth 1 -executable -type f -printf '%f\n'
    – manatwork
    Mar 21, 2014 at 15:07
  • @manatwork will it work for non existing paths?
    – jcubic
    Mar 21, 2014 at 15:09
  • @manatwork didn't know that you can do that. need to read more about IFS.
    – jcubic
    Mar 21, 2014 at 15:10
  • 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. Mar 22, 2014 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. Mar 22, 2014 at 11:44
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)) ])
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
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
1
  • The question was about Bash.
    – jcubic
    Nov 23, 2022 at 20:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .