学习 Shell 编程,需要对 Shell 有一些基本了解。本文从计算机发展历史介绍各种 Shell 之间的变迁。到了开始学习 Linux Shell 编程的时候,则要知道常见的 Shell 如 bash/zsh 的差异,以及 Shell 各种登录方式。

1. 从终端机(Terminal)说起

Shell 命令执行在终端中,谈论 Shell 时一定离不开终端机。终端机的英文是 Terminal,也有翻译为命令行的,这个翻译很好地反映了工具的作用:接受命令输入,展示命令执行过程,给出命令输出。

常见操作系统中都有终端:macOS 系统有终端机,linux 系统有 gnome-terminal,Windows 系统有 Windows Terminal。

mac Terminal

1.1 终端机——从硬件到软件

要了解终端机,需要了解一些计算机的发展历史。冯诺伊曼计算机架构下,计算机分为输入、输出、存储、控制、计算部分。早期计算机都是庞然大物,这几部分都是分开的。输入输出靠纸带,读取纸带的打孔获得输入,计算机执行完成,往纸带上打孔用以输出。使用起来相当麻烦:当时优秀的程序员都是直接写 01 代码的。

世界上第一台计算机

世界上第一台计算机

写一个程序,就要消耗一堆纸带,修改程序重新执行,得重新打纸带。就像用签字笔在纸上写字一样,写错了就只能划掉(当然我猜测可能有修正带这样的东西)。这显然不是正常人类干得出来的事,既不好读也不好写,更不用说调试了。

随着计算机的发展,键盘成了计算机的输入,显示器成为计算机的输出,这两样东西组合到一起,通过电缆和主机连接到一起:键盘输入,主机执行,显示器显示命令执行结果。这种方式比纸带高效得多,修改起来也更加方便,人机交互就容易了许多,比纸带不知道高到哪里去了。由此看来,今有键盘侠,古无纸带客,此言诚不我欺。

键盘显示器这两样东西组成的装置就是终端。它们其实并不算计算机的一部分(计算机的主要部分还是中间运算的部分),但有了它(指的是键盘和显示器一起)之后,人类和计算机交互起来效率大大提高。那么是不是可以给计算机本身也配置一个这样的装置呢,计算机启动的时候还可以输出一些日志,显示状态、报错等。当然是可以的!这样的”终端”就被称为控制台(console)。通过这个特殊的终端,可以对整个计算机进行控制。

随着技术进步,计算机进入集成电路时代,各种电路都塞到芯片中去了,电脑越做越小,微机越来越流行。

MOSFET

一种晶体管 MOSFET 的结构,图片来自维基百科

以前的庞然大物现在都可以放到口袋里带走了。键盘显示器成了电脑的标配。反而那种只能和大型主机交互的装置彻底走入历史,硬件终端完全消失,取而代之的是软件终端:在微机上用一套软件模拟终端和主机进行交互。

1.2 键盘和 tty

在 Linux 中,打开终端就会自动开启一个 Shell(如 Bash),还会显示 tty

Last login: Mon Sep 20 10:34:38 on ttys006

tty 是什么意思?这要从键盘说起。最开始键盘都是机械结构,直接将墨水印在纸上。它只是解决了信息输入的问题,而并没有解决远程输出的问题,即把信息从一个设备传送到另外一个设备。

通信技术在同步发展,此时已经有了电传打字机(teletype/teletypewriter),能够实现如下功能:

  1. 在本地通过键盘输入一些信息
  2. 输入结果能够在本地纸张上显示
  3. 本地输入结果能够传递到别的设备中,被别的设备识别
  4. 能够接受别的设备的输出,并且翻译为人类可以读的东西

这玩意就被称为 tty,按照词缀来翻译就是远程输入或者远程打字。翻译为电传打字的意思是说它传递的是电信号。

二战中使用的 teletype

二战中使用的 teletype图片来自维基百科

在计算机出现之前,人们就已经建立起了以电传打字机为节点的”互联网”。这个”互联网”,可以交换股票数据。计算机开始发展之后,人们也发现有和计算机交互的需求,于是就直接把 tty 拿过来了,作为计算机的输入输出部分。tty 是可以脱离计算执行的,它并不是计算机的一部分;现在的服务器在没有键盘显示器的情况下也能运行。

某些情况下,又不得不依赖键盘显示器操作。如果服务器宕机了需要处理,就需要给服务器连接显示器和键盘进行诊断。此时这样的终端也被看成是控制台(console),它的作用是输出计算机的基本信息,它属于计算机的一部分。当然现在并不会明显区分终端和控制台,它们都是和计算机交互的工具。

现在系统中还有 tty 命令,用于显示当前终端:

tty
/dev/ttys009

在 Linux 中,可以执行 Alt + Ctrl + F{2,6} 打开各个 tty。究竟有多少个 tty 呢,查看 /dev/ 文件夹,看其中的 tty 文件。在 linux 中,一切皆文件。tty 不例外,也可以向 tty 写入信息:

echo "hello" >> /dev/tty1

通过 who 命令可以获取所有已登录的终端,echo 内容到 /dev/tty{n} 可以在对应终端上显示。

通常 ssh 的时候,执行 tty 获得的信息可能是 pts,这是 pseudo terminal 的缩写,表示伪终端。在内核中有两部分,master side 和 tty 驱动中实现的 slave side。如果在 ssh 情况下输入 tty,则会是 /dev/pts,也可以通过往 /dev/pts 中写入信息往终端传输信息。

2. Shell 是什么

前面说到了终端,通过它可以和计算机进行交互。实际上终端也不是直接去控制硬件,而是它和操作系统(Operator System)内核(Kernel)交互,让操作系统内核去控制硬件。

内核

操作系统内核,图片来自鸟哥的 linux 私房菜

设计操作系统这一中介的原因,本质是计算机系统太复杂,如果每执行一个操作就直接操作计算机硬件,那将复杂到无法完成。电子计算机主要依靠晶体管中电子运动形成的高低电压,目前最新 CPU 已经能够集成 10 亿级别的晶体管,如果手工去操作,写代码绝对要到地老天荒。

解决办法是层层抽象,只要给操作系统发指令,剩下的事情就让操作系统帮我们搞定。Shell 就是我们给操作系统发送指令的一个通道。

广义上任何能够给操作系统发送信息实现一定功能的程序都是 Shell,如 Windows/macOS 上各种软件,Windows 文件浏览器,Mac 上的 Finder,Linux/Unix 上的文字界面的 Shell 程序。狭义的 Shell 指的是 Linux/Unix 上用 C 语言写成的程序,如 Bash/Zsh。本文以 Bash/Zsh 为代表,介绍狭义 Shell 的使用。

2.1 使用 Shell 的理由

  1. 大部分服务器运行 Linux/Unix 操作系统,Shell 是原生的管理方式
  2. Shell 传递文字界面,对带宽要求比较低
  3. 灵活使用 Shell,可以提升效率

3. Shell 登录方式

Shell 有多种使用方式:

  1. 文字界面登录 Linux 系统
  2. 在图形界面直接打开终端机
  3. 通过 ssh 远程登录服务器
  4. 执行一段 Shell Script

需要对上面的场景进行一些分类:按照是否需要输入密码,可以分为登录式(login)和非登录式(non-login);按照执行一个文件还是执行一个命令,可以分为交互式(interactive)和非交互式(non-interactive)。

3.1 登录与非登录式

登录式和非登录式的区别在于启动退出时候读取的文件不一样。

3.1.1 登录式

本地 Linux,进入系统后直接打开 terminal,这种方式被称为非登录(non-login),因为不需要输入账户和密码。bash 有如下配置文件:

/etc/profile
~/.bash_profile
~/.bash_login
~/.profile
~/.bash_logout

对于 login 的 Shell,通常会读取两个文件:

  1. /etc/profile,系统配置,一般不要修改
  2. ~/.bash_profile~/.bash_login~/.profile 中的一个,读到一个就停止

通常配置的却是 ~/.bashrc 文件。其中 rc 是 run commands 的意思。但 ~/.bashrc 并不是上面 2 中任何一个文件。奥秘就在 ~/.bash_profile 中:

cat ~/.bashrc
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
	. ~/.bashrc
fi

# User specific environment and startup programs

它直接就读取 ~/.bashrc 文件了。

3.1.2 非登录式

对于 non-login 的 Shell,它只读取 ~/.bashrc 这一个配置文件。

3.1.3 su 切换时候的环境变量

在 Linux 中,通过 su 命令切换用户。su 是 switch user 的意思。通常 su 之后,只是切换用户,并不会切换其他的。而执行 su - 之后,就会直接切换到 root 账户,连环境变量都会切换。

3.2 交互式和非交互式

3.2.1 交互式执行

交互式(interactive)执行就是输入一条命令,执行,输出执行结果或者报错。直接打开命令行工具或 SSH 时候的默认状态。好处就是可以马上执行,获得结果或者报错。后面第五部分会详细介绍交互式 Shell 的配置。

3.2.2 非交互式

简单来说就是执行 Shell Script。把要执行的命令按照顺序写到一个文件里,这个文件就是 Shell Script,然后让 Shell 调用这个脚本,里面的命令从上到下执行。Shell Script 的好处在于可以快速地复用命令,比如有如下场景:

  1. 同样的命令需要执行 100 次,但是命令本身并不提供循环执行的参数,比如对 100 个文件按照规则改名
  2. 执行的命令比较长,需要在前几条修改一下参数,然后执行后面的命令获取结果
  3. 在电脑 A 上执行的命令需要在电脑 B(或者更多电脑)上原样执行一遍

此时若采用交互式执行,则需要不停地敲键盘执行,和敲击鼠标无异,效率提升有限。执行文件的方式就是非交互式(non-interactive),此时执行就简化了许多。非交互式执行会开启一个新的线程来执行 Shell 命令,当执行遇到 EOF 之后,整个线程就会退出。

4. Shell Script

在非交互式执行中,需要特别学习 Shell Script 的语法:借助 Shell Script 的语法,还可以对命令进行扩充,如循环执行一条命令,如按照条件执行命令。Shell Script 编程的学习曲线有些陡峭:

  1. 空格十分敏感 在 Shell Script 中,等号(=)左右有无空格,表示完全不同的意思,但这样的错误一开始是很难发现的
  2. 报错过于简单 如果遇到了空格问题,报错不会提示空格缺失,而会说缺少命令
  3. 遇到错误后继续执行 一般的编程语言是遇到错误后停止执行,shell script 跳过错误,继续执行,前面不对的结果会影响后面的执行,如果不好好查日志,就不知道错误在哪里了
  4. 缺乏方便的调试工具 调试起来不是很方便,常常是在执行中调试。执行一次,再调试,环境就已经不一样了。

5. zsh 配置

当然,交互式的 Shell 也是被广泛使用的工具。

sh 是最古老的 Shell,目前已经不怎么使用了。目前最流行的版本是 Bash(Bourne Again Shell),但是 Bash 在没有配置的情况下很难用。常见的 Shell 还有 Zsh、Fish、Ksh、Tcsh。一般比较推荐 Zsh,它相对于 Bash 而言,有更好的补全和跳转功能。搭配上 oh-my-zsh 插件,可以极大提升效率。zsh 安装方式如下:

sudo dnf install zsh

安装好 zsh 之后需要切换系统默认登录 Shell 为 zsh:

sudo chsh -s /usr/bin/zsh user_name

配置 oh-my-zsh

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

配置 oh-my-zsh 插件,找到 ~/.zshrc 文件,在 plugins 中添加:

plugins=(
   git
   extract
   zsh-syntax-highlighting
   zsh-autosuggestions
 )

总结

本文主要介绍 Shell 的基本概念,主要解决 Shell 使用之前的问题。先介绍了终端机(Terminal)、tty 等概念,然后介绍了 Shell 交互和非交互的执行方式,最后介绍了 Shell 交互执行时候 zsh 的使用方式,配置 oh-my-zsh 相关的设置。