Is there an "easy" way of running an "ls -la" style command for listing all files / executable binaries in the current PATH?

(I intend to pipe the output into grep, for looking for commands with unknown prefixes but basically known "names", the kind of case when the auto-completion / tabbing in bash is essentially useless. So some sort of an "inverse auto-complete feature" ...)

  • Can you give examples? How will it different with ls -la? – John Siu Jan 9 '13 at 18:02
  • "ls -la"/"ls -a" does only list files in your current directory (pwd). I want to list all (executable) files in all directories included in PATH. – s-m-e Jan 9 '13 at 22:52
compgen -c # will list all the commands you could run.
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. 
  • Nice, this is very much what I have been looking for. It even includes executable binaries in the current directory. Thanks. – s-m-e Jan 9 '13 at 23:04
  • Works well... in bash. – Emanuel Berg Jan 10 '13 at 3:06
  • is there a way to list the path of each of these without doing which $(compgen -A function -abck)? – h3rrmiller Jan 10 '13 at 14:06
  • I couldn't find any better alternative. – Nykakin Jan 10 '13 at 16:11

Here's a function that lists the content of the directories $PATH. If passed arguments, the function only lists commands whose name contaisn one of the arguments. The arguments are interpreted as glob patterns.

shopt -s extglob
lspath () {
  local IFS pattern
  for d in $PATH; do
    for x in "$d/"$pattern; do
      [ "$x" = "$d/$pattern" ] || echo "${x##*/}"
  done | sort -u

Like many things, this is easier in zsh.

lspath () {
  (($#)) || set ''
  print -lr -- $^path/*$^@*(N:t) | sort -u

The ^ character in parameter expansion causes the text concatenated with the array to be added to each array element, e.g. path=(/bin /usr/bin); echo $^path/foo prints /bin/foo /usr/bin/foo.
/*$^@* looks like a comic book insult but is in fact the ordinary character /, the wildcard *, the special parameter $@ (the array of positional parameter) with the ^ modifier, and again *.
(N:t) is the glob qualifier N to get an empty expansion if there is no match followed by the history modifier t to keep only the basename (“tail”) of each match.

More cryptic, avoids the external call but this is only of cosmetic interest:

lspath () {
  (($#)) || set ''
  local names; names=($^path/*$^@*(N:t))
  print -lr -- ${(ou)names}

You may in fact be looking for the apropos command, which searches for man pages of commands whose short description contains a keyword. A limitation is that this only find commands that have a man page.

  • In the zsh function, the second ^ seems to result in no output for a "no arguments" call? If you drop it, you could list them all, and still search for single keywords (but not a list of them). The (Non:t) seems to say that asterisks should be expanded? Couldn't figure that last one out. – Emanuel Berg Jan 10 '13 at 3:02
  • @EmanuelBerg Right, thanks for the fix. I've added an explanation of the line noise. – Gilles 'SO- stop being evil' Jan 10 '13 at 13:37
  • OK, let's see: If there are no arguments, you set "them" to nothing, because what was there (when there wasn't any), still somehow was disruptive of what you did next? Also, I've never seen lowercase path, but when I run it in the shell, it is indeed $PATH but with spaces instead of :. The rest is crystal clear :) – Emanuel Berg Jan 11 '13 at 18:03
  • @EmanuelBerg No: if there are no arguments, I set the array of arguments to be a one-element array containing the empty string. This way, no arguments means every name containing the empty string matches, i.e. every name matches. $path is a zsh feature: it's a tied parameter, an array that is automatically updated when PATH is updated and vice versa. – Gilles 'SO- stop being evil' Jan 11 '13 at 19:18
  • Aha, now I see! Wow, that was unorthodox! – Emanuel Berg Jan 11 '13 at 19:27
function findinpath () { 
   OLDIFS="$IFS" ; 
   IFS="$(printf ':\t\n')" ; 
   for regexp in "$@" ; do 
      for adir in $PATH ; do 
         find "$adir" -perm -111 -a ! -type d -ls 2>/dev/null | grep -i "/[^/]*$regexp"
      done ; 
   done ; 
   IFS="$OLDIFS" ; 

the find only matches that : have at least one "x" (executable) bit set, and that is not a directory.

and use it with a list of regexp to be found:

findinpath awk sed '\.sh$'
  • regexp "." will show you everything – Olivier Dulac Jan 9 '13 at 18:19
  • with the regular IFS, you can find duplicate entries in your $PATH with : echo $PATH | tr ':' '\n' | sort | uniq -d – Olivier Dulac Jan 9 '13 at 18:42
  • 1
    I tried the code for all the answers and this is the only one that actually gave me what I was looking for (full paths to every instance of a given command on the PATH). – Chris Page Apr 10 '15 at 3:33
  • I m glad it helped :) – Olivier Dulac Apr 10 '15 at 8:47
for i in $(echo $PATH | sed -e 's/\:/\ /g'); do find "$i" -perm +rwx -exec echo {} \; 2> /dev/null; done

first we echo $PATH into sed and replace ":" with " ".

then we do a find on each of those things to find files with rwx and echo them.

2> /dev/null is so find won't print errors

  • 3
    you need a -maxdepth 1 and -type f in your find, else you'll find subdirectories and their files (which PATH search won't). Also, your permission test is weird—should just for +x, I'd think? – derobert Jan 9 '13 at 20:59
  • This won't handle the edge cases were a valid blank entry is the same as '.', for example :/bin or /bin::/usr/bin. Try adding s/::/:.:/;s/^:/.:/;s/:$/:./ to the sed command. – Arcege Jan 9 '13 at 23:51
  • Actually find can search more paths, so you can do it without the loop: find $(echo $PATH | sed -e 's/\:/\ /g') -perm +rwx … Of course, directory names containing spaces will mess it up, but the loop based one has the same problem anyway. – manatwork Jan 10 '13 at 14:00
  • @manatwork you're right. I added quotes to solve that issue just now – h3rrmiller Jan 10 '13 at 14:04
  • Sorry, that solves nothing. The problem is with replacing the colons with spaces to allow word splitting. So you transform “/foo bar:/fiz baz” into “/foo bar /fiz baz” then pass that to for. So for will loop over a list of 4 words. To manipulate word splitting better set the IFS according to your need: IFS=':'; find $PATH -perm +rwx …. – manatwork Jan 10 '13 at 14:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.