macOS 好用的命令行(3)

Useful commandline in macOS 3

Posted by Chinsyo on April 10, 2019

在前文中我阐释了命令行(Command Line, 简称 CLI)对比用户图形界面(Graphic User Interface, 简称 GUI)的优势,在 v2ex 等渠道收到了一些有益的反馈,在文章开头简短的回应。

首先针对 CLI 对比 GUI 是否具有我说的优势,这是个见仁见智的问题,但从我的经历和体会来讲,以下几个场景 CLI 具有优势。

(1)需要经常切换开发环境,办公电脑,个人电脑,他人电脑。这种场景下使用现成的 CLI 能够避免下载安装应用带来时间上和空间上的消耗。

(2)一些简单但重复的操作,CLI 可以覆盖 Automator 无法实现的一些功能。

其次针对前两篇尤其第二篇内容比较基础的问题。写开发心得体会的直接目的是对我的工作和学习做个总结,方便回顾和反思;也额外考虑到对于很多不常接触 CLI 的用户而言,陌生的命令像一个黑匣子,不少小白都被 sudo rm -rf / 这个恶作剧搞崩溃过,为了大家尽快熟悉 CLI ,我针对之前发布的命令都附带了简要的描述。

本文接下来主要涉及三个内容,1) 前两篇文章遗漏的同类命令 2) 命令行相关基础 3) 针对一个综合的命令逐行解释。CLI 操作覆盖了 Mac OSX 系统的方方面面,包含此篇在内的前三篇介绍的都是比较基础的命令,lldb, make, lipo, awk, sed, grep 等等开发息息相关的命令由于功能比较庞大,后续会展开篇幅通过专门的文章介绍,请各位读者耐心等候。

一、遗漏的命令

(1)系统相关命令

之前的文章连系统内核版本和状态的命令都列举了,居然遗漏了当前硬件架构的命令,实在很不应该,这个命令在交叉编译等场景下非常有用,获取当前系统准确的硬件架构是交叉编译重要的步骤。

1
2
# 显示当前硬件架构
machine 
1
2
# 显示命令记录
history 
1
2
# 设置命令别名
alias ll="ls -l"

(2)文件操作命令

由于文件操作相关命令很多,之前的文章只列举了最基础的增删改查等,本文再做一些补充。

1
2
3
# 创建文件, Make File 的缩写
# 可以跟 -n 参数设置空文件大小, 单位可选 b/k/m/g, 默认为 b
mkfile FILENAME 
1
2
3
4
# 查明文件类型, 如
# 输入: file 226.pdf
# 输出: 226.pdf: PDF document, version 1.4
file FILENAME 
1
2
3
# 对输入的行去重, Unique 的缩写
# 通常用于管道操作, 追加 -i 忽略大小写
uniq 
1
2
3
# 对输入的行排序
# 通常用于管道操作, 追加 -r 为逆序排列
sort 
1
2
3
# 对输入的行裁剪
# 通常用于管道操作, 追加 -d 指定分隔符
cut 
1
2
3
# 统计文件的字符/单词/行/字节 数, 默认统计除字符数外三项
# -c 字节数, -l 行数, -m 字符数, -w 单词数
wc FILENAME
1
2
3
4
# 递归查找文件
# 用法比较多, 详情 man find 查看, 本文不做展开
# 如, 查找所有文件并打印: find / -type f -exec echo {} \;
find DIRECTORY EXPRESSION
1
2
3
# 快速查找文件
# 需要建立索引数据库, 使用后会提示如何创建
locate PATERN

二、命令行基础

这部分是对我所学内容的总结,可能不够系统和准确,希望各位读者发现问题或者存在疑惑后可以通过留言等方式反馈。

在前面的文章中我引入了一个问题,Terminal, Shell, TTY 和 Console 有什么区别,理解了 Shell 是我们的命令行解释程序之后,我们来学习更多相关的基础知识。

(1)显示当前 Shell 和更新配置文件

我们可以通过 echo $SHELL 打印出当前使用的 Shell, 以 $ 符号开头的内容表示这是一个变量,这个变量之所以能够被访问到,是因为它被设置为当前的环境变量,我们可以通过 env | grep SHELL 印证。

前面提到过,source FILENAME 可以执行文件以更新配置文件,它和 sh FILENAME 的区别在于 source 之后的变量会暴露给当前命令行。通常对于 Mac OSX, 环境变量的配置文件位于当前用户家目录,ls -a ~/ 可以查看家目录下的文件,加上 -a 可以显示包括隐藏文件在内的全部文件,Bash 的配置文件为 .bashrc ,Zsh 的配置文件为 .zshrc 。

(2)重定向

在 Shell 编程中,我们有时需要将前面处理的结果作为输入或输出传递给下一个程序,这时就要用到重定向。

前面的例子中 ls > FILENAME 是指列举文件作为输入,创建 FILENAME 或覆盖 FILENAME, 如果将 > 替换为 » 则将输出追加到文件。反之 < 则是作为输入,«EOF 是指将后续输入结束符之前的内容作为输入。

常见的 >/dev/null 是指将输出重定向到 /dev/null,通常用于避免命令行输出结果。还有一种常见使用方式 2>&1,这里的 2 是文件描述符之一,2 表示标准错误输出,1 表示标准输出,0 表示标准输入,2>&1 表示将标准错误输出重定向到标准输出,即错误和正常输出一视同仁。

(3)组合命令

在组合命令中,主要有四个连接符。

1)管道|

1
2
# 将命令1的输出作为命令2的输入
COMMAND1 | COMMAND2 

2)分号;

1
2
# 执行完命令1之后执行命令2,即使命令1失败也会执行命令2
COMMAND1; COMMAND2

3)逻辑与&&

1
2
# 执行完命令1之后执行命令2,命令1失败则不会执行命令2
COMMAND1 && COMMAND2

4)逻辑或||

1
2
# 如果命令1执行失败,则执行命令2
COMMAND1 || COMMAND2

需要注意,可以通过()或者{}包围的方式组合命令使用以上连接符。除了将输出作为输入之外,前一命令的输入也可以作为参数传递给下一命令,这里可以通过 xargs 传递,默认情况下 xargs 会在空格处中断输入,而且得到的每个标记变成一个参数。但是,在 xargs 构建命令时,它会一次传递尽可能多的参数。您可以使用 -n或 –max-args 参数覆盖此行为。

三、命令逐行解释

我选取了在本系列文章第一篇出现过的查询系统 DNS 设置的命令,搜索引擎显示的方案运行结果不符合预期,以下为我编写的 Shell 脚本。

1
2
3
4
5
6
7
8
9
#! /bin/bash
# list all network services
IN=$(networksetup -listallnetworkservices | awk '{if (NR>1) print $0 ";"}');
while IFS=";" read -ra SERVICES; do
    for i in "${SERVICES[@]}"; do
        echo "${i} DNS Servers:"
        networksetup -getdnsservers "${i}"
    done
done <<< "$IN"

第一行表示如果该文件有执行权限,直接执行时采用的打开方式,Shell 脚本通常使用 /bin/bash 。

第二行的内容表明这是一段注释。

第三行,IN=是一个赋值语句,语句后半部分$()包围的内容表明这是一段赋值的内容为括号内的执行结果。括号内,管道连接符之前的部分表明列举所有网络连接服务,输出的第一行并非网络连接服务所以添加 NR>1 的条件表明跳过第一行,print $0 表明打印所有参数,后面追加了分号后续使用。

第四行和第九行表明这是一个循环,将第三行的 IN 作为输入,以分号作为分隔符,读取输入作为 SERVICES 变量。之所以以分号为分隔符,是因为网络连接服务中有 iPhone USB 这种包含空格的,而默认分隔符为空格会将其分为两个输入,单独查询 iPhone 这个不存在的网络连接服务 DNS 会报错。

第五六七八行内容比较简单,遍历 SERVICES,打印服务名称和提示语句,然后获取它的 DNS 设置,以下附图用于验证结果。