MIT 计算机教育中缺失的一课 笔记:命令行环境

Posted Chris

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MIT 计算机教育中缺失的一课 笔记:命令行环境相关的知识,希望对你有一定的参考价值。

_写在前面:本篇内容来自于 MIT 推出的课程:计算机教育中缺失的一课,这门课程介绍了命令行、强大的文本编辑器的使用、使用版本控制系统提供的多种特性等等。中文课程主页。

本篇内容为第五节:命令行环境。本节的主要内容如下:

  1. 如何同时执行多个不同的进程并追踪它们的状态、如何停止或暂停某个进程以及如何使进程在后台运行。
  2. 一些能够改善 shell 及其他工具的工作流的方法,这主要是通过定义别名或基于配置文件对其进行配置来实现的
  3. 如何使用 SSH 操作远端机器

任务控制

结束进程

Shell 会使用 UNIX 提供的信号机制执行进程间通信。当一个进程接收到信号时,它会停止执行、处理该信号并基于信号传递的信息来改变其执行。就这一点而言,信号是一种_软件中断_。

当我们输入 Ctrl-C 时,shell 会发送一个 SIGINT (interrupt program) 信号到进程。另外一个退出程序的信号:SIGQUIT (quit program) ,可以通过 Ctrl-\\ 触发。

尽管 SIGINTSIGQUIT 都常常用来发出和终止程序相关的请求。SIGTERM 则是一个更加通用的、也更加优雅地退出信号。为了发出这个信号我们需要使用 kill 命令, 它的语法是: kill -TERM <PID>

暂停和后台执行进程

在终端中,键入 Ctrl-Z 会让 shell 发送 SIGTSTP 信号。此时,程序被挂起,并没有结束。

使用 jobs 命令,可以查看当前会话中的进程。当程序被挂起时,可以使用 fg 命令,将挂起的程序继续执行,或者使用 bg 命令,将程序放到后台继续执行。可以通过 jobs 打印结果中的进程标号,来引用某一个进程,如进程标号为 [1],后续可以通过 bg %1 的方式来将该进程在后台运行,也可以通过 kill %1 的方式杀掉该进程。

执行命令时,在最后添加 & ,可以直接让程序在后台执行。但是注意,这个进程仍然是终端的子进程,所以关闭终端时,这个进程也会被终止(此时发送的信号为 SIGHUP)。为了防止这种情况,可以在程序最前使用 nohup 程序:nohup command command_options,此时关掉终端,程序仍然会继续执行。

以下代码演示了这些命令:

❯ sleep 1000
^Z
[1]  + 60227 suspended  sleep 1000
❯ nohup sleep 2000 &
[2] 60237
appending output to nohup.out
❯ jobs
[1]  + suspended  sleep 1000
[2]  - running    nohup sleep 2000
❯ bg %1
[1]  - 60227 continued  sleep 1000
❯ jobs
[1]  - running    sleep 1000
[2]  + running    nohup sleep 2000
❯ kill %1
[1]  - 60227 terminated  sleep 1000
❯ jobs
[2]  + running    nohup sleep 2000
❯ kill -SIGHUP %2
❯ jobs
[2]  + running    nohup sleep 2000
❯ kill %2
[2]  + 60237 terminated  nohup sleep 2000
❯ jobs

以下为 Linux 系统中的信号列表,具体可以通过 man signal 来查询:

     No    Name         Default Action       Description
     1     SIGHUP       terminate process    terminal line hangup
     2     SIGINT       terminate process    interrupt program
     3     SIGQUIT      create core image    quit program
     4     SIGILL       create core image    illegal instruction
     5     SIGTRAP      create core image    trace trap
     6     SIGABRT      create core image    abort program (formerly SIGIOT)
     7     SIGEMT       create core image    emulate instruction executed
     8     SIGFPE       create core image    floating-point exception
     9     SIGKILL      terminate process    kill program
     10    SIGBUS       create core image    bus error
     11    SIGSEGV      create core image    segmentation violation
     12    SIGSYS       create core image    non-existent system call invoked
     13    SIGPIPE      terminate process    write on a pipe with no reader
     14    SIGALRM      terminate process    real-time timer expired
     15    SIGTERM      terminate process    software termination signal
     16    SIGURG       discard signal       urgent condition present on socket
     17    SIGSTOP      stop process         stop (cannot be caught or ignored)
     18    SIGTSTP      stop process         stop signal generated from keyboard
     19    SIGCONT      discard signal       continue after stop
     20    SIGCHLD      discard signal       child status has changed
     21    SIGTTIN      stop process         background read attempted from control terminal
     22    SIGTTOU      stop process         background write attempted to control terminal
     23    SIGIO        discard signal       I/O is possible on a descriptor (see fcntl(2))
     24    SIGXCPU      terminate process    cpu time limit exceeded (see setrlimit(2))
     25    SIGXFSZ      terminate process    file size limit exceeded (see setrlimit(2))
     26    SIGVTALRM    terminate process    virtual time alarm (see setitimer(2))
     27    SIGPROF      terminate process    profiling timer alarm (see setitimer(2))
     28    SIGWINCH     discard signal       Window size change
     29    SIGINFO      discard signal       status request from keyboard
     30    SIGUSR1      terminate process    User defined signal 1
     31    SIGUSR2      terminate process    User defined signal 2

终端多路复用(Terminal multiplexer)

来自维基百科的解释:终端多路复用器是一种软件应用程序,可用于在单个终端显示器,终端仿真器窗口,PC /工作站系统控制台或远程登录会话中多路复用几个单独的基于伪终端的登录会话,或者从终端分离和重新附加会话。最常用的终端多路复用软件为 tmux

tmux 中有三个非常重要的概念,分别为:会话、窗口和面板,关于其有很多重要的快捷键需要掌握:

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

    • 在终端输入 tmux 开始一个新的会话
    • tmux new -s NAME 以指定名称开始一个新的会话
    • tmux ls 列出当前所有会话
    • tmux 中输入 <C-b> d (detach),将当前会话分离
    • tmux a 重新连接最后一个会话。您也可以通过 -t NAME 来指定具体的会话
  • 窗口 - 相当于编辑器或是浏览器中的标签页,从视觉上将一个会话分割为多个部分

    • <C-b> c (create)创建一个新的窗口,使用 <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> <空格> 在不同的面板排布间切换

别名设置

设置别名可以节省大量时间,将经常输入的命令设置为非常短的标记,用法为:alias alias_name="command arg1 arg2",以下是几个示例:

# 创建常用命令的缩写
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 的启动文件里,比如 .bashrc.zshrc

配置文件

一些常见的配置文件,位于用户目录下,以 . 开头:

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

管理这些配置文件推荐的方式:将这些文件都放到一个目录下,然后通过 git 控制版本,通过链接的方式,将文件链接到对应的文件上。这样的好处是显而易见的:如果有多台电脑需要配置,可以一键配置;并且更改配置也会同步到每一台电脑。

远端设备

通过 ssh 连接远程服务器,服务器可以通过 URL 指定(例如 bar.mit.edu),也可以使用 IP 指定(例如foobar@192.168.1.42)。

可以直接通过 ssh foobar@server ls 来在服务器上执行 ls 命令,只准备执行一条命令时非常方便。

使用秘钥连接远程服务器时,需要向服务器证明客户端持有对应的私钥。本地生成私钥:使用 ssh-keygen。将公钥传到服务器,有以下两种方式:

# 第一种方式,直接上传
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 复制文件,可以使用 scp 命令,语法为:scp path/to/local_file remote_host:path/to/remote_file。更好的是使用 rsync,它对 scp 进行了改进,可以检测本地和远端的文件以防止重复拷贝。

使用 SSH 端口转发

端口转发有两种:本地端口转发和远程端口转发。

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

SSH 配置文件

由于各种选项的存在,SSH 命令可能会很长,我们可以通过配置 ~/.ssh/config 文件,来保存服务器的配置,这个文件可以被 scprsync 等命令读取,转换为对应的命令行选项。配置示例:

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

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

课后练习

任务控制

  1. 我们可以使用类似 ps aux | grep 这样的命令来获取任务的 pid ,然后您可以基于 pid 来结束这些进程。但我们其实有更好的方法来做这件事。在终端中执行 sleep 10000 这个任务。然后用 Ctrl-Z 将其切换到后台并使用 bg 来继续它的执行。现在,使用 pgrep 来查找 pid 并使用 pkill 结束进程而不需要手动输入 pid。(提示:: 使用 -af 标记)。

    ❯ sleep 10000
    ^Z
    [1]  + 62750 suspended  sleep 10000
    ❯ bg
    [1]  + 62750 continued  sleep 10000
    ❯ pgrep sleep
    62750
    ❯ pkill -af sleep
    [1]  + 62750 terminated  sleep 10000
  2. 如果您希望某个进程结束后再开始另外一个进程, 应该如何实现呢?在这个练习中,我们使用 sleep 60 & 作为先执行的程序。一种方法是使用 wait 命令。尝试启动这个休眠命令,然后待其结束后再执行 ls 命令。

    ❯ sleep 10 &
    [3] 62850
    # wait 只能够等待当前 shell 的子进程
    ❯ wait 62850; ls
    [3]  + 62850 done       sleep 10
但是,如果我们在不同的 bash 会话中进行操作,则上述方法就不起作用了。因为 `wait` 只能对子进程起作用。之前我们没有提过的一个特性是,`kill` 命令成功退出时其状态码为 `0` ,其他状态则是非 `0`。`kill -0` 则不会发送信号,但是会在进程不存在时返回一个不为 `0` 的状态码。请编写一个 `bash` 函数 `pidwait` ,它接受一个 `pid` 作为输入参数,然后一直等待直到该进程结束。您需要使用 `sleep` 来避免浪费 `CPU` 性能。

`wait` 程序实现:
```bash
myWait() {
    pid=$1
    while true; do
        kill -0 $pid
        if [[ $? -eq 0 ]]; then
            echo "process exists, wait for 1 seconds..."
            sleep 1
        else
            break
        fi
    done
    echo "process finished!"
    ls
}
```
将程序保存为 `wait.sh` ,程序执行与结果:
```bash
❯ source wait.sh
❯ sleep 10
^Z
[1]  + 63612 suspended  sleep 10
❯ bg
[1]  + 63612 continued  sleep 10
❯ myWait 63612
process exists, wait for 1 seconds...
process exists, wait for 1 seconds...
process exists, wait for 1 seconds...
process exists, wait for 1 seconds...
process exists, wait for 1 seconds...
[1]  + 63612 done       sleep 10
myWait:kill:3: kill 63612 failed: no such process
process finished!
wait.sh
```

终端多路复用

  1. 请完成这个 tmux 教程:https://www.hamvocke.com/blog/a-quick-and-easy-guide-to-tmux/
  2. 参考这些步骤来学习如何自定义 tmux:https://www.hamvocke.com/blog/a-guide-to-customizing-your-tmux-conf/

    1. 配置文件位于:~/.tmux.conf
    2. 将前缀从 Ctrl+b 替换为 Ctrl+a,方便输入

      # remap prefix from \'C-b\' to \'C-a\'
      unbind C-b
      set-option -g prefix C-a
      bind-key C-a send-prefix
    3. 使用 | 将面板垂直分割,使用 - 将面板水平分割

      # split panes using | and -
      bind | split-window -h
      bind - split-window -v
      unbind \'"\'
      unbind %
    4. 更快捷地重载配置,使用 前缀+r

      # reload config file (change file location to your the tmux.conf you want to use)
      bind r source-file ~/.tmux.conf \\; display-message "Config reloaded..."
    5. 更快捷地切换面板,使用 Alt 键加方向键来切换面板

      # switch panes using Alt-arrow without prefix
      bind -n M-Left select-pane -L
      bind -n M-Right select-pane -R
      bind -n M-Up select-pane -U
      bind -n M-Down select-pane -D
    6. 开启鼠标模式(不一定需要)

      # Enable mouse mode (tmux 2.1 and above)
      set -g mouse on
    7. 停止自动重命名窗口,通过 , 手动重命名,每个窗口可以执行不同的上下文

别名

  1. 创建一个 dc 别名,它的功能是当我们错误的将 cd 输入为 dc 时也能正确执行。

    alias dc="cd"
  2. 执行 history | awk \'{$1="";print substr($0,2)}\' | sort | uniq -c | sort -n | tail -n 10 来获取您最常用的十条命令,尝试为它们创建别名。注意:这个命令只在 Bash 中生效,如果您使用 ZSH,使用 history 1 替换 history

配置文件

让我们帮助您进一步学习配置文件:

  1. 为您的配置文件新建一个文件夹,并设置好版本控制
  2. 在其中添加至少一个配置文件,比如说您的 shell,在其中包含一些自定义设置(可以从设置 $PS1 开始)。
  3. 建立一种在新设备进行快速安装配置的方法(无需手动操作)。最简单的方法是写一个 shell 脚本对每个文件使用 ln -s,也可以使用专用工具
  4. 在新的虚拟机上测试该安装脚本。
  5. 将您现有的所有配置文件移动到项目仓库里。
  6. 将项目发布到 GitHub。

我将我的 tmuxvimzsh 的配置文件上传到了 GitHub,欢迎基于此进行修改:https://github.com/chris-algo/dotfiles 。


广告时间

如果您喜欢我的文章,欢迎给我点赞关注!也欢迎关注我的公众号:算法小哥克里斯。

以上是关于MIT 计算机教育中缺失的一课 笔记:命令行环境的主要内容,如果未能解决你的问题,请参考以下文章

计算机教育中缺失的一课 - MIT - L10 - 大杂烩

计算机教育中缺失的一课 - MIT - L3 - 编辑器 (Vim)

计算机教育中缺失的一课 - MIT - L11 - Q&A

计算机教育中缺失的一课 - MIT - L9 - 安全和密码学

计算机教育中缺失的一课

Shell 工具和脚本:学习笔记