Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Expanding Tilde-based paths

Q: How do I expand a tilde-based path to its full path value?

A: Some situations may arise where you would like to expand a tilde-based relevant path into a resolved, absolute path. For example "~/" might resolve to "/Users/johndoe". Here are examples on how to accomplish this task under each development environment (Objective-C, Core Foundation, and POSIX).

Note: Only use this code for resolving paths that have been generated at runtime or specified by user input. You should never store paths. For persistent storage of file/directory locations you should use aliases.

Listing 1: Objective-C (result is autoreleased)

NSString *result = [@"~/Desktop" stringByExpandingTildeInPath];

Listing 2: Core Foundation (caller responsible for releasing result)

static CFURLRef CreateURLByExpandingTildePath(
    CFStringRef     path,
    Boolean         isDir
)
{
    CFURLRef        result = NULL, baseURL = NULL;
    CFStringRef     urlPath = NULL;
    Boolean         relative;

    assert(path != NULL);

    if ( ! CFStringHasPrefix(path, CFSTR("~")) )
    {
        // No leading "~", so use path as the entire path.
        urlPath = CFStringCreateCopy(kCFAllocatorDefault, path);
        relative = false;
    } else {
        CFIndex     pathLen;
        Boolean     foundSlash;
        CFRange     foundRange = {kCFNotFound,0};
        CFIndex     userNameLengthUTF16;

        relative = true;

        pathLen = CFStringGetLength(path);
        foundSlash = CFStringFindWithOptions(
            path,
            CFSTR("/"),
            CFRangeMake(1, pathLen-1),
            0,
            &foundRange
        );
        if (foundSlash) {
            userNameLengthUTF16 = foundRange.location - 1;
            urlPath = CFStringCreateWithSubstring(
                NULL,
                path,
                CFRangeMake(
                    foundRange.location + 1,    // +1 to exclude the slash
                    pathLen - (foundRange.location + 1)
                )
                );
        } else {
            userNameLengthUTF16 = pathLen - 1;
            urlPath = CFStringCreateWithCharacters(NULL, NULL, 0);
        }
        if (urlPath != NULL) {
            char            userNameUTF8[
                CFStringGetMaximumSizeForEncoding(
                    userNameLengthUTF16,
                    kCFStringEncodingUTF8) + 1  // +1 for terminating null
            ];
            CFIndex         convertedCountUTF16;
            CFIndex         userNameLengthUTF8;
            struct passwd * pw;

            convertedCountUTF16 = CFStringGetBytes(
                path,
                CFRangeMake(1, userNameLengthUTF16),
                kCFStringEncodingUTF8,
                0,
                false,
                (UInt8 *) userNameUTF8,
                sizeof(userNameUTF8),
                &userNameLengthUTF8
            );
            assert(convertedCountUTF16 == userNameLengthUTF16);
            assert(userNameLengthUTF8 < sizeof(userNameUTF8));
            userNameUTF8[userNameLengthUTF8] = 0;

            if (userNameLengthUTF8 == 0) {
                 pw = getpwuid( getuid() );
            } else {
                 pw = getpwnam(userNameUTF8);
            }
            if (pw != NULL) {
                baseURL = CFURLCreateFromFileSystemRepresentation(
                    NULL,
                    (const UInt8 *) pw->pw_dir,
                    strlen(pw->pw_dir),
                    true
                );
            }
        }
    }

    if (urlPath != NULL) {
        if ( ! relative ) {
            result = CFURLCreateWithFileSystemPath(
                NULL,
                urlPath,
                kCFURLPOSIXPathStyle,
                isDir
            );
        } else if ( baseURL != NULL ) {
            if ( CFStringGetLength(urlPath) == 0 ) {
                result = baseURL;
                CFRetain(result);
            } else {
                result = CFURLCreateWithFileSystemPathRelativeToBase(
                    NULL,
                    urlPath,
                    kCFURLPOSIXPathStyle,
                    isDir,
                    baseURL
                );
            }
        }
    }

    if (urlPath != NULL) {
        CFRelease(urlPath);
    }
    if (baseURL != NULL) {
        CFRelease(baseURL);
    }

    return result;
}


Listing 3: POSIX (caller responsible for freeing result)

char *CreatePathByExpandingTildePath(char *path)
{
    Boolean         success;
    char            *result = NULL;
    char            finalPath[PATH_MAX], workingPath[PATH_MAX];

    assert(path != NULL);
    strncpy(workingPath, path, strlen(path));

    if ( workingPath[0] != '~' )
        success = (realpath(workingPath, finalPath) != NULL);
    else
    {
        char *fullPath, *firstSlash, *suffix, *homeDirStr;
        struct passwd *pw;

        // initialize variables
        fullPath = firstSlash = suffix = homeDirStr = NULL;

        firstSlash = strchr(workingPath, '/');
        if (firstSlash == NULL)
            suffix = "";
        else
        {
            *firstSlash = 0;    // so userName is null terminated
            suffix = firstSlash + 1;
        }

        if (workingPath[1] == 0)
            pw = getpwuid( getuid() );
        else
            pw = getpwnam( &workingPath[1] );

        if (pw != NULL)
            homeDirStr = pw->pw_dir;

        if (homeDirStr != NULL)
            asprintf(&fullPath, "%s/%s", homeDirStr, suffix);

        if (fullPath != NULL)
            success = (realpath(fullPath, finalPath) != NULL);
        else
            success = false;

        free(fullPath);
    }

    if (success)
    {
        result = (char*)calloc(1, strlen(finalPath));
        if(result)
        {
            strncpy(result, finalPath, strlen(finalPath));
        }
    }

    return result;
}

Document Revision History

DateNotes
2007-09-24First Version

Posted: 2007-09-24




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.