当您使用
shell
进行工作时,可以使用一些方法改善您的工作流,本节课我们就来讨论这些方法。我们已经使用
shell
一段时间了,但是到目前为止我们的关注点主要集中在使用不同的命令上面。现在,我们将会学习如何同时执行多个不同的进程并追踪它们的状态、如何停止或暂停某个进程以及如何使进程在后台运行。我们还将学习一些能够改善您的
shell
及其他工具的工作流的方法,这主要是通过定义别名或基于配置文件对其进行配置来实现的。这些方法都可以帮您节省大量的时间。例如,仅需要执行一些简单的命令,我们就可以在所有的主机上使用相同的配置。我们还会学习如何使用 SSH 操作远端机器。
Lecture 5 Command-line Environment
当您使用
shell
进行工作时,可以使用一些方法改善您的工作流,本节课我们就来讨论这些方法。我们已经使用
shell
一段时间了,但是到目前为止我们的关注点主要集中在使用不同的命令上面。现在,我们将会学习如何同时执行多个不同的进程并追踪它们的状态、如何停止或暂停某个进程以及如何使进程在后台运行。我们还将学习一些能够改善您的
shell
及其他工具的工作流的方法,这主要是通过定义别名或基于配置文件对其进行配置来实现的。这些方法都可以帮您节省大量的时间。例如,仅需要执行一些简单的命令,我们就可以在所有的主机上使用相同的配置。我们还会学习如何使用 SSH 操作远端机器。
任务控制
- 某些情况下我们需要中断正在执行的任务,比如当一个命令需要执行很长时间才能完成时(假设我们在使用
find
搜索一个非常大的目录结构)。大多数情况下,我们可以使用Ctrl-C
来停止命令的执行。但是它的工作原理是什么呢?为什么有的时候会无法结束进程?
结束进程
ctrl+c、ctrl+\、★kill、★ps、top
您的
shell
会使用 UNIX 提供的信号机制执行进程间通信。当一个进程接收到信号时,它会停止执行、处理该信号并基于信号传递的信息来改变其执行。-> 就这一点而言,信号是一种软件中断。在上面的例子中,当我们输入
Ctrl-C
时,shell 会发送一个SIGINT
(Signal Interrupt)信号到进程。下面这个 Python 程序向您展示了捕获信号
SIGINT
并忽略它的基本操作,它并不会让程序停止。为了停止这个程序,我们需要使用SIGQUIT
(Signal Quit)信号,通过输入Ctrl-\
可以发送该信号。1
2
3
4
5
6
7
8
9
10
11
12#!/usr/bin/env python
import signal, time
def handler(signum, time):
print("\nI got a SIGINT, but I am not stopping")
signal.signal(signal.SIGINT, handler)
i = 0
while True:
time.sleep(.1)
print("\r{}".format(i))
i += 1如果我们向这个程序发送两次
SIGINT
,然后再发送一次SIGQUIT
,程序会有什么反应?注意^
是我们在终端输入Ctrl
时的表示形式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ python sigint.py
0
1
2
3
4
5
6
7
^C
I got a SIGINT, but I am not stopping
8
9
10
11
12
13
^C
I got a SIGINT, but I am not stopping
14
15
16
17
18
19
20
21
^\退出 (核心已转储)尽管
SIGINT
和SIGQUIT
都常常用来发出和终止程序相关的请求。SIGTERM
则是一个更加通用的、也更加优雅地退出信号。为了发出这个信号我们需要使用kill
命令, 它的语法是:kill -TERM <PID>
。kill
:用于删除执行中的程序或工作。kill
可将指定的信息送至程序,预设的信息为SIGTERM(15)
,可将指定程序终止。若仍无法终止该程序,可使用SIGKILL(9)
信息尝试强制删除程序。程序或工作的编号可利用ps
指令或jobs
指令查看。kill
命令的基本格式如下:kill [-s 信号声明 | -n 信号编号 | -信号声明] 进程号(PID)
1
2
3
4杀死指定用户所有进程
kill -9 $(ps -ef | grep hnlinux) //方法一 过滤出hnlinux用户进程
kill -KILL $(ps -ef | grep hnlinux) //方法一 过滤出hnlinux用户进程
kill -u hnlinux //方法二- 最常用的信号是:
1 (HUP)
:重新加载进程,如果想要更改配置而不需停止并重新启动服务,请使用该命令。 -> HangUP9 (KILL)
:杀死一个进程。SIGKILL
是一个特殊的信号,它不能被进程捕获并且它会马上结束该进程。不过这样做会有一些副作用,例如留下孤儿进程。
15 (TERM)
:正常停止一个进程。 -> Terminate
- 最常用的信号是:
ps
:用于显示当前进程的状态,类似于 windows 的任务管理器。 -> (process status)ps
命令的基本格式如下:ps [选项]
-a
:显示现行终端机下的所有程序,包括其他用户的程序-A
:显示所有进程-u
:以用户为主的格式来显示程序状况-x
:显示所有程序,不以终端机来区分-e
:显示所有进程,同“-A”
-f
:显示进程之间的关系
注意:一般
-ef
、-aux
联合起来使用,即ps -ef
、ps -aux
来查看进程信息。top
:用于实时显示 process 的动态。显示指定 pid 的进程的信息:
top -p pid
暂停和后台执行进程
ctrl+z、fg、bg、&、★jobs
信号可以让进程做其他的事情,而不仅仅是终止它们。例如,
SIGSTOP
会让进程暂停。在终端中,键入Ctrl-Z
会让 shell 发送SIGTSTP
信号,SIGTSTP
是 Terminal Stop 的缩写(即terminal
版本的SIGSTOP)。我们可以使用
fg
或bg
命令恢复暂停的工作。它们分别表示在前台继续或在后台继续。fg
:用于把后台工作恢复到前台执行。 -> foreground该命令的基本格式如下:
[root@localhost ~]#fg %工作号
注意,在使用此命令时,
%
可以省略,但若将% 工作号
全部省略,则此命令会将带有 + 号的工作恢复到前台。bg
:用于把后台暂停的工作恢复到后台执行。 -> background该命令的基本格式如下:
[root@localhost ~]#bg %工作号
jobs
命令会列出当前终端会话中尚未完成的全部任务。您可以使用 pid 引用这些任务。更加符合直觉的操作是您可以使用百分号 + 任务编号
(jobs
会打印任务编号)来选取该任务。如果要选择最近的一个任务,可以使用$!
这一特殊参数。jobs
:用来查看当前终端放入后台的工作。jobs
命令的基本格式如下:[root@localhost ~]#jobs [选项]
-l
:列出进程的 PID 号。-r
:只列出运行中的进程。-s
:只列出已停止的进程。
1
2
3[root@localhost ~]#jobs -l
[1]- 2023 Stopped top
[2]+ 2034 Stopped tar -zcf etc.tar.gz /etc在上面的例子中,当前终端有两个后台工作:一个是
top
命令,工作号为 1,状态是暂停,标志是"-"
;另一个是tar
命令,工作号为 2,状态是暂停,标志是"+"
。**"+"
号代表最近一个放入后台的工作,也是工作恢复时默认恢复的工作。"-"
号代表倒数第二个放入后台的工作**,而第三个以后的工作就没有"+-"
标志了。一旦当前的默认工作处理完成,则带减号的工作就会自动成为新的默认工作,换句话说,不管此时有多少正在运行的工作,任何时间都会有且仅有一个带加号的工作和一个带减号的工作。
还有一件事情需要掌握,那就是命令中的
&
后缀可以让命令直接在后台运行,这使得您可以直接在shell
中继续做其他操作,不过它此时还是会使用shell
的标准输出,这一点有时会比较恼人(这种情况可以使用shell
重定向处理)。1
2
3
4god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ sleep 1000 &
[1] 2945
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs -l
[1]+ 2945 运行中 sleep 1000 &让已经在运行的进程转到后台运行,您可以键入
Ctrl-Z
,然后紧接着再输入bg
。注意,后台的进程仍然是您的终端进程的子进程,一旦您关闭终端(会发送另外一个信号SIGHUP
),这些后台的进程也会终止。为了防止这种情况发生,您可以使用nohup
(一个用来忽略SIGHUP
的封装) 来运行程序。针对已经运行的程序,可以使用disown
。除此之外,您可以使用终端多路复用器来实现,下一章节我们会进行详细地探讨。下面这个简单的会话中展示了这些概念的应用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ sleep 1000
^Z
[1]+ 已停止 sleep 1000
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ nohup sleep 2000 &
[2] 2984
忽略输入并把输出追加到'nohup.out'
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs
[1]+ 已停止 sleep 1000
[2]- 运行中 nohup sleep 2000 &
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ bg %1
[1]+ sleep 1000 &
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs -l
[1]- 2983 运行中 sleep 1000 &
[2]+ 2984 运行中 nohup sleep 2000 &
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ kill -STOP %1
[1]+ 已停止 sleep 1000
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs -l
[1]+ 2983 停止 (信号) sleep 1000
[2]- 2984 运行中 nohup sleep 2000 &
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ kill -SIGHUP %1
[1]+ 挂起 sleep 1000
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs -l
[2]+ 2984 运行中 nohup sleep 2000 &
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ kill -SIGHUP %2
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs -l
[2]+ 2984 运行中 nohup sleep 2000 & #因为是nohup,所以 kill -SIGHUP 对该进程无效
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ kill %2
[2]+ 已终止 nohup sleep 2000
god@god-virtual-machine:~/桌面/missing-semester/command-line environment$ jobs -l
软件包管理
★apt
软件包:通常指的是一个应用程序,它可以是一个 GUI 应用程序、命令行工具或(其他软件程序需要的)软件库。
软件包管理:
- 底层工具:主要用来处理安装和删除软件包文件等任务:DPKG,RPM
- 上层工具:主要用于数据的搜索任务和依赖解析任务:APT,YUM,DNF

以 Ubuntu 操作系统为例,我们通过使用
apt
工具来进行软件包的管理。
★终端多路复用
★tmux
当您在使用命令行时,您通常会希望同时执行多个任务。举例来说,您可能想要同时运行您的编辑器,并在终端的另外一侧执行程序。尽管再打开一个新的终端窗口也能达到目的,使用终端多路复用器则是一种更好的办法。
像
tmux
(terminal multiplexer)这类的终端多路复用器可以允许我们基于面板和标签分割出多个终端窗口,这样您便可以同时与多个 shell 会话进行交互。不仅如此,终端多路复用使我们可以分离当前终端会话并在将来重新连接,这让您操作远端设备时的工作流大大改善,避免了
nohup
和其他类似技巧的使用。现在最流行的终端多路器是
tmux
。tmux
是一个高度可定制的工具,您可以使用相关快捷键创建多个标签页并在它们间导航。tmux
的快捷键需要我们掌握,它们都是类似<C-b> x
这样的组合,即需要先按下Ctrl+b
,松开后再按下x
。tmux
中对象的继承结构如下:- 会话:每个会话都是一个独立的工作区,其中包含一个或多个窗口。
tmux
开始一个新的会话tmux new -s NAME
以指定名称开始一个新的会话tmux ls
列出当前所有会话- 在
tmux
中输入<C-b> d
,将当前会话分离 tmux a
重新连接最后一个会话。您也可以通过-t
来指定具体的会话,如tmux a -t foobar
- 窗口 :相当于编辑器或是浏览器中的标签页,从视觉上将一个会话分割为多个部分
<C-b> c
创建一个新的窗口,使用<C-d>
关闭<C-b> N
跳转到第 N 个窗口,注意每个窗口都是有编号的<C-b> p
切换到前一个窗口<C-b> n
切换到下一个窗口<C-b> ,
重命名当前窗口<C-b> w
列出当前所有窗口
- 面板 :像 vim 中的分屏一样,面板使我们可以在一个屏幕里显示多个
shell
<C-b> "
水平分割<C-b> %
垂直分割<C-b> <方向>
切换到指定方向的面板,<方向> 指的是键盘上的方向键<C-b> z
切换当前面板的缩放,按一次ctrl+b z
最大化当前面板,再按一次恢复原来大小<C-b> [
开始往回卷动屏幕。您可以按下空格键来开始选择,回车键复制选中的部分<C-b> <空格>
在不同的面板排布间切换
- 会话:每个会话都是一个独立的工作区,其中包含一个或多个窗口。
别名
输入一长串包含许多选项的命令会非常麻烦。因此,大多数
shell
都支持设置别名。shell
的别名相当于一个长命令的缩写,shell
会自动将其替换成原本的命令。例如,bash
中的别名语法如下:1
alias alias_name="command_to_alias arg1 arg2"
注意,
=
两边是没有空格的,因为alias
是一个shell
命令,它只接受一个参数。值得注意的是,在默认情况下
shell
并不会保存别名。为了让别名持续生效,您需要将配置放进shell
的启动文件里,像是.bashrc
或.zshrc
,下一节我们就会讲到。别名有许多很方便的特性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28创建常用命令的缩写
alias ll="ls -lh"
能够少输入很多
alias gs="git status"
alias gc="git commit"
alias v="vim"
手误打错命令也没关系
alias sl=ls
重新定义一些命令行的默认行为
alias mv="mv -i" # -i prompts before overwrite
alias mkdir="mkdir -p" # -p make parent dirs as needed
alias df="df -h" # -h prints human readable format
别名可以组合使用
alias la="ls -A"
alias lla="la -l"
在忽略某个别名
\ls
或者禁用别名
unalias la
获取别名的定义
alias ll
会打印 ll='ls -lh'alias
:用于设置指令的别名。用户可利用alias
,自定指令的别名。若仅输入alias
,则可列出目前所有的别名设置。alias
的效力仅及于该次登入的操作。若要每次登入是即自动设好别名,可在.profile
或.cshrc
中设定指令的别名。该命令的基本格式如下:
[root@localhost ~]#alias [别名]=[指令名称]
★配置文件(Dotfiles)
很多程序的配置都是通过纯文本格式的被称作点文件的配置文件来完成的(之所以称为点文件,是因为它们的文件名以
.
开头,例如~/.vimrc
。也正因为此,它们默认是隐藏文件,ls
并不会显示它们)。shell
的配置也是通过这类文件完成的。在启动时,您的shell
程序会读取很多文件以加载其配置项。根据shell
本身的不同,您从登录开始还是以交互的方式完成这一过程可能会有很大的不同。对于
bash
来说,在大多数系统下,您可以通过编辑.bashrc
或.bash_profile
来进行配置。在文件中您可以添加需要在启动时执行的命令,例如上文我们讲到过的别名,或者是您的环境变量。实际上,很多程序都要求您在
shell
的配置文件中包含一行类似export PATH="$PATH:/path/to/program/bin"
的命令,这样才能确保这些程序能够被shell
找到。还有一些其他的工具也可以通过点文件进行配置:
bash
:~/.bashrc
,~/.bash_profile
git
:~/.gitconfig
vim
:~/.vimrc
和~/.vim
目录ssh
:~/.ssh/config
tmux
:~/.tmux.conf
我们应该如何管理这些配置文件呢,它们应该在它们的文件夹下,并使用版本控制系统进行管理,然后通过脚本将其 符号链接(symlinks) 到需要的地方。这么做有如下好处:
- 安装简单: 如果您登录了一台新的设备,在这台设备上应用您的配置只需要几分钟的时间;
- 可以执行: 您的工具在任何地方都以相同的配置工作
- 同步: 在一处更新配置文件,可以同步到其他所有地方
- 变更追踪: 您可能要在整个程序员生涯中持续维护这些配置文件,而对于长期项目而言,版本历史是非常重要的
可移植性
配置文件的一个常见的痛点是它可能并不能在多种设备上生效。例如,如果您在不同设备上使用的操作系统或者
shell
是不同的,则配置文件是无法生效的。或者,有时您仅希望特定的配置只在某些设备上生效。有一些技巧可以轻松达成这些目的。如果配置文件 if 语句,则您可以借助它针对不同的设备编写不同的配置。例如,您的 shell 可以这样做:
1
2
3
4
5
6
7if [[ "$(uname)" == "Linux" ]]; then {do_something}; fi
使用和 shell 相关的配置时先检查当前 shell 类型
if [[ "$SHELL" == "zsh" ]]; then {do_something}; fi
您也可以针对特定的设备进行配置
if [[ "$(hostname)" == "myServer" ]]; then {do_something}; fi如果配置文件支持 include 功能,您也可以多加利用。例如:
~/.gitconfig
可以这样编写:1
2[include]
path = ~/.gitconfig_local然后我们可以在日常使用的设备上创建配置文件
~/.gitconfig_local
来包含与该设备相关的特定配置。您甚至应该创建一个单独的代码仓库来管理这些与设备相关的配置。如果您希望在不同的程序之间共享某些配置,该方法也适用。例如,如果您想要在
bash
和zsh
中同时启用一些别名,您可以把它们写在.aliases
里,然后在这两个 shell 里应用:1
2
3
4Test if ~/.aliases exists and source it
if [ -f ~/.aliases ]; then
source ~/.aliases
fi
★远端设备
★ssh、ssh-keygen、★scp
对于程序员来说,在他们的日常工作中使用远程服务器已经非常普遍了。如果您需要使用远程服务器来部署后端软件或您需要一些计算能力强大的服务器,您就会用到
secure shell
(SSH
)。和其他工具一样,SSH
也是可以高度定制的,也值得我们花时间学习它。通过如下命令,您可以使用
ssh
连接到其他服务器:1
ssh foo@bar.mit.edu
这里我们尝试以用户名
foo
登录服务器bar.mit.edu
。服务器可以通过 URL 指定(例如bar.mit.edu
),也可以使用 IP 指定(例如foobar@192.168.1.42
)。后面我们会介绍如何修改ssh
配置文件使我们可以用类似ssh bar
这样的命令来登录服务器。ssh
:用于登录远程机器并在远程机器上执行命令。 -> (secure shell)该命令的基本格式如下:
[root@localhost ~]#ssh [OPTIONS] [-p PORT] [USER@]HOSTNAME [COMMAND]
★执行命令
ssh
的一个经常被忽视的特性是它可以直接远程执行命令。ssh foobar@server ls
可以直接用foobar
的用户身份在服务器上执行ls
命令。 想要配合管道来使用也可以,ssh foobar@server ls | grep PATTERN
会在本地查询远端ls
的输出而ls | ssh foobar@server grep PATTERN
会在远端对本地ls
输出的结果进行查询。
SSH 密钥
- 基于密钥的验证机制使用了密码学中的公钥,我们只需要向服务器证明客户端持有对应的私钥,而不需要公开其私钥。这样您就可以避免每次登录都输入密码的麻烦了。不过,私钥(通常是
~/.ssh/id_rsa
或者~/.ssh/id_ed25519
) 等效于您的密码,所以一定要好好保存它。
密钥生成
使用
ssh-keygen
命令可以生成一对密钥:1
ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519
您可以为密钥设置密码,防止有人持有您的私钥并使用它访问您的服务器。您可以使用
ssh-agent
或gpg-agent
,这样就不需要每次都输入该密码了。**
ssh-keygen
**:用于OpenSSH
身份验证密钥的生成、管理和转换,它支持 RSA 和 DSA 两种认证密钥。->(ssh key generate)该命令的基本格式如下:
[root@localhost ~]#ssh-keygen [OPTIONS] <file>...
-a
:rounds,保存私钥时,此选项指定使用的 KDF(密钥派生函数)轮数。-t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | RSA
:指定要创建的密钥类型,可能的值为“dsa”、“ecdsa”、“ecdsa-sk”、“ed25519”、“ed25519-sk”或“rsa”。-f
:指定密钥文件的文件名。
基于密钥的认证机制
ssh
会查询.ssh/authorized_keys
来确认那些用户可以被允许登录。您可以通过下面的命令将前面生成的公钥拷贝到服务器中:1
cat .ssh/id_ed25519.pub | ssh foobar@remote 'cat >> ~/.ssh/authorized_keys'
通过 SSH 复制文件
- 使用
ssh
复制文件有很多方法:ssh+tee
, 最简单的方法是执行ssh
命令,然后通过这样的方法利用标准输入实现cat localfile | ssh remote_server tee serverfile
。scp
:当需要拷贝大量的文件或目录时,使用scp
命令则更加方便,因为它可以方便的遍历相关路径。语法如下:scp path/to/local_file user@remote_host:path/to/remote_file
。rsync
对scp
进行了改进,它可以检测本地和远端的文件以防止重复拷贝。它还可以提供一些诸如符号连接、权限管理等精心打磨的功能。甚至还可以基于--partial
标记实现断点续传。rsync
的语法和scp
类似。
端口转发
很多情况下我们都会遇到软件需要监听特定设备的端口。如果是在您的本机,可以使用
localhost:PORT
或127.0.0.1:PORT
。但是如果需要监听远程服务器的端口该如何操作呢?这种情况下远端的端口并不会直接通过网络暴露给您。此时就需要进行端口转发。端口转发有两种:本地端口转发和远程端口转发。
本地端口转发
- 常见的情景是使用本地端口转发,即远端设备上的服务监听一个端口,而您希望在本地设备上的一个端口建立连接并转发到远程端口上。例如,我们在远端服务器上运行 Jupyter notebook 并监听
8888
端口。 然后,建立从本地端口9999
的转发,使用ssh -L 9999:localhost:8888 foobar@remote_server
。这样只需要访问本地的localhost:9999
即可。
★SSH 配置
我们可以使用
~/.ssh/config
来进行ssh
配置。1
2
3
4
5
6
7
8
9
10Host vm
User foobar
HostName 172.16.174.141
Port 2222
IdentityFile ~/.ssh/id_ed25519
LocalForward 9999 localhost:8888
# 在配置文件中也可以使用通配符
#Host *.mit.edu
# User foobaz这时我们就不需要使用
ssh foobar@172.16.174.141
来访问远端设备了,而只需要使用命令ssh vm
即可。这么做的好处是,使用
~/.ssh/config
文件来创建别名,类似scp
、rsync
和mosh
的这些命令都可以读取这个配置并将设置转换为对应的命令行选项。注意,
~/.ssh/config
文件也可以被当作配置文件,而且一般情况下也是可以被导入其他配置文件的。不过,如果您将其公开到互联网上,那么其他人都将会看到您的服务器地址、用户名、开放端口等等。这些信息可能会帮助到那些企图攻击您系统的黑客,所以请务必三思。服务器侧的配置通常放在
/etc/ssh/sshd_config
。您可以在这里配置免密认证、修改 ssh 端口、开启 X11 转发等等。 您也可以为每个用户单独指定配置。