Tested in a number of configurations on a number of file and symlink combinations. Written for PHP 5.3+, though should work in earlier versions if you change "$home = $_ENV['HOME'] ?: '.';" to "$home = $_ENV['HOME'] ? $_ENV['HOME'] : '.';". Designed for Linux/Unix, though probably works on Windows if you specify true for $semiColon - not tested though.

No licence. Free to use or change without credit. If you find this useful, it would be nice to hear from you in this thread.

PHP Code:
 * Searches a path list for the requested filename, with the requested permissions. Returns the first match.
 * @param string $name
 *         Filename to find
 * @param string $path
 *         Colon separated path list. If omitted or null, $_ENV['path'] is used. This will use realpath() to translate
 *         individual paths, and translate ~/ at the start of a path to $_ENV['home']. If no portions of this are
 *         valid paths, an E_USER_WARNING is triggered.
 * @param bool $readable
 *         If true, only files that are readable will match
 * @param bool $writable
 *         If true, only files that are writable will match
 * @param bool $executable
 *         If true, only files that are executable will match
 * @param bool $semiColon
 *         If true, uses the Windows path separator instead
 * @return bool|string
 *         Either the full, real path without a trailing "/" or false if it couldn't be found
function find_file($name$path null$readable true$writable false$executable false$semiColon false) {
// Handle default path
if (is_null($path)) {
$path $_ENV['PATH'];

// Get a value for home, defaulting to cwd
$home $_ENV['HOME'] ?: '.';

// Split the path
$paths explode(($semiColon ';' ':'), $path);

// Setup to detect fully invalid $path for a E_USER_WARNING
$hasValid false;

$paths as $pathItem) {
// realpath() doesn't support ~, so compensate for that
if (($pathItem === '~') || (substr($pathItem02) === '~/')) {
$pathItem $home '/' substr($pathItem2);

// Check this is a valid path
if (($pathItem === '') || !is_dir($pathItem)) {

// We got here, it's a valid path
$hasValid true;

// Setup fullName and pathItem with the real, full paths
$fullName = ($pathItem realpath($pathItem)) . '/' $name;

// Check criteria and return first match
if (file_exists($fullName) && !is_dir($fullName)
            && (!
$readable || is_readable($fullName))
            && (!
$writable || is_writable($fullName))
            && (!
$executable || is_executable($fullName))
        ) {

// If $path had no valid paths, trigger a warning
if (!$hasValid) {
trigger_error('No valid paths given'E_USER_WARNING);


Examples of use:

Find a readable my.cnf in some likely locations:

PHP Code:
Find an executable convert in the system provided path:

PHP Code: