任务控制

结束进程

Ctrl+C, 停止命令的执行, 当我们输入时,shell会发送一个SIGINT信号到进程。

下面这个例子展示如何捕获信号SIGINT并忽而略他的基本操作,我们可以使用SIGQUIT信号来结束程序,输Ctrl-\即可。

 import signal, time

 def handler(signum, time):
     if signum == signal.SIGINT:
         print("\nI got a SIGINT, but I am not stopping")
     else:
         print("\nReceive a signal: {}".format(signum))

 signal.signal(signal.SIGINT, handler)
 i = 0
 while True:
         time.sleep(.1)
         print("\r{}".format(i))
         i += 1

结束程序:

^C
I got a SIGINT, but I am not stopping
70
71
72
73
^\[1]    41119 quit       ./sigint.py

尽管SIGINTSIGQUIT都常常用来发出和终止程序相关的请求。

SIGTERM则是一个更加通用的、也更加优雅的退出限号。

我们可以使用kill命令发出这个信号,它的语法是:kill -TERM <pid>

暂停和后台执行进程

SIGSTOP, 信号会让进程暂停,键入Ctrl-Z会让shell发送SIGSTP信号

fg, 恢复暂停的工作,前台继续

bg, 恢复暂停的工作,后台继续

jobs, 列出当前终端会话中尚未完成的全部任务,使用pid引用这些任务(也可用pgrep找出这些pid)。更加符合直觉的操作是您可以使用百分号 + 任务编号(jobs 会打印任务编号)来选取该任务。如果要选择最近的一个任务,可以使用 $! 这一特殊参数。

命令中的&可以让命令直接再后台运行,该程序此时还会使用标准输出

Ctrl-Z可以让已经在运行的进程转到后台运行,然后紧接着再输入bg。注意,后台的进程仍然是您的终端进程的子进程,一旦您关闭终端(会发送另外一个信号SIGHUP),这些后台的进程也会终止。为了防止这种情况发生,您可以使用 nohup (一个用来忽略 SIGHUP 的封装) 来运行程序。针对已经运行的程序,可以使用disown 。除此之外,您可以使用终端多路复用器来实现。

下面这个简单的例子展示了上述命令的使用:

^Z
[1]  + 42429 suspended  sleep 1000
 ✘ ⚙  ~  nohup sleep 2000 &
[2] 42442
appending output to nohup.out
 ⚙  ~  jobs
[1]  + suspended  sleep 1000
[2]  - running    nohup sleep 2000
 ⚙  ~  bg %1
[1]  - 42429 continued  sleep 1000
 ⚙  ~ 
 ⚙  ~  jobs
[1]  - running    sleep 1000
[2]  + running    nohup sleep 2000
 ⚙  ~  kill -STOP %1
[1]  + 42429 suspended (signal)  sleep 1000
 ⚙  ~  jobs
[1]  + suspended (signal)  sleep 1000
[2]  - running    nohup sleep 2000
 ⚙  ~  kill -SIGHUP %1
[1]  + 42429 hangup     sleep 1000
 ⚙  ~  jobs
[2]  + running    nohup sleep 2000
 ⚙  ~  kill -SIGHUP %2
 ⚙  ~  jobs
[2]  + running    nohup sleep 2000
 ⚙  ~ 
 ⚙  ~  kill  %2
[2]  + 42442 terminated  nohup sleep 2000
 ⚙  ~  jobs

SIGKILL是一个特殊的信号,它不能被进程捕获并且它会马上结束该进程。不过这样做会有一些副作用,例如留下孤儿进程。

可以通过man signal或者kill -t来获取更多关于信号的信息。

终端多路复用

tmux 这类的终端多路复用器可以允许我们基于面板和标签分割出多个终端窗口,这样您便可以同时与多个 shell 会话进行交互。

不仅如此,终端多路复用使我们可以分离当前终端会话并在将来重新连接。

这让您操作远端设备时的工作流大大改善,避免了 nohup 和其他类似技巧的使用。

tmux 是一个高度可定制的工具,您可以使用相关快捷键创建多个标签页并在它们间导航。

tmux 的快捷键需要我们掌握,它们都是类似 <C-b> x 这样的组合,即需要先按下Ctrl+b,松开后再按下 xtmux 中对象的继承结构如下:

  • 会话

    - 每个会话都是一个独立的工作区,其中包含一个或多个窗口

    • tmux 开始一个新的会话
    • tmux new -s NAME 以指定名称开始一个新的会话
    • tmux ls 列出当前所有会话
    • tmux 中输入 <C-b> d ,将当前会话分离
    • tmux a 重新连接最后一个会话。您也可以通过 -t 来指定具体的会话
  • 窗口

    - 相当于编辑器或是浏览器中的标签页,从视觉上将一个会话分割为多个部分

    • <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 切换当前面板的缩放
    • <C-b> [ 开始往回卷动屏幕。您可以按下空格键来开始选择,回车键复制选中的部分
    • <C-b> <空格> 在不同的面板排布间切换

扩展阅读: 这里 是一份 tmux 快速入门教程, 而这一篇 文章则更加详细,它包含了 screen 命令。您也许想要掌握 screen 命令,因为在大多数 UNIX 系统中都默认安装有该程序。

别名

bash中的别名语法如下:

alias alias_name="command_to_alias arg1 arg2"

注意, =两边是没有空格的,因为 alias 是一个 shell 命令,它只接受一个参数。

别名有许多很方便的特性:

# 创建常用命令的缩写
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'

在默认情况下 shell 并不会保存别名。为了让别名持续生效,您需要将配置放进 shell 的启动文件里,像是.bashrc.zshrc

配置文件

shell的配置都是通过配置文件完成的,如:.vimrc, 根据 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

这些脚本文件我们应该使用版本控制系统进行管理,然后通过脚本将其符号链接到需要的地方。例如:换了一台笔记本,我们就可以直接下载这些配置文件,在指定的地方使用ln -s进行链接,配置一台新笔记本也就几分钟时间。

您可以在这里找到无数的dotfiles 仓库 —— 其中最受欢迎的那些可以在这里找到(我们建议您不要直接复制别人的配置)。这里 也有一些非常有用的资源。

本课程的老师们也在 GitHub 上开源了他们的配置文件: Anish, Jon, Jose.

可移植性

配置文件的一个常见痛点是它可能并不能在多种设备上生效。为了适配多种操作系统或者shell,可以这样做

if [[ "$(uname)" == "Linux" ]]; then {do_something}; fi

# 使用和 shell 相关的配置时先检查当前 shell 类型
if [[ "$SHELL" == "zsh" ]]; then {do_something}; fi

# 您也可以针对特定的设备进行配置
if [[ "$(hostname)" == "myServer" ]]; then {do_something}; fi		

如果配置文件支持 include 功能,您也可以多加利用。例如:~/.gitconfig 可以这样编写:

[include]
    path = ~/.gitconfig_local

如果您希望在不同的程序之间共享某些配置,该方法也适用。例如,如果您想要在 bashzsh 中同时启用一些别名,您可以把它们写在 .aliases 里,然后在这两个 shell 里应用:

# Test if ~/.aliases exists and source it
if [ -f ~/.aliases ]; then
    source ~/.aliases
fi

远端设备

如果需要使用远程服务器来部署一些后端软件或做一些计算,我们会用到安全shell(SSH)。

例如:

ssh foo@bar.mit.edu

用户名foo,主机名bar.mit.edu

执行命令

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 命令可以生成一对密钥:

ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519

您可以为密钥设置密码,防止有人持有您的私钥并使用它访问您的服务器。您可以使用 ssh-agentgpg-agent ,这样就不需要每次都输入该密码了。

如果您曾经配置过使用 SSH 密钥推送到 GitHub,那么可能您已经完成了这里 介绍的这些步骤,并且已经有了一个可用的密钥对。要检查您是否持有密码并验证它,您可以运行 ssh-keygen -y -f /path/to/key.

基于密钥的认证机制

ssh 会查询 .ssh/authorized_keys 来确认那些用户可以被允许登陆。您可以通过下面的命令将一个公钥拷贝到这里:

cat .ssh/id_ed25519.pub | ssh foobar@remote 'cat >> ~/.ssh/authorized_keys'

如果支持 ssh-copy-id 的话,可以使用下面这种更简单的解决方案:

ssh-copy-id -i .ssh/id_ed25519.pub foobar@remote

通过 SSH 复制文件

使用 ssh 复制文件有很多方法:

  • ssh+tee, 最简单的方法是执行 ssh 命令,然后通过这样的方法利用标准输入实现 cat localfile | ssh remote_server tee serverfile。回忆一下,tee 命令会将标准输出写入到一个文件;
  • scp :当需要拷贝大量的文件或目录时,使用scp 命令则更加方便,因为它可以方便的遍历相关路径。语法如下:scp path/to/local_file remote_host:path/to/remote_file
  • rsyncscp 进行来改进,它可以检测本地和远端的文件以防止重复拷贝。它还可以提供一些诸如符号连接、权限管理等精心打磨的功能。甚至还可以基于 --partial标记实现断点续传。rsync 的语法和scp类似;

端口转发

很多情况下我们都会遇到软件需要监听特定设备的端口。如果是在您的本机,可以使用 localhost:PORT127.0.0.1:PORT。但是如果需要监听远程服务器的端口该如何操作呢?这种情况下远端的端口并不会直接通过网络暴露给您。

此时就需要进行 端口转发。端口转发有两种,一种是本地端口转发和远程端口转发(参见下图,该图片引用自这篇StackOverflow 文章)中的图片。

本地端口转发Local Port Forwarding

远程端口转发Remote Port Forwarding

常见的情景是使用本地端口转发,即远端设备上的服务监听一个端口,而您希望在本地设备上的一个端口建立连接并转发到远程端口上。例如,我们在远端服务器上运行 Jupyter notebook 并监听 8888 端口。 染后,建立从本地端口 9999 的转发,使用 ssh -L 9999:localhost:8888 foobar@remote_server 。这样只需要访问本地的 localhost:9999 即可。

SSH 配置

我们已经介绍了很多参数。为它们创建一个别名是个好想法,我们可以这样做:

alias my_server="ssh -i ~/.id_ed25519 --port 2222 -L 9999:localhost:8888 foobar@remote_server

不过,更好的方法是使用 ~/.ssh/config.

Host vm
    User foobar
    HostName 172.16.174.141
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 9999 localhost:8888

# 在配置文件中也可以使用通配符
Host *.mit.edu
    User foobaz

这么做的好处是,使用 ~/.ssh/config 文件来创建别名,类似 scprsyncmosh的这些命令都可以读取这个配置并将设置转换为对应的命令行选项。

注意,~/.ssh/config 文件也可以被当作配置文件,而且一般情况下也是可以被倒入其他配置文件的。不过,如果您将其公开到互联网上,那么其他人都将会看到您的服务器地址、用户名、开放端口等等。这些信息可能会帮助到那些企图攻击您系统的黑客,所以请务必三思。

服务器侧的配置通常放在 /etc/ssh/sshd_config。您可以在这里配置免密认证、修改 shh 端口、开启 X11 转发等等。 您也可以为每个用户单独指定配置。

杂项

连接远程服务器的一个常见痛点是遇到由关机、休眠或网络环境变化导致的掉线。如果连接的延迟很高也很让人讨厌。Mosh(即 mobile shell )对 ssh 进行了改进,它允许连接漫游、间歇连接及智能本地回显。

有时将一个远端文件夹挂载到本地会比较方便, sshfs 可以将远端服务器上的一个文件夹挂载到本地,然后您就可以使用本地的编辑器了。

Shell & 框架

在 shell 工具和脚本那节课中我们已经介绍了 bash shell,因为它是目前最通用的 shell,大多数的系统都将其作为默认 shell。但是,它并不是唯一的选项。

例如,zsh shell 是 bash 的超集并提供了一些方便的功能:

  • 智能替换, **
  • 行内替换/通配符扩展
  • 拼写纠错
  • 更好的 tab 补全和选择
  • 路径展开 (cd /u/lo/b 会被展开为 /usr/local/bin)

框架 也可以改进您的 shell。比较流行的通用框架包括preztooh-my-zsh。还有一些更精简的框架,它们往往专注于某一个特定功能,例如zsh 语法高亮zsh 历史子串查询。 像 fish 这样的 shell 包含了很多用户友好的功能,其中一些特性包括:

  • 向右对齐
  • 命令语法高亮
  • 历史子串查询
  • 基于手册页面的选项补全
  • 更智能的自动补全
  • 提示符主题

需要注意的是,使用这些框架可能会降低您 shell 的性能,尤其是如果这些框架的代码没有优化或者代码过多。您随时可以测试其性能或禁用某些不常用的功能来实现速度与功能的平衡。

终端模拟器

和自定义 shell 一样,花点时间选择适合您的 终端模拟器并进行设置是很有必要的。有许多终端模拟器可供您选择(这里有一些关于它们之间比较的信息)

您会花上很多时间在使用终端上,因此研究一下终端的设置是很有必要的,您可以从下面这些方面来配置您的终端:

  • 字体选择
  • 彩色主题
  • 快捷键
  • 标签页/面板支持
  • 回退配置
  • 性能(像 Alacritty 或者 kitty 这种比较新的终端,它们支持GPU加速)。