Is the file hidden?

2020-07-17 04:06发布

How can I determine whether a certain path points to a hidden file/folder?

NSString *file = @"/my/file/some.where";
BOOL fileIsHidden = // <-- what do I do here?

I know that hidden files are prefixed by a period. This is not the only criteria for a file to be hidden. I've read somewhere that there's a .hidden file that also configures what files are hidden.

Is there a Cocoa/Carbon way to find this out easily without rewriting all this logic and gathering information from various sources?

EDIT: the kLSItemInfoIsInvisible check seems to work for some files. It doesn't seem to hide:

/dev
/etc
/tmp
/var

All these are hidden by Finder by default.

标签: cocoa macos
5条回答
萌系小妹纸
2楼-- · 2020-07-17 04:45

As far as I know, hidden files on OS X are determined by either the filename being prefixed with a period or by a special "invisible" bit that is tracked by the Finder.

A few years back, I had to write something that toggled the visibility of a given file, and I found it was actually a lot more complicated than I expected. The crux of it was obtaining a Finder info (FInfo) record for the file and checking if the kIsInvisible bit was set. Here's the method I wrote for toggling file visibility—I think a lot of it is relevant to your task at hand, although you'll obviously have to tweak it a bit.

- (BOOL)toggleVisibilityForFile:(NSString *)filename isDirectory:(BOOL)isDirectory
{
    // Convert the pathname to HFS+
    FSRef fsRef;
    CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filename, kCFURLPOSIXPathStyle, isDirectory);

    if (!url)
    {
        NSLog(@"Error creating CFURL for %@.", filename);
        return NO;
    }

    if (!CFURLGetFSRef(url, &fsRef))
    {
        NSLog(@"Error creating FSRef for %@.", filename);
        CFRelease(url);
        return NO;
    }

    CFRelease(url);

    // Get the file's catalog info
    FSCatalogInfo *catalogInfo = (FSCatalogInfo *)malloc(sizeof(FSCatalogInfo));
    OSErr err = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, catalogInfo, NULL, NULL, NULL);

    if (err != noErr)
    {
        NSLog(@"Error getting catalog info for %@. The error returned was: %d", filename, err);
        free(catalogInfo);
        return NO;
    }

    // Extract the Finder info from the FSRef's catalog info
    FInfo *info = (FInfo *)(&catalogInfo->finderInfo[0]);

    // Toggle the invisibility flag
    if (info->fdFlags & kIsInvisible)
        info->fdFlags &= ~kIsInvisible;
    else
        info->fdFlags |= kIsInvisible;

    // Update the file's visibility
    err = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, catalogInfo);

    if (err != noErr)
    {
        NSLog(@"Error setting visibility bit for %@. The error returned was: %d", filename, err);
        free(catalogInfo);
        return NO;
    }

    free(catalogInfo);
    return YES;
}

Here's Apple's documentation on the Finder Interface, if you want more information. Hope this helps.

查看更多
疯言疯语
3楼-- · 2020-07-17 04:49

As the poster pointed out, it doesn't seem to work on /etc and /var and what not, so I modified the method.

It now takes a "isFile" boolean, YES means its a file, NO means a directory

BOOL isInvisible(NSString *str, BOOL isFile){
        CFURLRef inURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)str, kCFURLPOSIXPathStyle, isFile);
        LSItemInfoRecord itemInfo;
        LSCopyItemInfoForURL(inURL, kLSRequestAllFlags, &itemInfo);

        BOOL isInvisible = itemInfo.flags & kLSItemInfoIsInvisible;
        return (isInvisible != 0);
    }

    int main(){
           NSLog(@"%d",isInvisible(@"/etc",NO)); // => 1
           NSLog(@"%d",isInvisible(@"/Users",NO)); // => 0
           NSLog(@"%d",isInvisible(@"/mach_kernel",YES)); // => 1

    }

It seems to work on everything now!

查看更多
\"骚年 ilove
4楼-- · 2020-07-17 05:07

I think the point is that the Finder is the frontend towards the user of the filesystem tree(s). You want to ask the Finder if he thinks that a file is hidden or not, so you need an API to do that.

It seems that LSCopyItemInfoForURL does the work, as shown in the other answers. This post is very useful:

There are a few ways for things to be considered invisible (under Mac OS X):

  • kLSItemInfoIsInvisible Finder flag is set
  • filename begins with period
  • listing in the /.hidden file
  • invisible due to parentage
  • invisible due to package

I'm not copying it all, it's long but well written.

查看更多
淡お忘
5楼-- · 2020-07-17 05:08

Philosophical bit first:

No file is actually hidden. The Finder maintains its own internal data to determine if a file should be displayed in a directory listing or not; and this information may be shared with the other applications on the system.

Still, unless you are implementing a file system browser, the relevant determinations are usually transparently taken care of (no pun intended) by the internal workings of NSOpenPanel and friends.

If you are accessing a file programatically, and you maintain some semblance of ownership over the same file, or you are not displaying the file (or not) in the UI, it really doesn't matter if Finder considers it hidden or not.

As for the technical bit; since any application is able (through the aforementioned and venerable NSOpenPanel) to access this information, it's probably available somewhere; but as has been pointed out, this requires a rather circuitous little delve into CoreFoundation and LaunchServices.

The real issue is probably wether you need to know.

查看更多
Root(大扎)
6楼-- · 2020-07-17 05:10

From http://forums.macosxhints.com/archive/index.php/t-22641.html:

BOOL isInvisibleCFURL(CFURLRef inURL)
{
  LSItemInfoRecord itemInfo;
  LSCopyItemInfoForURL(inURL, kLSRequestAllFlags, &itemInfo);

  BOOL isInvisible = itemInfo.flags & kLSItemInfoIsInvisible;
  return isInvisible;
}

Update

Aha! /etc, /tmp, and /var are all invisible because they're actually symlinks to /private/etc, /private/tmp, and /private/var. If you tell Finder to directly visit /private (using the Go To Folder menu item), you'll see that they show up just fine. (Thanks to @IlDan for the tip)

I'm not sure what the best way to deal with this is; it only matters if you have a visible symlink to a file inside a hidden folder. You could probably get away with just manually excluding symlinks that go into /private, but if now, you might have to check the hidden status of every folder on the way up the path.

查看更多
登录 后发表回答