2

I'm looking to find all executable files that are NOT in my $PATH.

Currently I'm doing this

find / \( -path "/opt" -prune -o -path "/var" -prune -o -path "/bin" -prune -o -path "/sbin" -prune -o -path "/usr" -prune -o -path "/opt" \) -o -type f -executable -exec file {} \;

I feel like there is a better way, I tried using a for loop with IFS=: to separate out the different parts of PATH but couldn't get it to work.

Edit: I should have specified I don't want to use a script for this.

3
  • executable by the current login-ed user? see also unix.stackexchange.com/a/622761/72456 Commented Feb 24, 2021 at 17:07
  • If you don't want a script, what do you want? Commented Feb 25, 2021 at 7:27
  • A 1 liner, no matter how complicated. Figured my method wasn't the simplest way Commented Feb 25, 2021 at 11:49

2 Answers 2

6

Assuming GNU find and the bash shell (as is used in the question), this is a short script that would accomplish what you're trying to do:

#!/bin/bash

IFS=:
set -f

args=( -false )
for dirpath in $PATH; do
        args+=( -o -path "$dirpath" )
done

find / \( \( "${args[@]}" \) -o \
          \( -type d \( ! -executable -o ! -readable \) \) \) -prune -o \
    -type f -executable -exec file {} +

This first creates the array args, consisting of dynamically constructed arguments to find. It does this by splitting the value of $PATH on colons, the value that we've given to the IFS variable. The splitting is happening when we use $PATH unquoted in the loop header.

Ordinarily, the shell would invoke filename globbing on each of the words generated from the splitting of $PATH, but I'm using set -f to turn off filename globbing, just in case any of the directory paths in $PATH contains globbing characters (these would still be problematic as the -path operand of find would interpret them as patterns).

If my PATH variable contains the string

/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin

then args will be the following list (each line here is a separate element in the array, this is not really a set of strings with newline characters in-between them):

-false
-o
-path
/usr/bin
-o
-path
/bin
-o
-path
/usr/sbin
-o
-path
/sbin
-o
-path
/usr/X11R6/bin
-o
-path
/usr/local/bin
-o
-path
/usr/local/sbin

This list is slotted into the find command invocation, in parentheses. There is no need to repeat -prune for each and every directory, as you could just use it once as I have above.

I've opted for pruning any non-executable or non-readable directory. This ought to get rid of a lot of permission errors for directories that you can't access or list the contents of. Would you want to simplify the find command by removing this bit, use

find / \( "${args[@]}" \) -prune -o \
    -type f -executable -exec file {} +

Also, I'm running file on the found pathnames in batches, rather than once per pathname.

12
  • Note that executable but not readable files would not be reported. Commented Feb 24, 2021 at 17:16
  • Note that it assumes that $PATH components don't contain wildcard characters (unlikely to be a problem in practice). Commented Feb 24, 2021 at 17:17
  • @StéphaneChazelas This is why I used set -f. Also, I should add the assumption about not wanting to see non-readable executables. Commented Feb 24, 2021 at 17:18
  • With or without -f, with a PATH='*'; -path '*' would match any path. Commented Feb 24, 2021 at 17:18
  • Note that the second -executable is redundant. Commented Feb 24, 2021 at 17:19
4

With zsh, you could do:

set -o extendedglob
LC_ALL=C find / -regextype egrep \
  -regex ${(j[|])${(u)path:P}//(#m)[][.\$^*()+{}\\|.]/\\$MATCH} -prune -o \
  -type f -executable -exec file {} +
  • $path is a special array tied to the $PATH env var.
  • $path:P gets the realpath (absolute, canonical, without symlinks) of each member
  • ${(u)array} removes duplicate (here after canonicalisation)
  • ${array//(#m)pattern/\\$MATCH} prepends a \ before anything that matches the pattern (here all the egrep regexp operators)
  • ${(j[|])array} joins the elements of the array with | to obtain a /dir|/dir\.2|... regexp.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.