记录 MacOS 的使用日常记录

前言

此篇博文是笔者所总结的 Macos 系列之一;

本文为作者的原创作品,转载需注明出处;

find port and kill

经常我们需要有在命令行执行 kill 的场景,比如,莫名其妙的某个端口被占用了
比如,本机的 4000 端口被某个不知道的进程所占用,这个时候,如何找到并将其 kill 掉呢?

  1. 使用 netstat

    1
    2
    3
    4
    5
    6
    $ netstat -vanp tcp | grep 4000
    tcp4 645 0 127.0.0.1.4000 127.0.0.1.57534 ESTABLISHED 408300 146988 37177 0
    tcp4 0 0 127.0.0.1.57534 127.0.0.1.4000 ESTABLISHED 408300 146988 759 0
    tcp4 664 0 127.0.0.1.4000 127.0.0.1.57533 CLOSE_WAIT 408300 146988 37177 0
    tcp4 0 0 127.0.0.1.57533 127.0.0.1.4000 FIN_WAIT_2 408300 146988 759 0
    tcp4 0 0 *.4000 *.* LISTEN 131072 131072 37177 0

    这样,通过上述的命令便知道了,4000 端口是被 37177 这个进程占用了,只需要将其 kill 即可

    1
    $ kill -9 37177

    https://stackoverflow.com/questions/3855127/find-and-kill-process-locking-port-3000-on-mac

  2. 使用 lsof

    1
    lsof -i:80

    -i参数表示网络链接,:80指明端口号,该命令会同时列出PID,方便kill

按键

常用按键

⌘ —— Command ()
⌃ —— Control
⌥ —— Option (alt)
⇧ —— Shift
⇪ —— Caps Lock
fn—— 功能键就是fn

自定义某个应用的快捷键

今天在使用 Note 做编辑的时候,发现其它的快捷键都有,就是没有 Strikethrough 的快捷键,而我有大量的行需要使用到 Strikethrough,造成很大的不变,因此,我需要自定义 Note 的快捷键,

how self define app shortcuts.png

  1. 选择 App Shortcuts
  2. 选择你要设置的应用,这里我选择 Note.app
  3. 选择你要设置的功能的名称,一定要和你的 app 中所定义的功能名称对应,这里我需要设置 Strikethrough 的快捷键,自然输入的就是 Strikethrough

Command 中切换用户

切换至 ROOT 用户

1
$ sudo -i

切换回普通用户

1
$ su - mac

JVM

安装地址

/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home

备注,只要是通过官网默认的 dmg 安装包进行安装的,就会默认安装到这个目录中;

修改环境变量

必须同时修改 ~/.bash_profile 和 ~/.profile 两个文件,这样,重新看起一个终端的时候,才会使用新的 jvm。

小技巧

我的电脑型号

2014 款 macbook,

2014 rMBP机身重量为3.46磅,屏幕分辨率为2,560 x1,600,像素密度为227ppi。设备上的接口也没有变化,左边分别是MagSafe 2充电口、两个Thunderbolt 2接口、一个USB 3.0接口、3.5mm耳机接口以及两个麦克风孔;右边也有一个USB 3.0接口、一个HDMI输出口以及全尺寸SD卡槽

可见, Thunderbolt 2 以及 Thunderbolt 1 使用的接口型号是 Mini DP,所以,可以认为,两个其实就是同一个东西。

外接 4K 显示器

http://bbs.itools.cn/thread-98257-1-1.html

苹果在2013年开始使用2代雷电技术,其支持DisplayPort 1.2(17.28Gbps)。也就是说:late 2013或者更新的mac使用的是2代雷电接口,并且能支持1个在60Hz的4k显示器。如果有独显,那就是同时输出多个了(最主要还是看你显卡)。

从作者的描述中可以看到,只要是使用 2 代的 thundbolt,转成 DP 1.2,就可以支持 60Hz 的 4K 输出。可以试试。

DNS 检查

今天遇到一个奇怪的事情,HRX 官网地址之前映射在 119.23.xxx.xx UAT 地址上,可是后来,将域名映射为 39.108.xx.xxx PROD 地址上以后,域名服务提供商确认映射 OK 了,可是我本地的域名映射却始终指向的是 UAT 的地址,也通过 Flushing your DNS cache in Mac OS X and Linux 的步骤清理掉本机上的 DNS 缓存,可是还是不行.. 但是在远方的同事(不在同一个局域网)却能够正确的解析到 PROD 地址上;纳里,这时怎么回事,看来必须得对 OSX 的域名解析了解下了

1
2
3
4
5
6
7
$ nslookup www.hrx.com
Server: 192.168.31.1
Address: 192.168.31.1#53

Non-authoritative answer:
Name: www.hrx.com
Address: 119.23.xxx.xx

可以看到,hrx 的域名的确还是被映射到的 UAT 的地址,可是,问题是,域名解析在 DNS 服务商那里改变了呀…

注意这行Server: 192.168.31.1,192.168.31.1 似曾相似,这不就是我的小米路由器嘛.. Ok,顿时焕然大悟,原来,我本机现在使用的是路由器进行的 DNS 解析,所以,要做的不是去 flush 本机上的 DNS cache,而是应该去 Flush 小米路由器上的 DNS 缓存才能生效;Ok,这下搞明白了..

另外,可以直接通过查看 /etc/resolv.conf 查看本机 DNS 解析器,

1
2
3
4
5
6
7
8
9
10
11
$ cat /etc/resolv.conf 
\#
\# Mac OS X Notice
\#
\# This file is not used by the host name and address resolution
\# or the DNS query routing mechanisms used by most processes on
\# this Mac OS X system.
\#
\# This file is automatically generated.
\#
nameserver 192.168.31.1

192.168.31.1 既是 DNS Server…

Chrome 对应的快捷键

command + R: 刷新
command + shift + R: 强制刷新

查看隐藏文件

如果要查看隐藏文件,在控制终端输入如下的命令,

1
$ defaults write com.apple.finder AppleShowAllFiles TRUE

不过,这个时候,如果文件夹是打开的或者是激活的状态,是不会立即生效的,执行如下命令,

1
$ killall Finder

将当前的所有 Finder 杀死,然后启动 Finder,则可以看到所有的隐藏文件了;如果要不显示隐藏文件,输入如下命令即可,

1
$ defaults write com.apple.finder AppleShowAllFiles False

Reference: https://www.howtogeek.com/211496/how-to-hide-files-and-view-hidden-files-on-mac-os-x/

什么时候买合适

一个专门的网站告诉你什么是入手新的苹果是最佳的时机?

https://buyersguide.macrumors.com/

iTerms

下载

iTerms 是取代 macos 默认的 Terminal 的利器,下载地址,https://www.iterm2.com/downloads.html

配色方案

默认启动的时候,iTerms 是没有任何配色方案的,通过下面的步骤,启动 iTerms 的配色方案,

  1. 修改 _bash_profile_

    1
    > vim ~/.bash_profile

    添加如下的内容,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #enables colorin the terminal bash shell export
    export CLICOLOR=1

    #setsup thecolor scheme for list export
    export LSCOLORS=gxfxcxdxbxegedabagacad

    #sets up theprompt color (currently a green similar to linux terminal)
    #export PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;36m\]\w\[\033[00m\]\$ '
    #enables colorfor iTerm
    export TERM=xterm-256color
  2. 添加 .vimrc

    1
    2
    3
    syntax on
    set number
    set ruler

    这一步的目的是,在使用 vim 编辑器的时候,会显示配色方案;

  3. 重新加载 .bash_profile 激活
    1
    > source ~/.bash_profile

这样,当你再次输入命令的时候,配色方案就生效了,如下图所示,

选择不同的背景

iTerm2 -> Preferences -> Profiles -> Colors -> Color Presets:

导入其它的背景

iTerm2 -> Preferences -> Profiles -> Colors -> Color Presets -> Imports

Github 上有许多的背景,比如 Peppermint: https://github.com/dotzero/iTerm-2-Peppermint ,比如官网上的 http://iterm2colorschemes.com/

字体调整

修复

重启后,远程登录设置被重置

每次重新启动以后,远程登录选项的设置都被重置,被关闭;这对于使用 macos 作为服务器的用户来说,是个噩梦,想想,一旦服务器自动重启后,却无法通过 ssh 远程登录,是何感想;这个问题非常的扰人,就我的 imac 出现这个问题,自己的 macbook 没有这个问题,可见,这个并非是苹果的特殊设置;关于这个问题讨论得非常的少,唯一的就是这篇 https://discussions.apple.com/thread/7147813 文章从头至尾的在讨论,有人通过下面的方法解决了,

“I renamed each original .plist file to “.plistOLD,” then dragged the edited .plist to its appropriate folder. Then, I used the latest beta of BatChmod (1.7 beta 5 at this writing) to change the ownership of each edited .plist to “root,” gave “root” and the “Administrators” group Read/Write permission, and the Everyone group Read (only) permission. After a restart, the File Sharing and Screen Sharing checkboxes remained checked.”

这个牛人是按个查找系统里面的所有的 .plist 文件,然后将它的权限赋给 Root,同时使得 “Administrators” group 用户读写权限,然后,不断的重启试验,然后在某一次,重启后,他成功了;不过这给了我启发,一定是某个重要文件的权限出了问题,导致系统重启后,相关账户,Root 或者 Administor 用户没有权限设置系统为之前的状态,因此导致了这个状态设置(远程登录状态设置)在每次重启的时候丢失了;

回想了下,自己很有可能就是在安装 Homebrew 的时候,听信了网上设置 /usr/local 权限的解决办法,执行了下面的语句,

1
sudo chown -R $(whoami) $(brew --prefix)/*

执行了之后,该命令很猛,扫描了系统的所有文件,然后将所有的系统文件的所有权赋值给了当前用户,God,一定是这样导致的;上面的解决办法出自 https://github.com/Homebrew/brew/issues/3228 但是他所解决的问题是,之前一定要先安装好了 Homebrew 的用户才能使用的方法,它是去找所有与 brew 相关的文件,然后添加用户权限;但是如果,现在你是一个并没有安装 Homebrew 的用户呢?想想它会返回什么?对的是, /*,因此,就是这一行命令,让我把系统文件所有的权限都给改了;天煞的……

所以,如果上面的问题不解决,那么还会有更多的奇奇怪怪的问题出现,因此,只能抹盘重装系统了;记得一定要抹盘后安装;

最后,经过个把小时的系统重装,设置远程登录后再次重启,重启后,远程登录的状态便不再丢失了~~~

急救

忘记用户名密码?

重装系统忘记了管理员用户密码,怎么办?进入 root 命令环境,删除用户 db 设置,重启,重新设置管理员用户;

  1. 重启,一直按着 COMMAND + S 不松手,直到出现 root 命令行
  2. 然后依次执行如下命令,
    1
    2
    3
    4
    5
    fsck -y
    mount -uaw /
    # 删除设置数据库
    rm /var/db/.AppleSetupDone
    reboot

这样,重启以后就会重新进入用户设置页面了;

/etc/sudoers is owned by uid 501, should be 0

这个错误直接导致 sudo 不能使用,同样需要重启系统进入 root command 环境,然后对 /etc/sudoers 文件的权限进行修复;步骤如下

  1. 重启,一直按着 COMMAND + S 不松手,直到出现 root 命令行
  2. 然后依次执行如下命令进行修复

    1
    2
    3
    4
    5
    fsck -fy
    mount -uw /
    chown root:wheel /etc/sudoers
    chmod 440 /etc/sudoers
    reboot

    默认情况下,/etc/sudoers 必须属于 root,并且必须属于 wheel 工作组,权限也必须为 440,恢复 /etc/sudoers 的默认设置;重启以后,sudo 正常工作;

cron and crontab

本章节主要描述,在 macos 系统下如何创建 cron job;

概念

首先要明确两个概念,cron 和 crontab,

  1. cron
    cron 准时启动由 crontab 所定义的命令;cron 是通过 launchd 随开机自启动而启动的,无需手动启动,cron 会每隔 1 分钟检查 crontab 的状态,看是否有新增的 crontab job,如果有,则加载进内存,并且按照 crontab 所定义的时间执行相应的 job;所以,总结起来就是,只要 crontab 将 job 定义好了以后,cron 便会自动的将这些 job 加载并在规定时刻执行,无需我们队 cron 进行任何管理;
  2. crontab
    该命令定义由 cron 执行的 job;那么如何定义,参考 crontab 小节;

crontab

时间定义

触发时间定义

在定义 cron job 的时候,首先要定义的是 job 执行的时刻,看一个最简单的例子

1
0 0,12 1 2 * /sbin/ping -c 192.168.0.1 >> ~/tmp/ping.out

可见,最开始对应了 5 个参数,分别是 00,1212*,那么这 5 个参数按照先后顺序依次对应如下的 field,

1
2
3
4
5
6
7
field         allowed values
----- --------------
minute 0-59
hour 0-23
day of month 1-31
month 1-12 (or names, see below)
day of week 0-7 (0 or 7 is Sun, or use names)

所以,上面所定义的 cron job 的意思就是说,在每年的 2 月份的第 1 天,分别在凌晨 12 点和中午 12 点的 0 分的时候开始 ping,并将 ping 的结果输出到日志中;上面使用到了两个特定的符号,一个是 *,表示的是任意值,一个是,,表示的是数组,上面的参数 0,12 表示的就是由值 0 和 12 所构成的数组,在时间表达式中表示分别在这两个时间执行;

四个特殊的符号

cronjob 总共有四个特殊的符号,摘自 https://crontab.guru/

1
2
3
4
5
6
opr     description
--- ------------
* any value
, value list separator
- range of values
/ step values
  1. * any value
    这个没有什么好多说的,任意值,换句话说,取值范围之内的值都符合;
  2. , value list separator
    如果 month 设置为 *,将 day 的表达式写成 2,10,表示分别在每个月的第 2 天或第 10 天执行;
  3. - range of values 范围表达式符号
    如果 #2 的表达式改成-2-10,则表示从每个月的第 2 天开始直到第 10 天结束,每天都执行;
  4. / step values 不仅表达式
    这个是最不好理解的,/ 后接数字,既步长,表示递进多少步长取值,举例,以 hour 定义为例,表达式 0-23/2 的实际取值转换为 value list speparator 等价于 0,2,4,6,8,10,12,14,16,18,20,22,上面的意思就是每个两小时执行一次,因此步长是 2 小时;上面的表达式也可以写成 */2,两者等价;当然,如果你想说,我想在规定的时间段内,每个两小时执行一次,可以写成 0-12/2,这就表示从凌晨 12 点开始直到中午 12 点,每个两小时执行一次;不过要特别特别注意的是,/ 默认在起始值要执行一次,比如 0-23/2,在 0 点要执行一次;

@reboot

该命令只在系统重启后执行一次,且只执行一次;该命令非常有用,比如一些需要在系统启动后初始化执行的 bash command 就可以在这里定义了;看一个最简单的例子,

1
@reboot /bin/echo "computer started!" >> ~/tmp/start.out`

那么意思就是说,在每次开机的时候执行 ping 并输出到日志文件中,并且仅执行一次;

一些复杂的例子

1
0 0,12 1 */2 * /sbin/ping -c 192.168.0.1 >> ~/tmp/ping.out

表示,在每间隔 1 个月(步长 2)的第 1 天的早上 12:00am 和中午 12:00pm 的时候执行一次;

创建和编辑

使用 crontab -e

要创建 crontab 很简单,直接使用命令 crontab -e 并输入命令即可;

小记,自己在试验的时候,直接使用 crontab -e 并没有初始化创建成功,crontab -e 默认使用的是 vim 编辑器,而是使用了 nano 编辑器才成功,

1
env EDITOR=nano crontab -e

编辑好以后,使用 CTRL + T 存储内容,CTRL + X 退出即可;但是,当初始化成功以后,就可以使用 crontab -e 的 vim 编辑器进行编辑和保存了;

为了测试方便,可以首先创建一个每分钟都会执行一次的命令,输入如下内容,

1
* * * * * /sbin/ping www.baidu.com >> ~/tmp/ping.out

保存,即可;这里尤其要注意的是,cron 并不会读取你的 profile 和系统中所定义的环境变量,因此,你需要手动的指定命令的位置;为了减少复杂性,可以直接在 crontab 命令的开始处中添加如下的内容导入环境变量,然后上面的测试用例就可以写成如下的方式;

1
2
3
#!/bin/sh
PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:~/bin
* * * * * ping www.baidu.com >> ~/tmp/ping.out

使用 bash

1
2
export JOB_MINUTES=4
{ crontab -l ; echo $JOB_MINUTES * * * * /tmp/my_script.sh; } | crontab -

- 表示使用 crontab 使用标注输入,也就是左侧管道的返回值;这样,就可以动态的定制 crontab 了;

references: https://stackoverflow.com/questions/47084266/dynamically-setting-time-of-cron-job-based-off-of-system-variables

https://askubuntu.com/questions/880052/how-can-i-change-crontab-dynamically

查看

查看命令是否创建或编辑成功,

1
2
$ crontab -l
* * * * * /sbin/ping www.baidu.com >> ~/tmp/cron.out

如果输出了刚才所创建的命令,表示创建成功!

作用范围

cron job 只对当前用户有效,cron job 一旦创建,该 job 的 owner 是当前用户,只有当前用户或者 root 才有权限执行,并且 bash 的相对路径是当前用户,命令中可以使用 $HOME 或者 ~ 指向用户的根目录;

何时启动

cron job 和 launchctl 不同,launchctl 要求必须是在用户通过 GUI logged in 以后(称作 login session )才能执行,而 cron job 不受这个限制,即便是用户不登录一样可以执行;所以,它的命令只要系统启动,便可以执行,也可以理解为它的脚本是在 Start Up Session 开始执行的;

为了印证上述的论断,笔者做了一个小小的测试,在不登录当前用户的情况下,执行如下的 crontab

1
2
3
#!/bin/sh
PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:~/bin
* * * * * echo "hello!" >> ~/tmp/ping.out

每分钟像 ping.out 中写入一个字符串 “hello!”,10 分钟以后登录,发现 ping.out 中已经写满了 10 个 “hello!”;所以可以断定,cron 的任何 job 都不需要登录后执行

https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/Introduction.html 介绍了苹果启动执行的生命周期;

reference

Specification: https://www.pantz.org/software/cron/croninfo.html

bash

使用 Terminal 创建、删除用户

当使用 macos 作为服务器的时候,难免会使用到命令行的方式来创建用户,创建用户的方式有两种,一种是普通用户,一种是 admin 用户;有两种流行的方式来创建,一种是使用 dscl 命令,一种是使用 sysadminctl 命令;

  • dscl 命令非常的细,细到可以设置用户的 UniqueID 等等,设置非常的繁琐,而且这些信息往往都是我们不关注的,相关内容查看 https://apple.stackexchange.com/questions/226073/how-do-i-create-user-accounts-from-the-terminal-in-mac-os-x-10-11
  • sysadminctl 命令就简单了许多了,封装了非常多不必要的复杂性,从它的帮助文档中可以看到,
    1
    2
    3
    4
    5
    6
    $ sysadminctl help
    -adminUser <admin user name> -adminPassword <admin user password>
    -deleteUser <user name> [-secure || -keepHome] (interactive || -adminUser <administrator user name> -adminPassword <administrator password>)
    -newPassword <new password> -oldPassword <old password> [-passwordHint <password hint>]
    -resetPasswordFor <local user name> -newPassword <new password> [-passwordHint <password hint>] (interactive] || -adminUser <administrator user name> -adminPassword <administrator password>)
    -addUser <user name> [-fullName <full name>] [-UID <user ID>] [-shell <path to shell>] [-password <user password>] [-hint <user hint>] [-home <full path to home>] [-admin] [-picture <full path to user image>] (interactive] || -adminUser <administrator user name> -adminPassword <administrator password>)

创建普通用户

  1. 创建一个普通用户 <username>;

    1
    sysadminctl -addUser <username>
  2. 将其加入 /etc/sudoers,失败,即便是使用当前系统唯一的系统管理员都不可以;说是没有权限;看来,通过命令行的方式将用户加入 sudoers 被堵死了;估计只能进入 root Shell 环境才可以了;

  3. 那么,如果要切换到刚创建的 <username> 上呢?记得一定要使用 sudo,否则不成功

    1
    sudo su - <username>

    如果直接,是不会成功的;

    1
    su - <username>

创建 admin 用户

从 syadminctl 的帮助文档中可以看到,命令是通过 -adminUser 来创建的,不过没试;

删除用户

  • 通过 sysadminctl 删除
    下面的命令删除上面所创建的普通用户 <username> 失败,

    1
    sysadminctl -deleteUser <username\>

    错误是,系统认为用户 <username> 是拥有 SecureToken 的用户,不能使用命令行通过 syadminctl 直接删除;

  • 通过 System Preferences $\to$ User & Groups 却能删除成功;从这里可以看到,MacOS 的定位了,它是偏向桌面应用的,命令行的功能上有所削弱,或者说,通过命令行在功能上有比较大的限制;

修改密码

1
syadminctl -resetPasswordFor <username> -newPassword xxx ...

也失败,得到的错误是,Operation is not permitted without secure token unlock! 这里突然提到这么一个新的概念,secure token,错误的意思是,secure token 没有解锁的情况下,是不允许这么操作的;那这个 Secure Token 是干嘛的呢?见下一小节

Security Token

摸索

在执行相关命令的时候,总是报错

1
2
$ sysadminctl -secureTokenOn shangyang -password comedsh006
2018-10-19 11:27:52.966 sysadminctl[3069:148374] Operation is not permitted without secure token unlock.

意思是,如果想要执行上述命令,必须解锁 secure token;

按照 mac 的官方说法是,第一个被创建的 admin 用户会自动的分配一个 secure token,但是,我检查的情况是没有,

1
2
$ sysadminctl -secureTokenStatus <admin>
2018-10-19 11:42:05.516 sysadminctl[3082:151032] Secure token is DISABLED for user <admin>

相关问题在这里有讨论,https://apple.stackexchange.com/questions/313366/what-is-a-secure-token-and-how-do-i-get-an-admin-users-that-has-one

Security Token,

浅显易懂,总结得非常好,https://www.linkedin.com/pulse/high-sierra-secure-token-sysadminctl-alex-kaloostian/

https://derflounder.wordpress.com/2018/01/20/secure-token-and-filevault-on-apple-file-system/

总结

从上述的内容分析可知,Secure Token 是 10.13 版本,苹果新增的一个特性,主要是为了 FileVault 工具所设计的,System Preferences $\to$ Security & Privacy $\to$ FileVault 进入,从该工具的描述内容可知,它是用来对磁盘内容进行加密的,而这个加密工具,需要一个额外的权限,就是这个 Secure Token;

并且从官方文档的介绍中说明,该 Secure Token 不需要用户自行设置,在系统安装的时候,随着第一个管理员创建的时候,就会生成一个默认的 Secure Token 并赋给该管理员账号;但是通过 -secureTokenStatus 检查,imac 的默认管理员账号的 secureToken 是 DISABLE 的,并且我的 macbook 的默认系统管理员的 secureToken 默认也是 DISABLE 的;这就匪夷所思了,到底是没创建还是没激活 Secure Token?

总结

不难看出,通过 ssh 命令行创建用户的方式限制颇多,但是一旦进入桌面应用以后,所有的限制就都没有了;所以,这里务必要认识到,MacOS 是定位为一个桌面系统而非 Server,有这样的限制,也不足为其了;所以,如果必须要使用 MacOS 作为服务器,最好是通过桌面端创建好所需要的用户;但是,如果我需要批量的通过 SSH 命令初始化用户的话,就没辙了,至少,笔者没摸索出可行的方案;