How can I add OS X “tags” to files programmaticall

2019-03-07 19:30发布

问题:

Since Mavericks, OS X has had the ability to tag & colour files in Finder.

Is there any way to add tags to files through Cocoa APIs or via a shell command?

回答1:

Sorry for adding another answer, but the one related to setting Label colors was pretty long already. Here is an excerpt from a python script that I use to set the User Tags. It seems to work to make things searchable, but not sure if the tags will show up correctly. Usage is basically:

tagfile.py "Tag Name" FileOrFolderName

Code below.

#! /usr/bin/env python
# -*- coding: utf-8 -*-

""" Write tags to file
Usage:
    tagfile.py "TagName" FileName1 FileName2 

    You can use wildcards for the file name. Use quotes if spaces in tags.
    To check if it worked, use xattr -l FileName

"""

import sys
import subprocess

def writexattrs(F,TagList):
    """ writexattrs(F,TagList):
    writes the list of tags to three xattr fields on a file-by file basis:
    "kMDItemFinderComment","_kMDItemUserTags","kMDItemOMUserTags
    Uses subprocess instead of xattr module. Slower but no dependencies"""

    Result = ""

    plistFront = '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><array>'
    plistEnd = '</array></plist>'
    plistTagString = ''
    for Tag in TagList:
        plistTagString = plistTagString + '<string>{}</string>'.format(Tag.replace("'","-"))
    TagText = plistFront + plistTagString + plistEnd

    OptionalTag = "com.apple.metadata:"
    XattrList = ["kMDItemFinderComment","_kMDItemUserTags","kMDItemOMUserTags"]
    for Field in XattrList:    
        XattrCommand = 'xattr -w {0} \'{1}\' "{2}"'.format(OptionalTag + Field,TagText.encode("utf8"),F)
        if DEBUG:
            sys.stderr.write("XATTR: {}\n".format(XattrCommand))
        ProcString = subprocess.check_output(XattrCommand, stderr=subprocess.STDOUT,shell=True) 
        Result += ProcString
    return Result

DEBUG = False


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print __doc__
    else:
        TagList = [ sys.argv[1] ]
        # print TagList
        # Or you can hardwire your tags here
        # TagList = ['Orange','Green']
        FileList = sys.argv[2:]

        for FileName in FileList:
            writexattrs(FileName, TagList)


回答2:

Check out tag, "a command line tool to manipulate tags on Mac OS X 10.9 Mavericks files, and to query for files with those tags". The GitHub repository has installation instructions (there are Homebrew and MacPorts packages).



回答3:

I add this answer, because OP asked for a shell script and tagged it bash. I wrote this Automator service, which tags the selected file with the tags of another file. I have added comments to outline the use of bash's interaction with the tags and colours using bash script.

 

Basics

In scripts both OpenMeta and Mavericks tags can be accessed with the command xattr. Using it without modifiers, $ xattr [file], gives a list of set attributes. $ xattr -h gives a nice guide to usage.

Mavericks' tags are in com.apple.metadata:_kMDItemUserTags, while OpenMeta tags can be in a variety of attributes. Amongst others com.apple.metadata:kOMUserTags, org.openmetainfo:kMDItemOMUserTags and org.openmetainfo:kOMUserTags.

Mavericks handles colours and tags in different attributes, by placing tags in _kMDItemUserTags and colours in FinderInfo for every file. This is a bizarre choice, and it is one of the reasons Finder struggles under the pressure of tagging. If you have 800 files tagged kapow, each in a different folder, and you subsequently choose the colour blue for kapow, Finder has to find and alter attributes for every single file.

You can play around with the oddity by removing the com.apple.FinderInfo attribute from a tagged and coloured file: $ xattr -d com.apple.FinderInfo [file]. The colour will disappear in Finder listings, but the tag (and its colour) remains associated with the file.

 

Bash script to import tags from another file

In the script, the selected file(s) in Finder is/are saved to the variable $tagless, and the chosen supplier of tags is $tagfull.

TAGFULID=${#@}
TAGFUL=${!TAGFULID}

## Use xattr to read all existing tags:
ATTRS=$(xattr "$TAGFUL")

for f in "$@" ## For every selected file in Finder, do:
do
    if("$TAGFUL"="$f") ## Is the supplier of tags is amongst the selected files?
    then
        break
    fi

    if [[ "$ATTRS" == *kMDItemUserTags* ]] ## Are there tags?
        then
        ## Load tags:
        TAGS=$(xattr -px com.apple.metadata:_kMDItemUserTags "$TAGFUL")
        ## Write tags:
        xattr -wx com.apple.metadata:_kMDItemUserTags "$TAGS" "$f"
    fi
    if [[ "$ATTRS" == *FinderInfo* ]] ## Are there colours?
    then
        ## Load colour:
        FINDERINFO=$(xattr -px com.apple.FinderInfo "$TAGFUL")
        ## Write colour:
        xattr -wx com.apple.FinderInfo "$FINDERINFO" "$f"
    fi
done



回答4:

In Apple's What's New in OS X it states that NSURL handles tags, and the Common File System Resource_Keys gives the required key as NSURLTagNamesKey and states its value is just an array of strings.



回答5:

The OpenMeta framework is a third-party standard for adding metadata to OS X files using extended attributes. It is used by a number of third-party applications.

Or you can use the XATTR command to manipulate the extended attributes via command line.



回答6:

You could give this a shot:

xattr -w com.apple.metadata:_kMDItemUserTags '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><array><string>Orange</string><string>Red</string></array></plist>' $currentFile

You'll want to replace $currentFile with the file you'd like to add tags to, and change

<string>Orange</string><string>Red</string>

to a list of whatever tags you want to add.



回答7:

This does not cover tags, but for changing label colors, one way to do it is through a command like this:

xattr -wx com.apple.FinderInfo \
0000000000000000000400000000000000000000000000000000000000000000 myfile.txt

The 04 buried in the middle is setting the file color.

Here is a python script which wraps that command lets you set the tag color on a file or series of files:

import sys
import subprocess

def colorizeFile(ColorName,FileName):
    ReverseTable = {
         "clear"  :  "01",
         "gray"   :  "03",
         "green"  :  "04",
         "purple" :  "06",
         "blue"   :  "09",
         "yellow" :  "0A",
         "red"    :  "0C",
         "orange" :  "0E",
         "c"      :  "01",
         "a"      :  "03",
         "g"      :  "04",
         "p"      :  "06",
         "b"      :  "09",
         "y"      :  "0A",
         "r"      :  "0C",
         "o"      :  "0E",
    }

    HexString = 18*"0" + ReverseTable.get(ColorName) + 44*"0"
    Xcommand = 'xattr -wx com.apple.FinderInfo {0} {1}'.format(HexString,FileName)
    ProcString = subprocess.check_call(Xcommand, stderr=subprocess.STDOUT,shell=True) 

if __name__ == "__main__":
    if len(sys.argv)<3:
        sys.stderr.write(__doc__.format(sys.argv[0]))
    else:
        Cname = sys.argv[1]
        Flist = sys.argv[2:]
        for File in Flist:
            colorizeFile(Cname.lower(),File)
        sys.stderr.write("## Colorized {0} file(s) as {1}\n".format(len(Flist),Cname)) 

Usage is:

  labelcolor.py [color] *.jpg

where [color] is a name or abbreviation as defined below:

    clear (c), grAy (a), green (g), purple (p), 
    blue (b), yellow (y), red (r), orange (o)


回答8:

In Ask Different

With multiple answers, one of which is accepted:

  • Possible to tag a folder via terminal? (2013-11-15)

Here in Stack Overflow the question arose slightly earlier (2013-11-01) so I'll add my answer here.

openmeta

Open source at https://code.google.com/p/openmeta/source/browse/trunk/trunk/openmeta

The openmeta command appears to take a dual attribute approach, working with both:

  • com.apple.metadata:kMDItemOMUserTags
  • com.apple.metadata:_kMDItemUserTags

Example usage

sh-3.2$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.9.5
BuildVersion:   13F1096
sh-3.2$ uname -a
Darwin gpes3e-gjp4.local 13.4.0 Darwin Kernel Version 13.4.0: Wed Mar 18 16:20:14 PDT 2015; root:xnu-2422.115.14~1/RELEASE_X86_64 x86_64
sh-3.2$ date
Sun 26 Jul 2015 08:00:23 BST
sh-3.2$ rm ~/Desktop/test.txt 
sh-3.2$ touch ~/Desktop/test.txt 
sh-3.2$ xattr -l ~/Desktop/test.txt 
sh-3.2$ ./openmeta
openmeta version 0.1 by Tom Andersen code.google.com/p/openmeta/ 

Usage: openmeta [options] -p PATH[s] 

Note that commas are to be used nowhere - tag lists use quotes for two word tags in output

example (list tags and ratings):  openmeta -p PATH
example (list tags and ratings multiple):  openmeta -p PATH PATH
example (list tags): openmeta -t -p PATH[s]
example (add tags): openmeta -a foo bar -p PATH[s]
example (add tags with spaces): openmeta -a "three word tag" "foo bar" -p PATH[s]
example (set tags):  openmeta -s foo bar -p PATH[s]
example (clear all tags):  openmeta -s -p PATH[s]
example (set managed):  openmeta -m Y -p PATH[s]
example (set rating 0 - 5 stars):  openmeta -r 3.5 -p PATH[s]
example (print rating):  openmeta -r -p PATH[s]
example (clear rating):  openmeta -r 0.0 -p PATH[s]
example (lousy rating):  openmeta -r 0.1 -p PATH[s]
sh-3.2$ ./openmeta -a kerfuffle -p ~/Desktop/test.txt 
kerfuffle /Users/gjp22/Desktop/test.txt
sh-3.2$ ./openmeta -p ~/Desktop/test.txt 
/Users/gjp22/Desktop/test.txt
tags: kerfuffle
rating: none found

sh-3.2$ xattr -l ~/Desktop/test.txt 
com.apple.metadata:kMDItemOMUserTagTime:
00000000  62 70 6C 69 73 74 30 30 33 41 BB 64 BD 3C D4 95  |bplist003A.d.<..|
00000010  F2 08 00 00 00 00 00 00 01 01 00 00 00 00 00 00  |................|
00000020  00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000030  00 11                                            |..|
00000032
com.apple.metadata:kMDItemOMUserTags:
00000000  62 70 6C 69 73 74 30 30 A1 01 59 6B 65 72 66 75  |bplist00..Ykerfu|
00000010  66 66 6C 65 08 0A 00 00 00 00 00 00 01 01 00 00  |ffle............|
00000020  00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 14                                |......|
00000036
com.apple.metadata:_kMDItemUserTags:
00000000  62 70 6C 69 73 74 30 30 A1 01 5B 6B 65 72 66 75  |bplist00..[kerfu|
00000010  66 66 6C 65 0A 30 08 0A 00 00 00 00 00 00 01 01  |ffle.0..........|
00000020  00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 16                          |........|
00000038
kOM109SyncDone:
00000000  62 70 6C 69 73 74 30 30 09 08 00 00 00 00 00 00  |bplist00........|
00000010  01 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00 00 09                    |..........|
0000002a
sh-3.2$ 

Limitations of other utilities

Apple Finder, for example.

After using Finder to remove the kerfuffle tag, kerfuffle remains as an OpenMeta tag:

sh-3.2$ date ; xattr -l ~/Desktop/test.txt 
Sun 26 Jul 2015 08:02:13 BST
com.apple.metadata:kMDItemOMUserTagTime:
00000000  62 70 6C 69 73 74 30 30 33 41 BB 64 BD 3C D4 95  |bplist003A.d.<..|
00000010  F2 08 00 00 00 00 00 00 01 01 00 00 00 00 00 00  |................|
00000020  00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000030  00 11                                            |..|
00000032
com.apple.metadata:kMDItemOMUserTags:
00000000  62 70 6C 69 73 74 30 30 A1 01 59 6B 65 72 66 75  |bplist00..Ykerfu|
00000010  66 66 6C 65 08 0A 00 00 00 00 00 00 01 01 00 00  |ffle............|
00000020  00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 14                                |......|
00000036
com.apple.metadata:_kMDItemUserTags:
00000000  62 70 6C 69 73 74 30 30 A0 08 00 00 00 00 00 00  |bplist00........|
00000010  01 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00 00 09                    |..........|
0000002a
kOM109SyncDone:
00000000  62 70 6C 69 73 74 30 30 09 08 00 00 00 00 00 00  |bplist00........|
00000010  01 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00 00 09                    |..........|
0000002a
sh-3.2$ 

Understanding those limitations

With attention to domains and naming conventions: Developer thoughts on adopting OpenMeta – Ironic Software (2009-03, and now in the Internet Archive Wayback Machine) reminds us that com.apple.metadata was for use by Apple when OpenMeta (a project not in the apple.com domain) began the Apple-oriented com.apple.metadata:kMDItemOMUserTags approach.

So I should not expect Apple software to gain or maintain compatibility with both approaches to tagging.


Edge cases

In some cases it may be desirable to remove Apple-oriented com.apple.metadata:_kMDItemUserTags tags without removing OpenMeta-oriented com.apple.metadata:kMDItemOMUserTags tags.

However, doing so – programmatically – is probably beyond the scope of the question asked by @nacross.



回答9:

Starting with Mavericks, it is also possible to set colors tags in Cocoa, using NSAppleScript.

NSURL *fileURL = [NSURL fileURLWithPath:@"/Users/sheaparis/Documents/filezilla_sites.xml"];

//Format the filepath for the AppleScript environment.
//  Without this, the file cannot be found.
NSString *filepath = [fileURL path];
NSString *appleScriptFilePath = [filepath stringByReplacingOccurrencesOfString:@"/" withString:@":"];
if ([appleScriptFilePath hasPrefix:@":"]) {
    appleScriptFilePath = [appleScriptFilePath substringFromIndex:1];
}
NSLog(@"appleScriptFilePath: %@", appleScriptFilePath);

//Tells Finder to set the Red color tag for the specified file
NSString *sourceString = [NSString stringWithFormat:
                          @"set theFile to \"%@\" as alias\n"
                          "tell application \"Finder\" to set label index of theFile to 2", appleScriptFilePath];
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:sourceString];

NSDictionary *scriptErrorDict = nil;
[script executeAndReturnError:&scriptErrorDict];
if (scriptErrorDict) {
    NSLog(@"errorDict: %@", scriptErrorDict);
}

If this is executed on a file that only has one color, then it clears the current color, and sets the specified color. However, if multiple colors are already set on the file, then it does not clear the existing colors before setting the specified color.

Using the AppleScript environment, the color mapping is as follows:

  • 0: None
  • 1: Orange
  • 2: Red
  • 3: Yellow
  • 4: Blue
  • 5: Purple
  • 6: Green
  • 7: Grey


回答10:

Starting with Mavericks, it is possible to get and set color tags in Cocoa, using NSURL.

NSURL has a slew of properties that can be set or read, through the respective setResourceValue:forKey:error: and getResourceValue:forKey:error: methods.

Using the NSURLLabelNumberKey key, you can set the color tags, as follows:

NSURL *fileURL = [NSURL fileURLWithPath:@"/Users/[username]/Documents/[some_file]"];
NSError *resourceError;
if (![fileURL setResourceValue:@(2) forKey:NSURLLabelNumberKey error:&resourceError]) {
    NSLog(@"Error while setting file resource: %@", [resourceError localizedDescription]);
}

If this is executed on a file that only has one color, then it clears the current color, and sets the specified color. However, if multiple colors are already set on the file, then it does not clear the existing colors before setting the specified color.

Here is the value-color mapping (on El Capitan):

  • @(0): None
  • @(1): Grey
  • @(2): Green
  • @(3): Purple
  • @(4): Blue
  • @(5): Yellow
  • @(6): Red
  • @(7): Orange

I have not been able to set a tag using NSURLLabelColorKey. Here is my experience on El Capitan, with the keys related to 'tags' (Colors):

  • NSURLLabelNumberKey: can be read/set successfully, with numbers 0-7. Any other number will return an error. If there are multiple tags set, then this will return the index of the first color that is set, as it searches numerically through the indexes 1 through 7. Although you can clear a color in Finder by clicking on the color, programmatically setting a color that is already set does not clear that color.
  • NSURLLabelColorKey: returns nil, even when a color tag is set for a file. Setting a value with this key has no effect.
  • NSURLTagNamesKey: returns an array of the color names for the tags that are set.