One of the core components of Mac OS X is launchd, and it turns out it can do some cool things.

I particularly like the idea of using QueueDirectories to monitor and act upon files dropped into a directory, without having to run any extra daemons. The files could be uploaded to S3, transcoded to a different video format, gzipped… anything.

Anyway, I recently fell into the launchd documentation, and came out with this write-up. Let me know if you find it useful.


The first thing that the Mac OS kernel runs on boot is launchd, which bootstraps the rest of the system by loading and managing various daemons, agents, scripts and other processes. The launchd man page clarifies the difference between a daemon and an agent:

In the launchd lexicon, a “daemon” is, by definition, a system-wide service of which there is one instance for all clients. An “agent” is a service that runs on a per-user basis. Daemons should not attempt to display UI or interact directly with a user’s login session. Any and all work that involves interacting with a user should be done through agents.

Daemons and agents are declared and configured by creating .plist files in various locations of the system:

~/Library/LaunchAgents         Per-user agents provided by the user.
/Library/LaunchAgents          Per-user agents provided by the administrator.
/Library/LaunchDaemons         System-wide daemons provided by the administrator.
/System/Library/LaunchAgents   Per-user agents provided by OS X.
/System/Library/LaunchDaemons  System-wide daemons provided by OS X.

Perhaps best of all, launchd is open source under the Apache License 2.0. You can currently find the latest source code on the Apple Open Source site.

launchd as cron

The Mac OS crontab man page says:

Although cron(8) and crontab(5) are officially supported under Darwin,
their functionality has been absorbed into launchd(8), which provides a
more flexible way of automatically executing commands.

Turns out launchd has a simple StartInterval <integer> property, which starts the job every N seconds. However the true cron-like power lies in StartCalendarInterval:

StartCalendarInterval <dictionary of integers or array of dictionary of integers>

This optional key causes the job to be started every calendar interval as
specified. Missing arguments are considered to be wildcard. The semantics
are much like crontab(5).  Unlike cron which skips job invocations when the
computer is asleep, launchd will start the job the next time the computer
wakes up.  If multiple intervals transpire before the computer is woken,
those events will be coalesced into one event upon wake from sleep.

     Minute <integer>
     The minute on which this job will be run.

     Hour <integer>
     The hour on which this job will be run.

     Day <integer>
     The day on which this job will be run.

     Weekday <integer>
     The weekday on which this job will be run (0 and 7 are Sunday).

     Month <integer>
     The month on which this job will be run.

Lets find the shortest example of this in action:

pda@paulbook ~ > grep -rl StartCalendarInterval \
                   /Library/Launch* /System/Library/Launch* | \
                   xargs wc -l | sort -n | head -n1 | awk '{print $2}' | xargs cat

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

Better than cron? Apart from better handling of skipped jobs after system wake, it also supports per-job environment variables, which can save writing wrapper scripts around your cron jobs:

EnvironmentVariables <dictionary of strings>

This optional key is used to specify additional environmental variables to
be set before running the job.

So, anything XML is obviously worse than 0 52 3 * 5 /path/to/command, but launchd is packing more features than cron, so it can pull it off.

launchd as a filesystem watcher

Apart from having an awesome daemon/agent manager, Mac OS X also has an excellent Mail Transport Agent called postfix. There’s a good chance your ISP runs the same software to handle millions of emails every day. We’ll be using it as an example of how launchd can start jobs based on filesystem changes.

Because your laptop isn’t, and shouldn’t be, a mail server, you don’t want postfix running all the time. But when messages are injected into it, e.g. by a script shelling out to /usr/sbin/sendmail or /usr/bin/mail, you want them to be delivered straight away.

Here’s how Mac OS X does it (/System/Library/LaunchDaemons/org.postfix.master.plist):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "">
<plist version="1.0">

We’ll start with the simple part. ProgramArguments passes -e 60 to postfix, described thusly:

-e exit_time
              Terminate the master process after exit_time seconds.
              Child processes terminate at their convenience.

So postfix is told to exit after running for 60 seconds. The mystery (to me, earlier today, at least) is how it gets started. It could be on a cron-like schedule, but (a) it isn’t, (b) that would suck, and (c) it would result in delayed mail delivery. It turns out the magic lies in QueueDirectory, which I initially overlooked thinking it was a postfix option. The launchd.plist man page says:

WatchPaths <array of strings>
This optional key causes the job to be started if any one of the listed
paths are modified.

QueueDirectories <array of strings>
Much like the WatchPaths option, this key will watch the paths for
modifications. The difference being that the job will only be started if
the path is a directory and the directory is not empty.

The Launchd Wikipedia page actually goes into more detail:

Watch a directory for new files. The directory must be empty to begin with,
and must be returned to an empty state before QueueDirectories will launch
its task again.

So launchd can monitor a directory for new files, and then trigger an agent/daemon to consume them. In this case, the postfix sendmail(1) man page tells us that “Postfix sendmail(1) relies on the postdrop(1) command to create a queue file in the maildrop directory”, and the man page for postdrop(1) tells us that /var/spool/postfix/maildrop is the maildrop queue. launchd sees new mail there, fires up postfix, and then stops it after 60 seconds. This might cause deferred mail to stay deferred for quite some time, but again; your laptop isn’t a mail server.

launchd as inetd

Tranditionally the inetd and later xinetd “super-server daemon” were used to listen on various ports (e.g. FTP, telnet, …) and launch daemons on-demand to handle in-bound connection, keeping them out of memory at other times. Sounds like something launchd could do…

Lets create a simple inetd-style server at ~/Library/LaunchAgents/my.greeter.plist:

<plist version="1.0">
    <string>puts "Hi #{gets.match(/(\w+)\W*\z/)[1]}, happy #{"%A")}!"</string>

Load it up and give it a shot:

pda@paulbook ~ > launchctl load ~/Library/LaunchAgents/my.greeter.plist
pda@paulbook ~ > echo "My name is Paul." | nc localhost 13117
Hi Paul, happy Friday!

launchd as god!

You can use launchd to ensure a process stays alive forever using <key>KeepAlive</key><true/>, or stays alive under the following conditions.

  • SuccessfulExit — the previous run exited successfully (or if false, unsuccessful exit).
  • NetworkState — network (other than localhost) is up (or if false, down).
  • PathState — list of file paths exists (or if false, do not exist).
  • OtherJobEnabled — the other named job is enabled (or if false, disabled).

These can be combined with various other properties, for example:

  • WorkingDirectory
  • EnvironmentVariables
  • Umask
  • ThrottleInterval
  • StartOnMount
  • StandardInPath
  • StandardOutPath
  • StandardErrorPath
  • SoftResourceLimits and HardResourceLimits
  • Nice


There’s some more information at, and the launchd and launchd.plist man pages are worth reading.



/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/include/ruby-2.0.0/ruby/ruby.h:24:10: fatal error: 'ruby/config.h' file not found
#include "ruby/config.h"
1 error generated.
make: *** [generator.o] Error 1


sudo cp -rf /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/include/ruby-2.0.0/universal-darwin15/ruby /usr/local/include/

PS: /System/Library 目录是有系统保护,禁止修改的,因此拷贝到/usr/local/include即可。

在mac os x系统编译php时,在configure阶段就报如下错误:

error: Don’t know how to define struct flock on this system, set –enable-opcache=no


sudo cp /opt/mysql/lib/libmysqlclient.* /usr/local/lib/libmysqlclient.*






系统管理-> System Log->日志级别(在左边栏)


Name: javax.jmdns

Level: off


openssl rsa -in -out

 ssl on; 
 ssl_certificate cert/;
 ssl_certificate_key cert/;



server {
	listen	  80;
	return	  301 https://$server_name$request_uri;

server {
	listen	  443;




sqlite3 ~/Library/Keychains/*/ocspcache.sqlite3 ‘DELETE FROM responses WHERE responderURI LIKE “tp://”;’

升级xcode 后 Qt 出问题了,google 找到了解决方法。

~> Xcode 8

This problem occurs when command line tools are installed after Xcode is installed. What happens is the Xcode-select developer directory gets pointed to /Library/Developer/CommandLineTools.

Step 1:

Point Xcode-select to the correct Xcode Developer directory with the command:

sudo xcode-select -switch /Applications/

Step 2:

Confirm the license agreement with the command:

xcodebuild -license

This will prompt you to read through the license agreement. 

Enter agree to accept the terms.

>= Xcode 8

Step 1:

As Bruce said, this happens when Qt tries to find xcrun when it should be looking for xcodebuild.

Open the file:


Step 2:


isEmpty($$list($$system(“/usr/bin/xcrun -find xcrun 2>/dev/null”))))


isEmpty($$list($$system(“/usr/bin/xcrun -find xcodebuild 2>/dev/null”)))


安装macOS Sierra后,会发现系统偏好设置的“安全与隐私”中默认已经去除了允许“任何来源”App的选项,无法运行一些第三方应用。


  1. sudo spctl –master-disable



需要说明的是,如果在系统偏好设置的“安全与隐私”中重新选中允许App Store 和被认可的开发者App,即重新打开Gatekeeper后,允许“任何来源”App的选项会再次消失,可运行上述命令再次关闭Gatekeeper。

MYSQL 5.5 之前, UTF8 编码只支持1-3个字节,只支持BMP这部分的unicode编码区, BMP是从哪到哪,到这里看,基本就是0000~FFFF这一区。 从MYSQL5.5开始,可支持4个字节UTF编码utf8mb4,一个字符最多能有4字节,所以能支持更多的字符集。

utf8mb4 is a superset of utf8





一 什么是Emoji

1.iphone、ipad系统:安装emoji free,再设置-通用-键盘-国际键盘-添加新的键盘,然后把emoji添加在里面即可在发短信和一些输入文本的文本框中输入表情。
IOS 5用户可直接从通用中添加emoji 键盘,无需再安装emoji free
3.Windows Phone : 安装此 Emoji Keys,在其中输入之后复制粘贴到需要输入表情的地方即可


二 Emoji表情符号问题
1 问题:
2 现象:
IOS 4 输入Emoji表情符,在IOS5.01 显示正常,在IOS5.1中(大陆版)显现为方块, 但IOS5.01/5.1输入的表情符号,显示正      常
IOS5.01/5.1 输入表情符,在IOS5.01/5.1中显示正常,但在IOS4.X显示为方块
输入Emoji入帖子正文, 可正常存储。 但用户昵称在IOS4.X 输入Emoji,系统正常, 而IOS5.01/5.1则提示系统错误。
3 本质:
iOS 5 and OS X 10.7 (Lion) use the Unicode 6.0 standard ‘unified’ code points for emoji.
iOS 5 Emoji  采用Unicode 6 标准来统一code points

iOS 4 on SoftBank iPhones used a set of unofficial code points in the Unicode Private Use Area, and so aren’t      compatible with any other systems
iOS 4 采用SoftBank Unicode, 一种非官方的, 采用私有Unicode 区域。
4 举例:
one emoji symbol “tiger”, it is “\U0001f42f” in iOS5, but “\ue050” in earlier iOS version
虎脸Emoji符号在iOS5 为Unicode:\U0001f42f;而在IOS4.x 为:\ue050 (SoftBank 编码)
另外: 按理讲, 从iOS5 应该兼容以前版本的emoji, 但现在出现5.01版本完美兼容(无论大陆版,美版,还是港版), 而5.1     大陆版出现了不兼容现象(腾讯微信也出现了同样的问题)。
三 问题分析
1 系统存储错误问题(如昵称,帖子内容)
由于IOS5.X 采用新的Unicode, 其UTF8 编码大多为4个字节, 而由于昵称/帖子内容column并没设成utf8mb4,因此存储会    发生错误。
2 不同iOS 之间Emoji 不兼容的问题。
iOS 5 到4 不兼容的问题,很简单,unicode6 和softbank编码的不同
iOS 4 到 5,按理说应该兼容,也就是说,iOS应该自动判断如果是softbank编码,自动转成unicode6。但现在看来, iOS5.1(大陆版)好像只支持unicode6, 而不支持softbank.
客户端发送emoji-encoding: Softbank或unicode6, 由服务端分别给出相应的编码表。
四 解决方案
1 数据存储(MySQL varchar  数据类型对UTF8 支持问题)
MYSQL 5.5 之前, UTF8 编码只支持1-3个字节, 从MYSQL5.5开始,可支持4个字节UTF编码,但要特殊标记。例如我们的帖子内容项,我们加上了这个支持。服务端mysql统一存储为ios5.x也就是Unicode编码。

[sql]  view plain copy

  1. ALTER TABLE topic MODIFY COLUMN content varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘内容’;

2 编码转换:
五 部分代码
1 sql代码

[sql]  view plain copy

  1. CREATE TABLE `ios_emoji` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘自增ID’,
  3.   `unicode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘Unicode编码’,
  4.   `utf8` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘UTF8编码’,
  5.   `utf16` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘UTF16编码’,
  6.   `sbunicode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘SBUnicode编码’,
  7.   `filename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘文件名’,
  8.   `filebyte` longblob COMMENT ‘文件内容字节’,
  9.   PRIMARY KEY (`id`)

2 java代码

[java]  view plain copy

  1. import;
  2. import org.apache.commons.lang.StringUtils;
  3. public class IOSEmojiUtil {
  4.     public static String[] ios5emoji ;
  5.     public static String[] ios4emoji ;
  6.     public static String[] androidnullemoji ;
  7.     public static String[] adsbuniemoji;
  8.     public static void initios5emoji(String[] i5emj,String[] i4emj,String[] adnullemoji,String[] adsbemoji){
  9.         ios5emoji = i5emj;
  10.         ios4emoji = i4emj;
  11.         androidnullemoji = adnullemoji;
  12.         adsbuniemoji = adsbemoji;
  13.     }
  14.     //在ios上将ios5转换为ios4编码
  15.     public static String transToIOS4emoji(String src) {
  16.         return StringUtils.replaceEach(src, ios5emoji, ios4emoji);
  17.     }
  18.     //在ios上将ios4转换为ios5编码
  19.     public static String transToIOS5emoji(String src) {
  20.         return StringUtils.replaceEach(src, ios4emoji, ios5emoji);
  21.     }
  22.     //在android上将ios5的表情符替换为空
  23.     public static String transToAndroidemojiNull(String src) {
  24.         return StringUtils.replaceEach(src, ios5emoji, androidnullemoji);
  25.     }
  26.     //在android上将ios5的表情符替换为SBUNICODE
  27.     public static String transToAndroidemojiSB(String src) {
  28.         return StringUtils.replaceEach(src, ios5emoji, adsbuniemoji);
  29.     }
  30.     //在android上将SBUNICODE的表情符替换为ios5
  31.     public static String transSBToIOS5emoji(String src) {
  32.         return StringUtils.replaceEach(src, adsbuniemoji, ios5emoji);
  33.     }
  34.     //eg. param: 0xF0 0x9F 0x8F 0x80
  35.     public static String hexstr2String(String hexstr) throws UnsupportedEncodingException{
  36.         byte[] b = hexstr2bytes(hexstr);
  37.         return new String(b, “UTF-8”);
  38.     }
  39.     //eg. param: E018
  40.     public static String sbunicode2utfString(String sbhexstr) throws UnsupportedEncodingException{
  41.         byte[] b = sbunicode2utfbytes(sbhexstr);
  42.         return new String(b, “UTF-8”);
  43.     }
  44.     //eg. param: 0xF0 0x9F 0x8F 0x80
  45.     public static byte[] hexstr2bytes(String hexstr){
  46.         String[] hexstrs = hexstr.split(” “);
  47.         byte[] b = new byte[hexstrs.length];
  48.         for(int i=0;i<hexstrs.length;i++){
  49.             b[i] = hexStringToByte(hexstrs[i].substring(2))[0];
  50.         }
  51.         return b;
  52.     }
  53.     //eg. param: E018
  54.     public static byte[] sbunicode2utfbytes(String sbhexstr) throws UnsupportedEncodingException{
  55.         int inthex = Integer.parseInt(sbhexstr, 16);
  56.         char[] schar = {(char)inthex};
  57.         byte[] b = (new String(schar)).getBytes(“UTF-8”);
  58.         return b;
  59.     }
  60.     public static byte[] hexStringToByte(String hex) {
  61.         int len = (hex.length() / 2);
  62.         byte[] result = new byte[len];
  63.         char[] achar = hex.toCharArray();
  64.         for (int i = 0; i < len; i++) {
  65.             int pos = i * 2;
  66.             result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
  67.         }
  68.         return result;
  69.     }
  70.     private static byte toByte(char c) {
  71.         byte b = (byte) “0123456789ABCDEF”.indexOf(c);
  72.         return b;
  73.     }
  74.     public static void main(String[] args) throws UnsupportedEncodingException {
  75.         // TODO Auto-generated method stub
  76.         byte[] b1 = {-30,-102,-67}; //ios5 //0xE2 0x9A 0xBD
  77.         byte[] b2 = {-18,-128,-104}; //ios4 //”E018″
  78.         //————————————-
  79.         byte[] b3 = {-16,-97,-113,-128};    //0xF0 0x9F 0x8F 0x80
  80.         byte[] b4 = {-18,-112,-86};         //E42A
  81.         ios5emoji = new String[]{new String(b1,”utf-8″),new String(b3,”utf-8″)};
  82.         ios4emoji = new String[]{new String(b2,”utf-8″),new String(b4,”utf-8″)};
  83.         //测试字符串
  84.         byte[] testbytes = {105,111,115,-30,-102,-67,32,36,-18,-128,-104,32,36,-16,-97,-113,-128,32,36,-18,-112,-86};
  85.         String tmpstr = new String(testbytes,”utf-8″);
  86.         System.out.println(tmpstr);
  87.         //转成ios4的表情
  88.         String ios4str = transToIOS5emoji(tmpstr);
  89.         byte[] tmp = ios4str.getBytes();
  90.         //System.out.print(new String(tmp,”utf-8″));
  91.         for(byte b:tmp){
  92.             System.out.print(b);
  93.             System.out.print(” “);
  94.         }
  95.     }
  96. }

六 参考资料
1 Emoji 全编码表:(我参考的这个)
2 Emoji全编码表

3 iOS5/4 Emoji  兼容性:
4 MySQL emoji问题
5 Emoji 中文对应表

七 下载资源 





How to support full Unicode in MySQL databases