Pixel, Byte, and Comma的软件开发者Martin Streicher 在本文中为我们揭示了UNIX高手的秘密。Martin Streicher 是一位 Ruby on Rails 的自由开发人员和 Linux Magazine 的前任主编。Martin 毕业于 Purdue University 并获得计算机科学学位,从 1986 年起他一直从事 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
。
#p#
嘘......关于 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 手册页了解正确语法。
#p#
使用历史记录重写
如果您经常在 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,该函数将扩展内联历史替换。
#p#
与扩展名无关的自动解压
鉴于 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 上。
【编辑推荐】