任务控制 链接到标题

信号与终止进程 链接到标题

shell 会使用 unix 提供的信号机制来进行进程之间的通信,当一个进程接收到信号时,它会停止执行原来的任务、处理该信号、并基于该信号传递的信息来改变任务的执行,可以认为信号是一种 软件中断

下面这个 python 程序演示了捕获 SIGINT 信号并忽略该信号,即这个程序在收到 SIGINT 信号时,不会终止程序,我们需要使用 SIGQUIT 信号来停止这个程序,可以通过 <C-\> 来发送该信号。

#!/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), end="")
    i += 1

运行该程序,向该程序发送两次 SIGINT,然后发送一次 SIGQUIT,程序反应如下:

zwyyy in 🌐 d3855u in ~/missing-semester 13s
 ❯ python3 sig.py
28^C
I got a SIGINT, but I am not stopping
53^C
I got a SIGINT, but I am not stopping
63^\zsh: quit       python3 sig.py

注意 ^ 是我们在终端输入 Ctrl 时的表现形式。

暂停与后台执行进程 链接到标题

信号除了终止进程之外,也可以让进程做其他事情,例如 SIGSTOP 会让进程暂停,在终端中,键入 <C-z> 会让 shell 发送 SIGTSTP 信号(terminal 版本的 SIGSTOP)。

我们可以使用 fg 或者 bg 来恢复暂停的工作,它们分别表示在前台继续或者在后台继续。

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

在命令中添加 & 后缀可以让命令直接在后台运行,这样就能在 shell 中继续执行其他操作,但是该命令仍会使用 shell 的标准输出,例如 sleep 1000 &

对于正在运行的前台进程,我们输入 <C-z>,该进程就会被暂停,再输入 bg %<任务编号> 就能让该进程在后台运行。要注意的是,后台运行的进程仍然是当前 shell 的子进程,一旦关闭当前终端(会发送 SIGHUP 信号),这些后台的进程也会被终止,可以使用 nohup 运行进程来防止这种情况(忽略 SIGHUP)。

以下这个简单的会话中展示了上面这些概念的应用:

$ sleep 1000
^Z
[1]  + 18653 suspended  sleep 1000

$ nohup sleep 2000 &
[2] 18745
appending output to nohup.out

$ jobs
[1]  + suspended  sleep 1000
[2]  - running    nohup sleep 2000

$ bg %1
[1]  - 18653 continued  sleep 1000

$ jobs
[1]  - running    sleep 1000
[2]  + running    nohup sleep 2000

$ kill -STOP %1
[1]  + 18653 suspended (signal)  sleep 1000

$ jobs
[1]  + suspended (signal)  sleep 1000
[2]  - running    nohup sleep 2000

$ kill -SIGHUP %1
[1]  + 18653 hangup     sleep 1000

$ jobs
[2]  + running    nohup sleep 2000

$ kill -SIGHUP %2

$ jobs
[2]  + running    nohup sleep 2000

$ kill %2
[2]  + 18745 terminated  nohup sleep 2000

$ jobs

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

终端复用器:tmux 链接到标题

tmux 这类的终端多路复用器可以允许我们基于 panes 和 tabs 分割出多个终端窗口(即打开一个 terminal 窗口相当于打开了多个),这样您便可以同时与多个 shell 会话进行交互。

tmux 也允许我们分离当前 session 并在将来重新连接,被分离的 session 内的 shell 不会收到 SIGHUP 信号。

tmux 有三个核心概念:

  • sessions
    • windows
      • panes

这三个概念的层级如上所示。windows 可以理解为浏览器中的 tabs。

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 快速入门教程。

别名与配置文件 链接到标题

别名 链接到标题

当我们在 shell 中执行 alias ll="ls -lh" 后,执行 ll 命令就相当于执行 ls -lh

zwyyy in 🌐 d3855u in ~/missing-semester
 ❯ ls
nohup.out  sig.py  words.txt

zwyyy in 🌐 d3855u in ~/missing-semester
 ❯ ls -lh
total 968K
-rw------- 1 zwyyy zwyyy    0 Jun 15 15:29 nohup.out
-rw-r--r-- 1 zwyyy zwyyy  246 Jun 15 15:39 sig.py
-rw-r--r-- 1 zwyyy zwyyy 962K Jun 15 14:55 words.txt

zwyyy in 🌐 d3855u in ~/missing-semester
 ❯ alias ll="ls -lh"

zwyyy in 🌐 d3855u in ~/missing-semester
 ❯ ll
total 968K
-rw------- 1 zwyyy zwyyy    0 Jun 15 15:29 nohup.out
-rw-r--r-- 1 zwyyy zwyyy  246 Jun 15 15:39 sig.py
-rw-r--r-- 1 zwyyy zwyyy 962K Jun 15 14:55 words.txt

当我们关闭这个 shell 或者切换到另一个 shell 后,别名就不再生效,为了让别名对所有 shell 生效,需要将它写入 shell 的配置文件中,以我这里使用的 zsh 为例,在 ~/.zshrc 中追加 alias ll="ls -lh",再执行 source ~/.zshrc 后,别名就将一直生效。

配置文件 链接到标题

很多程序的配置都是通过纯文本格式的被称作 dotfile 的配置文件来完成的(之所以称为 dotfile,是因为它们的文件名以 . 开头,例如 ~/.vimrc。也正因为此,它们默认是隐藏文件,ls并不会显示它们)。

shell 的配置也是通过这类文件完成的。在启动时,您的 shell 程序会读取很多文件以加载其配置项。根据 shell 本身的不同,您从登录开始还是以交互的方式完成这一过程可能会有很大的不同。关于这一话题,这里 有非常好的资源

对于 bash来说,在大多数系统下,您可以通过编辑 .bashrc.bash_profile 来进行配置。在文件中您可以添加需要在启动时执行的命令,例如上文我们讲到过的别名,或者是您的环境变量。

实际上,很多程序都要求您在 shell 的配置文件中包含一行类似 export PATH="$PATH:/path/to/program/bin" 的命令,这样才能确保这些程序能够被 shell 找到。

还有一些其他的工具也可以通过 dotfile 进行配置:

  • bash - ~/.bashrc, ~/.bash_profile
  • git - ~/.gitconfig
  • vim - ~/.vimrc~/.vim 目录
  • ssh - ~/.ssh/config
  • tmux - ~/.tmux.conf

此外,也有一些工具,其配置文件位于 ~/.config/xxx/ 目录下:

  • fish - ~/.config/fish/config.fish
  • gdb - ~/.config/gdb/gdbinit
  • lazygit - ~/.config/lazygit/config.yaml

我们应该如何管理这些配置文件呢,它们应该在它们的文件夹下(例如统一放在 ~/.config),并使用版本控制系统进行管理,然后通过脚本将其 符号链接 到需要的地方。这么做有如下好处:

  • 安装简单: 如果您登录了一台新的设备,在这台设备上应用您的配置只需要几分钟的时间;
  • 可移植性: 您的工具在任何地方都以相同的配置工作
  • 同步: 在一处更新配置文件,可以同步到其他所有地方
  • 变更追踪: 您可能要在整个程序员生涯中持续维护这些配置文件,而对于长期项目而言,版本历史是非常重要的

连接远程设备 链接到标题

参见 ssh 简单教程