[MIT公开课(计算机教育中缺失的一课)]5.命令行环境

(该系列文章大部分内容来源于MIT课程笔记,加入了个人的理解、原笔记中没有的细节和其他的需要理解的内容,公开课地址:https://www.bilibili.com/video/BV14E411J7n2?p=1)

上一讲:数据整理
下一讲:版本控制(git)


目录

  • 用户更改密码
  • 进程控制
      • 查看进程(ps)
      • 动态查看进程(top)
      • 结束进程
      • 暂停和后台执行进程
  • 终端多路复用
  • 别名
  • 配置文件(Dotfiles)
      • 可移植性
  • 远端设备
      • 执行命令
      • SSH 密钥
        • 密钥生成
        • 基于密钥的认证机制
      • 通过 SSH 复制文件
      • SSH 配置
      • 端口转发
        • 杂项
  • Shell & 框架
  • 终端模拟器
  • 课后练习
      • 任务控制
      • 终端多路复用
      • 别名
      • 配置文件
      • 远端设备


我们将学习一些能够改善您的 shell 及其他工具的工作流的方法,这主要是通过定义别名或基于配置文件对其进行配置来实现的。这些方法都可以帮您节省大量的时间。例如,仅需要执行一些简单的命令,我们就可以在所有的主机上使用相同的配置。我们还会学习如何使用 SSH 操作远端机器。


用户更改密码

passwd [user]

passwd 命令将会试着强迫你使用“强”密码。这意味着它会拒绝接受太短的密码、与先前 相似的密码、字典中的单词作为密码或者是太容易猜到的密码:(macOS上并不会提示)

[me@linuxbox ~]$ passwd
(current) UNIX password:
New UNIX password:
BAD PASSWORD: is too similar to the old one New UNIX password:
BAD PASSWORD: it is WAY too short
New UNIX password:
BAD PASSWORD: it is based on a dictionary word

进程控制

当系统启动的时候,内核先把一些它自己的活动初始化为进程,然后运行一个叫做 init 的程序,再运行一系列的称为 init 脚本的 shell脚本(位于/etc),它们可以启动所有的系统服务。系统分配给每个进程一个数字,这 个数字叫做进程 (process) ID 或 PIDPID 号按升序分配,init 进程的 PID 总是 1。内核也对分配给每个进程的内存和就绪状态进行跟踪以便继续执行这个进程。

查看进程(ps)

ps(process status)有许多选项,它最简单地使用形式是这样的:

➜  Downloads  ps
  PID TTY           TIME CMD
 7959 ttys000    0:09.23 -zsh

默认情况下,ps 不会显示很多进程信息,只是列出与当前终端会话相关的进程。TTY 是 “Teletype”(直译电传打字机) 的简写,是指进程的控制终端。TTY 足足显示了 Unix 的年代久远。TIME 字段表示进程所消耗的 CPU 时间数量。

加上 x 选项(注意没有开头的- 字符),告诉 ps 命令,展示所有进程,不管它们由什么终端(如果有的话)控制:

➜  Downloads  ps x
  PID   TT  STAT      TIME COMMAND
  316   ??  S      0:00.37 /System/Library/Frameworks/Lo
  317   ??  S      0:16.85 /usr/sbin/cfprefsd agent
  322   ??  S      0:15.84 /usr/libexec/UserEventAgent 
  324   ??  S      0:00.99 /System/Library/PrivateFramew

输出结果中,新添加了一栏 STATSTAT 是 “state” 的简写,它揭示了进程当前状态:

状态 含义
R 运行中。这意味着,进程正在运行或准备运行。
S 正在睡眠。进程没有运行,而是,正在等待一个事件,比如 说,一个按键或者网络分组。
D 不可中断睡眠。进程正在等待 I/O,比方说,一个磁盘驱动 器的 I/O。
T 已停止. 已经指示进程停止运行。稍后介绍更多。
Z 一个死进程或“僵尸”进程。这是一个已经终止的子进程, 但是它的父进程还没有清空它。(父进程没有把子进程从进程表中删除)
< 一个高优先级进程。这可能会授予一个进程更多重要的资 源,给它更多的 CPU 时间。进程的这种属性叫做 niceness。 具有高优先级的进程据说是不好的(less nice),因为它占用了比较多的 CPU 时间,这样就给其它进程留下很少时间。
N 低优先级进程。一个低优先级进程(一个“nice”进程)只有 当其它高优先级进程被服务了之后,才会得到处理器时间。

动态查看进程(top)

top 以进程活动顺序显示连续更新的系统进程列表(默认情况下,每三秒钟更新一次),top 显示结果由两部分组成:最上面是系统概要,下面是进程列表,以 CPU 的使用率排序。

结束进程

您的 shell 会使用 UNIX 提供的信号机制执行进程间通信。当一个进程接收到信号时,它会停止执行、处理该信号并基于信号传递的信息来改变其执行。就这一点而言,信号是一种软件中断。在上面的例子中,当我们输入 Ctrl-C 时,shell 会发送一个SIGINT(signal interrupt)信号到进程。(可以通过man signal查看其他所有信号)

下面这个 Python 程序向您展示了捕获信号SIGINT 并忽略它的基本操作,它并不会让程序停止。为了停止这个程序,我们需要使用SIGQUIT信号,通过输入Ctrl-\可以发送该信号。

#!/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

运行结果:

lilhoe@LilHoedeMacBook-Pro Downloads % python test.py
42^C
I got a SIGINT, but I am not stopping
66^\zsh: quit       /Library/Frameworks/Python.framework/Versions/3.8/bin/python3.8 test.py
lilhoe@LilHoedeMacBook-Pro Downloads %

如果我们向这个程序发送两次 SIGINT ,然后再发送一次 SIGQUIT,程序会有什么反应?注意 ^ 是我们在终端输入Ctrl 时的表示形式:

$ python test.py
24^C
I got a SIGINT, but I am not stopping
26^C
I got a SIGINT, but I am not stopping
30^\[1]    39913 quit       python test.py

尽管 SIGINT 和 SIGQUIT 都常常用来发出和终止程序相关的请求。SIGTERM 则是一个更加通用的、也更加优雅地退出信号。为了发出这个信号我们需要使用 kill 命令, 它的语法是: kill -TERM (输入kill -l查看kill命令详细参数)。

暂停和后台执行进程

信号可以让进程做其他的事情,而不仅仅是终止它们。例如,SIGSTOP 会让进程暂停。在终端中,键入 Ctrl-Z 会让 shell 发送 SIGTSTP 信号。

我们可以使用 fg(返回前台继续运行,重新连接标准输出)或 bg(移到后台继续运行)命令恢复暂停的工作。它们分别表示在前台继续或在后台继续。

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

命令中的 & 后缀可以让命令在直接在后台运行,这使得您可以直接在 shell 中继续做其他操作,不过它此时还是会使用 shell 的标准输出,这一点有时会比较恼人(这种情况可以使用 shell 重定向处理)。

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

下面这个简单的会话中展示来了些概念的应用。

lilhoe@LilHoedeMacBook-Pro Downloads % nohup sleep 2000 & 
[3] 45956
lilhoe@LilHoedeMacBook-Pro Downloads % appending output to nohup.out
lilhoe@LilHoedeMacBook-Pro Downloads % jobs
[1]    running    sleep 100000
[2]  - running    sleep 100000
[3]  + running    nohup sleep 2000
$ 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  # 2进程是nohup产生的进程,忽略SIGHUP命令

$ jobs
[2]  + running    nohup sleep 2000

$ kill %2  # 取消进程
[2]  + 18745 terminated  nohup sleep 2000

$ jobs

若要唤醒1进程,可以使用bg+%+进程序号:(下图此刻1进程已处于running状态,若为suspended可以通过该命令变为runnning。)

[MIT公开课(计算机教育中缺失的一课)]5.命令行环境_第1张图片

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


终端多路复用

当您在使用命令行接口时,您通常会希望同时执行多个任务。距离来说,您可以想要同时运行您的编辑器,并在终端的另外一侧执行程序。尽管再打开一个新的终端窗口也能达到目的,使用终端多路复用器则是一种更好的办法。

像 tmux 这类的终端多路复用器可以允许我们基于面板和标签分割出多个终端窗口,这样您便可以同时与多个 shell 会话进行交互。不仅如此,终端多路复用使我们可以分离当前终端会话并在将来重新连接。这让您操作远端设备时的工作流大大改善,避免了 nohup 和其他类似技巧的使用。

现在最流行的终端多路器是 tmux。tmux 是一个高度可定制的工具,您可以使用相关快捷键创建多个标签页并在它们间导航。tmux 的快捷键需要我们掌握,它们都是类似 x 这样的组合,即需要先按下Ctrl+b,松开后再按下 x。tmux 中对象的继承结构如下:

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

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

  • 窗口(Windows):等同于其他编辑器里的标签,从视觉上将一个会话分割为多个部分。

c 创建一个新的窗口,使用 关闭
N跳转到第 N 个窗口,注意每个窗口都是有编号的
p切换到前一个窗口
n 切换到下一个窗口
重命名当前窗口
w 列出当前所有窗口

  • 面板(Panes):像 vim 中的分屏一样,面板使我们可以在一个屏幕里显示多个 shell。

" 水平分割
% 垂直分割
<方向> 切换到指定方向的面板,<方向> 指的是键盘上的方向键
z 切换当前面板的缩放
[ 开始往回卷动屏幕。您可以按下空格键来开始选择,回车键复制选中的部分
<空格> 在不同的面板排布间切换

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


别名

输入一长串包含许多选项的命令会非常麻烦。因此,大多数 shell 都支持设置别名。shell 的别名相当于一个长命令的缩写,shell 会自动将其替换成原本的命令。例如,bash 中的别名语法如下:

alias alias_name="command_to_alias arg1 arg2"

注意, =两边是没有空格的,因为 alias 是一个 shell 命令,它只接受一个参数。在终端输入alias可以查看系统默认设置的所有别名。

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

# 创建常用命令的缩写
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。举例:修改bash命令行的提示符:

vim ~/.bashrc

在文件中加入:

PS1="bash>"

保存退出,输入bash,提示符将会变化:
[MIT公开课(计算机教育中缺失的一课)]5.命令行环境_第2张图片

若修改为PS1="\w >",提示符变为当前目录+">",其他修改 bash 终端提示符可以参考这里,修改zsh终端可以参考这里。

删除别名,使用 unalias+别名 命令。


配置文件(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

我们应该如何管理这些配置文件呢,它们应该在它们的文件夹下,并使用版本控制系统进行管理,然后通过脚本将其 符号链接 到需要的地方:ln -s 指定符号链接 将路径存放到创建的文件中 你要创建的符号链接。(ln指令创建链接,使逻辑上相同的文件存储在不同的位置,可以理解为其中一个为实体文件,其他目录下的都是指向它的指针)

这么做有如下好处:

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

配置文件中需要放些什么?您可以通过在线文档和帮助手册了解所使用工具的设置项。另一个方法是在网上搜索有关特定程序的文章,作者们在文章中会分享他们的配置。还有一种方法就是直接浏览其他人的配置文件:您可以在这里找到无数的dotfiles仓库 —— 其中最受欢迎的那些可以在这里找到(建议您不要直接复制别人的配置)。这里 也有一些非常有用的资源。

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

可移植性

配置文件的一个常见的痛点是它可能并不能在多种设备上生效。例如,如果您在不同设备上使用的操作系统或者 shell 是不同的,则配置文件是无法生效的。或者,有时您仅希望特定的配置只在某些设备上生效。

有一些技巧可以轻松达成这些目的。如果配置文件 if 语句,则您可以借助它针对不同的设备编写不同的配置。例如,您的 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

然后我们可以在每天设备上创建配置文件 ~/.gitconfig_local 来包含与该设备相关的特定配置。您甚至应该创建一个单独的代码仓库来管理这些与设备相关的配置。

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

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

远端设备

如果您需要使用远程服务器来部署后端软件或您需要一些计算能力强大的服务器,您就会用到Secure Shell(SSH)。首先,它要认证远端主机是否为它所知道的那台主机(这样就阻止了所谓的“中间人”的攻击);其次,它加密了本地与远程主机之间所有的通讯信息。通过如下命令,您可以使用 ssh 连接到其他服务器(ssh username@IP/url):

ssh [email protected]

这里我们尝试以用户名 foo 登陆服务器 bar.mit.edu。服务器可以通过 URL 指定(例如bar.mit.edu),也可以使用 IP 指定(例如[email protected])。后面我们会介绍如何修改 ssh 配置文件使我们可以用类似 ssh bar 这样的命令来登陆服务器。

举例:本人有两台电脑,现从一个的终端远程登录另一个:

  1. 在Macbook的设置->网络->高级->TCP/IP里查看本机的IP地址
  2. 在终端查看自己的用户名,我的是hoesijia
  3. 在另一台电脑输入ssh [email protected],成功连接,可以进行远程操作。
  4. 需要断开连接输入exit

[MIT公开课(计算机教育中缺失的一课)]5.命令行环境_第3张图片

执行命令

ssh 的一个经常被忽视的特性是它可以直接远程执行命令。 ssh foobar@server ls 可以直接在用foobar的命令下执行 ls 命令。 想要配合管道来使用也可以, ssh foobar@server ls | grep PATTERN 会在本地查询远端 ls 的输出而 ls | ssh foobar@server grep PATTERN 会在远端对本地 ls 输出的结果进行查询。

比方说下面的例子,我们在远端系统中执行 ls 命令,并把命令输出重定向到本地系统中的一个文件里面:

[me@linuxbox ~]$ ssh remote-sys 'ls \*' > dirlist.txt 
me@twin4's password:

注意,上面的例子中使用了单引号。这样做是因为我们不想路径名展开操作在本地执行,而希望它在远端系统中被执行。同样地,如果我们想要把输出结果重定向到远端主机的文件中, 我们可以把重定向操作符和文件名都放到单引号里面:

[me@linuxbox ~]$ ssh remote-sys 'ls * > dirlist.txt'

SSH 密钥

基于密钥的验证机制使用了密码学中的公钥,我们只需要向服务器证明客户端持有对应的私钥,而不需要公开其私钥。这样您就可以避免每次登陆都输入密码的麻烦了=。不过,私钥(通常是 ~/.ssh/id_rsa 或者 ~/.ssh/id_ed25519) 等效于您的密码,所以一定要好好保存它。

密钥生成

使用 ssh-keygen 命令可以生成一对密钥:

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

您可以为密钥设置密码,防止有人持有您的私钥并使用它访问您的服务器,可以通过ls -a ~/.ssh/id*查看文件,检查是非配置成功。您可以使用 ssh-agentgpg-agent ,这样就不需要每次都输入该密码了,我的操作如下:

lilhoe@LilHoedeMacBook-Pro ~ % ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/lilhoe/.ssh/id_ed25519.
Your public key has been saved in /Users/lilhoe/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:lNayx2aWbFOGEfPWLBjHbZc/TISFKQSuQ/gGomebD94 [email protected]
The key's randomart image is:
+--[ED25519 256]--+
|         .B+..*o.|
|      . .o X.*oo.|
|   . o .=.+ B.=..|
|  . . +o.= = . o.|
| . o   =S @     .|
|  o o . .* .     |
|   +             |
|  . +            |
|   . E           |
+----[SHA256]-----+

lilhoe@LilHoedeMacBook-Pro ~ % ls -a ~/.ssh/id* 
/Users/lilhoe/.ssh/id_ed25519     /Users/lilhoe/.ssh/id_ed25519.pub

lilhoe@LilHoedeMacBook-Pro ~ % cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzUnKAiYXjsKgBFgUBaorggGzFG7D2Qb7Sp+EJpRmQ4 [email protected]

lilhoe@LilHoedeMacBook-Pro ~ % ssh-agent
SSH_AUTH_SOCK=/var/folders/3h/nm48hx4502s0fyvfc59cbgwh0000gn/T//ssh-jg5tIjpZWWXO/agent.2939; export SSH_AUTH_SOCK;
SSH_AGENT_PID=2940; export SSH_AGENT_PID;
echo Agent pid 2940;

如果您曾经配置过使用 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'

其中foobar填远程的用户名,remote填远程的IP或者url。

如果支持 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

例如,如果我们想要从 remote-sys远端系统的home目录下复制文档 document.txt,到我们本地系统的当前工作目录下,可以这样操作:

[me@linuxbox ~]$ scp remote-sys:document.txt . 
me@remote-sys's password:
document.txt
100% 5581 5.5KB/s 00:00

和 ssh 命令一样,如果所需的远端主机帐户名与本地系统中的不一致,那么你可以把用户名 添加到远端主机名的开头:

[me@linuxbox ~]$ scp bob@remote-sys:document.txt .
  • rsyncscp 进行来改进,它可以检测本地和远端的文件以防止重复拷贝。它还可以提供一些诸如符号连接、权限管理等精心打磨的功能。甚至还可以基于 --partial标记实现断点续传。rsync 的语法和scp类似。
rsync -avP . foobar@remote:cmd  # 复制整个文件夹并保存所有权限

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 转发等等。 您也可以为每个用户单独指定配置。

端口转发

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

端口转发有两种,一种是本地端口转发和远程端口转发(参考StackOverflow 文章)。

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

杂项

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

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


Shell & 框架

zsh是目前最流行的shell,是 bash 的超集并提供了一些方便的功能:

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

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

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

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


终端模拟器

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

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

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

课后练习

任务控制

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

  2. 如果您希望某个进程结束后再开始另外一个进程, 应该如何实现呢?在这个练习中,我们使用 sleep 60 & 作为先执行的程序。一种方法是使用 wait 命令。尝试启动这个休眠命令,然后待其结束后再执行 ls命令。
    但是,如果我们在不同的 bash 会话中进行操作,则上述方法就不起作用了。因为 wait只能对子进程起作用。之前我们没有提过的一个特性是,kill 命令成功退出时其状态码为 0 ,其他状态则是非0。kill -0 则不会发送信号,但是会在进程不存在时返回一个不为0的状态码。请编写一个 bash 函数pidwait ,它接受一个 pid 作为输入参数,然后一直等待直到该进程结束。您需要使用 sleep 来避免浪费 CPU 性能。

终端多路复用

请完成这个 tmux 教程 参考这些步骤来学习如何自定义 tmux。

别名

执行 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。

远端设备

进行下面的练习需要您先安装一个 Linux 虚拟机(如果已经安装过则可以直接使用),如果您对虚拟机尚不熟悉,可以参考这篇教程 来进行安装。

  1. 前往 ~/.ssh/ 并查看是否已经存在 SSH 密钥对。如果不存在,请使用ssh-keygen -o -a 100 -t ed25519来创建一个。建议为密钥设置密码然后使用ssh-agent,更多信息可以参考 这里;
  2. .ssh/config加入下面内容:
Host vm
    User username_goes_here
    HostName ip_goes_here
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 9999 localhost:8888
  • 使用 ssh-copy-id vm 将您的 ssh 密钥拷贝到服务器。
  • 使用python -m http.server 8888 在您的虚拟机中启动一个 Web 服务器并通过本机的http://localhost:9999 访问虚拟机上的 Web 服务器
  • 使用sudo vim /etc/ssh/sshd_config编辑 SSH 服务器配置,通过修改PasswordAuthentication的值来禁用密码验证。通过修改PermitRootLogin的值来禁用 root 登陆。然后使用sudo service sshd restart重启 ssh 服务器,然后重新尝试。
  • (附加题) 在虚拟机中安装 mosh 并启动连接。然后断开服务器/虚拟机的网络适配器。mosh可以恢复连接吗?
  • (附加题) 查看ssh-N-f 选项的作用,找出在后台进行端口转发的命令是什么?

参考:
https://missing-semester-cn.github.io/2020/command-line/

你可能感兴趣的:(linux,mac,shell,bash,mit)