简介: 您无需闯入水门大厦办公室来揭开 UNIX® 高手的秘密。本月,有位发言人正在为您揭秘。

如果您想知道我为何佩戴黑色太阳镜、假胡须和棒球帽(模仿一个专业冰壶球队 The Floating Stones 的徽标),那是因为我正在逃亡之中。我正在躲避黑色的遥控直升机、面色苍白的系统管理员和许多 “妖魔鬼怪” 的追踪,我这样做的目的只有一个,就是向您披露 UNIX® 高手的重大秘密。请戴上您的铝箔帽阅读本文吧!

保存环境变量

大多数 UNIX 用户在 .bashrc(针对 Bash shell)和 .zshrc(针对 Z shell)等 shell 启动文件中塞满大量用户设置,以便一次又一次地重建钟爱的 shell 环境。启动文件能够创建别名、设置 shell 选项、创建函数、以及设置环境变量。关键的环境变量包括 HOME(指向您的主目录)、PATH(列举从中搜索应用程序的目录)和 MANPATH(列举从中搜索手册页的目录)。要查看您的 shell 中设置了哪些环境变量,键入 printenv 命令。查阅 shell 手册页,获取可用环境变量的完整列表。

与 shell 一样,可以通过环境变量定制其他许多 UNIX 应用程序。例如,Java™ 子系统要求定义 JAVA_HOME 来指向 Java 运行时的根。同样,Amazon Web Services (AWS) 实用程序套件强制使用 AWS_CREDENTIAL_FILE 来指向一个包含有效私匙凭证的文件。单独的应用程序也提供环境变量,关键是如何发现这些变量。幸运的是,这种工作不需要非法入侵;相反,只需查询手边的实用工具手册页,查找标题为 “Environment Variables” 的章节即可。

例如,分页实用程序 less 定义了几个有用的环境变量:

环境变量 LESS 存储一些命令行选项,以在您每次调用该分页程序时减少键入量。例如,如果您需要阅读大量日志文件,可将以下语句添加到一个 shell 启动文件中:

export LESS='--RAW-CONTROL-CHARACTERS --squeeze-lines --ignore-case'

上述选项将分别解译控制字符(通常是语法着色),将多个空行压缩为一行,并忽略字符串匹配中的大小写。如果您使用代码,可尝试以下选项:

export LESS='--LINE-NUMBERS --quit-if-one-screen --quit-on-intr'

名为 LESSKEY 的环境设置指向一个密匙绑定文件。可以使用密匙绑定来定制 less 的行为,比如,匹配另一个页面或编辑器的行为。

与 shell 一样,less 能保留多个调用之间的历史。设置 LESSHISTFILE 和 LESSHISTSIZE 分别指向一个持久命令文件和设置要记录的命令的最大条数。

GNU Compiler Collection (GCC) 是另一个典型的环境变量应用示例。GCC 定义各种环境变量来定制其操作。LIBRARY_PATH,顾名思义,是一个目录列表,用于搜索要链接到的库;COMPILER_PATH 的工作方式与 shell 的 PATH 非常相似,但是由 GCC 在内部使用,用于查找编译过程中使用的子程序。

如果您针对单个平台写代码并构建二进制文件,您可能永远也不会用到这些环境变量,但是,如果您跨平台交叉编译相同的代码,那么这些变量对于访问每个平台的不同的头部和库至关重要。您可以将这些变量设置为不同的值集合,一个集合针对一种机器,而另一个集合针对另一种风格的系统。

事实上,您可以从 GCC 获得一个暗示:可以为每个应用程序维护多个环境变量集合,根据手边的工作从一个集合切换到另一个集合。一种方法是在每个项目目录中保存一个环境初始化文件并根据需要 source 它。例如,许多 Ruby 开发人员使用这种方法来在不同的 Ruby 版本间切换,根据需要更改环境变量 PATH、GEM_HOME 和 GEM_PATH,从一个版本跳到另一个版本。

“点缀” 环境

与环境变量非常相似的是,许多 Linux® 和 UNIX 应用程序都提供一个点 文件 — 文件名以圆点开始的小文件 — 来进行定制。与环境变量不同的是:环境变量采集少量标记和相对较少的信息量,而点文件可能更广泛、更复杂,拥有自己独特的语法规则、甚至自己的编程语言。点文件是保存选项和设置的理想位置,因为(根据 UNIX 传统)以一个圆点开始的文件名不会出现在标准的目录清单中。(使用 ls -a 来查看这些所谓的隐藏文件。)点文件是纯文本文件,只是文件名比较特别而已。

点文件通常位于您的主目录内,但有些实用程序也在当前工作目录中查找点文件。如果一个应用程序支持多个点文件,则该程序通常应用于优先规则,来表明一个文件比另一个文件优先。通常,“本地” 点文件 — 位于当前工作目录 — 优先级最高,然后是主目录中的点文件,最后是一个系统范围配置文件。这些文件可以全部存在,也可以存在一个,或者都不存在,这取决于应用程序将这些文件视为互斥的还是递增的。在第一种情况下,优先链中第一个点文件的优先权是不容置疑的。在后一种情况下,配置可以级联或融解到最终结果中。

less 的密匙绑定文件是一个简单点文件示例,位于 $HOME/.lesskey 中。文件中的每一行都是一对(一个按键和一条命令),如下所示:

\r     forw-line

\n     forw-line

e      forw-line

j      forw-line

^E     forw-line

^N     forw-line

k      back-line

y      back-line

^Y     back-line

fetchmail 是比较复杂的点文件示例。这个实用程序在本地从多个远程源提取电子邮件并传送消息。这个实用程序的操作只通过 $HOME/.fetchmailrc 控制。(参见手册页了解它的众多选项。)cron、git、vi,以及其他许多命令都能识别点文件。同样,请阅读这个应用程序的手册页,了解可以在点文件中配置的内容。有些点文件内容丰富,足以占用一个单独的手册页,比如 crontab。

嘘......关于 SSH 的秘密

Secure Shell (SSH) 是一个功能强大的子系统,用于安全地登录到远程系统、复制文件并穿越防火墙。由于 SSH 是一个子系统,它提供大量选项来定制和简化其操作。事实上,SSH 提供名为 $HOME/.ssh 的整个 “点目录” 来包含其所有数据。(您的 .ssh 目录必须是模式 600,以阻止他人访问。非 600 模式将干扰正常的操作。)特别是,文件 $HOME/.ssh/config 可以定义大量快捷方式,包括机器名称的别名、每主机访问控制等。

下面是位于 $HOME/.ssh/config 中的一个典型代码块,用于定制一个特定主机的 SSH:

Host worker

HostName worker.example.com

IdentityFile ~/.ssh/id_rsa_worker

User joeuser

~/.ssh/config 中的每个块配置一个或多个主机。不同的块使用一个空行分隔。这个块使用 4 个选项:Host、HostName、IdentityFile 和 User。Host 为 HostName 指定的机器创建一个昵称。昵称允许您键入 ssh worker,而不是 ssh worker.example.com。另外,IdentityFile 和 User 选项指定如何登录到 worker。前者指向此主机使用的一个私匙,后者提供登录 ID。这样,这个代码块就等同于以下命令:

ssh joeuser@worker.example.com -i ~/.ssh/id_rsa_worker

ControlMaster 是一个鲜为人知的强大选项。如果设置,同一个主机的多个 SSH 会话将共享单个连接。一旦第一个连接建立,后续连接就不再需要凭证,从而消除了每次连接同一机器都需要键入密码的麻烦。ControlMaster 非常方便,您可能愿意为每台机器启用它。启用方法非常简单,只需使用主机通配符 *:

Host *

ControlMaster auto

ControlPath ~/.ssh/master-%r@%h:%p

如您所料,标记了 Host * 的块将应用到每个主机,甚至是那些在配置文件中没有明确指定的主机。ControlMaster auto 尝试使用一个现有连接,并在没有发现共享连接时创建一个新连接。ControlPath 指向一个文件,以便持久化一个控制套接字以供共享。%r 用远程登录用户名替换,%h 用目标主机名替换,%p 代替连接使用的端口。(您还可以使用 %l,它使用本地主机名替换。)上述规范使用类似于下面的文件名创建控制套接字:

master-joeuser@worker.example.com:22

当到远程主机的所有连接都被切断时,每个控制套接字都就会被移除。如果您想随时了解连接到了哪些主机,只需键入 ls ~/.ssh 并查看控制套接字的主机名部分(%h)。

SSH 配置文件非常大,它也有自己的手册页。键入 man ssh_config 查看所有可能的选项。这里有一个巧妙的 SSH 技巧:可以通过 SSH 从本地系统进入远程系统。要用到的命令行如下所示:

$ ssh example.com -L 5000:localhost:3306

这条命令的意思是:通过 example.com 进行连接,并在本地机器上的端口 5000 和名为 “localhost” 的机器上的端口 3306(MySQL 服务器端口)之间建立一条通道。由于 localhost 在 example.com 上解释(因为通道已建立),因此 localhost 就是 example.com。由于出站通道 — 以前称为本地转发(local forward)— 已建立,本地客户端能够连接到端口 5000,并与 example.com 上运行的 MySQL 服务器通信。

通道创建的常规形式如下:

$ ssh proxyhost

localport:targethost:targetport

其中,proxyhost 是可以通过 SSH 访问的机器,并且拥有一个到 targethost 的网络连接(不通过 SSH)。localport 是您的本地系统上的一个非特权端口(1024 以上的任一未用端口),targetport 是您要连接到的服务的端口。

前面的命令从您的机器发送出去,到达外部世界。 也可以使用 SSH 发送进来,或者从外部世界连接到您的本地系统。入站通道的常规形式如下:

$ ssh user@proxyhost -R proxyport:targethosttargetport

建立一条入站通道 — 以前称为远程转发 — 时,proxyhost 和 targethost 的角色将被反转:目标是您的本地机器,代理是远程机器。user 是您在代理上的登录名。以下命令提供了一个具体示例:

$ ssh joe@example.com -R 8080:localhost:80

这条命令的意思是:用户 Joe 连接到 example.com,并将远程端口连接到本地端口 80。这条命令向 example.com 上的用户提供一个通道,以连接到 Joe 的机器上。远程用户能够连接到 8080,以便连接 Joe 机器上的 Web 服务器。

除了分别用于本地和远程转发的 -L 和 -R 之外,SSH 还提供 -D 参数来在远程机器上创建一个 HTTP 代理。请参见 SSH 手册页了解正确语法。

使用历史记录重写

如果您经常在 shell 提示符中花费大量时间,保存 shell 历史记录可以节约时间和输入。但是如果历史记录不能被修改,就会导致一些麻烦:记录重复的命令,且多个 shell 实例可能会干扰各自的历史记录。这两个问题很容易解决,只需在您的 .bashrc 中添加两行:

export HISTCONTROL=ignoreboth

shopt -s histappend

第一行将移除您的 shell 历史记录中连续的重复命令。如果您想移除所有零散的副本,可将 ignoreboth 更改为 erasedups。第二行在 shell 退出时将 shell 的历史记录附加到您的记录文件。默认情况下,Bash 记录文件命名为 ~/~/.bash_history (不错,这是一个点文件)。可以通过设置 HISTFILE(不错,这是一个环境变量)来更改它的位置。如果您想将一个 shell 的最近 10,000 命令保存在一个包含 100,000 条目的记录文件中,将 export HISTSIZE=10000 HISTFILESIZE=100000 添加到您的 shell 启动文件中。要查看一个 shell 的历史记录,在任意提示处键入 history 即可。

如果不能调用,那么保存的命令历史记录就用处不大。而这正是 shell !(或 bang)操作符的作用所在:

!! ("bang bang") 完整地重复最后一条命令。

!:0 是前一条命令的名称。

!^ 是前一条命令的第一个参数。!:2、!:3 ... !$ 等命令是前一条命令的第二、第三......以及最后一个参数。

!* 是最后一条命令的所有参数,命令名除外。

!n 重复历史中编号为 n 的命令。

!handle 重复以 handle 中的字符开始的最后一条命令。例如,!ca 将重复以字符 ca 开始的最后一条命令,如 cat README。

!?handle 重复包含 handle 中的字符组成的字符串的最后一条命令。例如,!?READ 还会匹配 cat README。

^original^substitution 使用 substitution 替换 original 的第一个 实例。例如,如果前一条命令是 cat README,,命令 ^README^license.txt 将生成一条新命令 cat license.txt。

!:gs/original/substitution 将使用 substitution 替换 original 的所有 实例(!:gs 表示 “全局替换[global substitution]”)。

!-2 是倒数第二条命令,!-3 是倒数第三条命令,以此类推。

您甚至可以合并历史表达式来生成 !-2:0 -R !^ !-3:2 这样的 “魔汤”,该命令将扩展为倒数第二条命令的名称,加上 -R,再加上前一条命令的第一个参数,以及倒数第三条命令的第二个参数。要使这样的神秘命令更具可读性,可以在键入时扩展历史参考。在任意提示符键入命令 bind Space:magic-space ,或者将其添加到一个启动文件, 从而将空格键绑定到函数 magic-space,该函数将扩展内联历史替换。

与扩展名无关的自动解压

鉴于 Internet 上有如此众多的代码,您可能每天都会下载数十个文件。可能会出现这样的情况:所有那些文件都使用不同的方式打包 — 有的是 ZIP 文件,有的是 RAR 文件,还有很多是 tarball 文件,尽管每个包都使用不同的实用程序压缩。记住如何解压缩和扩展每种包格式将会使人精疲力尽。那么,为何不在单个命令中完成所有那些任务呢?下面这个函数在许多样例点文件中广泛可用:

ex () {

if [ -f $1 ] ; then

case $1 in

*.tar.bz2) tar xjf $1 ;;

*.tar.gz) tar xzf $1 ;;

*.bz2) bunzip2 $1 ;;

*.rar) rar x $1 ;;

*.gz) gunzip $1 ;;

*.tar) tar xf $1 ;;

*.tbz2) tar xjf $1 ;;

*.tgz) tar xzf $1 ;;

*.zip) unzip $1 ;;

*.Z) uncompress $1 ;;

*.7z) 7z x $1 ;;

*) echo "'$1' cannot be extracted via extract()" ;;

esac

else

echo "'$1' is not a valid file"

fi

}

这个函数 ex 扩展了 11 种文件格式;如果要处理其他包类型,该函数还可以扩展。一旦定义 — 例如,在一个 shell 启动文件中 — 就可以简单地键入 ex somefile,其中 somefile 以以下一种已命名扩展结束:

$ ls

source

$ tar czf source.tgz source

$ ls -1

source

source.tgz

$ rm -rf source

$ ex source.tgz

$ ls -1

source

source.tgz

顺便说一下,如果您将今天下载的文件放错了位置,可以运行 find 来查找它:

$ find ~ -type f -mtime 0

命令 -type f 查找纯文本文件,-mtime 0 查找自当天午夜以来创建的文件。

更多秘密

需要揭开的专家秘密还有很多。在 Web 上搜索 “shell auto-complete”,进一步了解自动完成特性,该特性用于在您键入一条命令时提供上下文敏感的扩展。另外,搜索 “shell prompts” 以了解如何定制您的 shell 提示:可以将其设置为彩色,可以设置您的当前工作目录或 Git 分支,还可以显示历史数目 — 如果经常调用历史,这是一个方便的参考信息。要查看工作示例,可在 Github 中搜索 “dot files”。许多专家都将他们的 shell 配置张贴在 Github 上。

请原谅,现在我不得不寻找假发和古铜色化妆品了。要知道,如果您长得像 Groucho Marx(美国喜剧演员和电影明星 —— 译者注),要想躲起来还真不太容易。

关于作者

Martin Streicher 是一位 Ruby on Rails 的自由开发人员和 Linux Magazine 的前任主编。Martin 毕业于 Purdue University 并获得计算机科学学位,从 1986 年起他一直从事 UNIX 类系统的编程工作。他喜欢收集艺术品和玩具。