Post

服务器一些基本操作

服务器一些基本操作

用来整理的数据以及相关的应用场景。日志处理通常是一个比较典型的使用场景,因为我们经常需要在日志中查找某些信息,这种情况下通读日志是不现实的。

sed

sed 是一个基于文本编辑器 ed 构建的 “流编辑器” 。在 sed 中,您基本上是利用一些简短的命令来修改文件,而不是直接操作文件的内容(尽管您也可以选择这样做)。相关的命令非常多,但是最常用的是 s,即 替换 命令,例如我们可以这样写:

1
2
3
4
ssh myserver journalctl
 | grep sshd
 | grep "Disconnected from"
 | sed 's/.*Disconnected from //'

s 命令的语法如下:s/REGEX/SUBSTITUTION/, 其中 REGEX 部分是我们需要使用的正则表达式,而 SUBSTITUTION 是用于替换匹配结果的文本。sed 还可以非常方便的做一些事情,例如打印匹配后的内容,一次调用中进行多次替换搜索等。但是这些内容我们并不会在此进行介绍。sed 本身是一个非常全能的工具,但是在具体功能上往往能找到更好的工具作为替代品。

正则表达式

不同字符所表示的含义,根据正则表达式的实现方式不同,也会有所变化,这一点确实令人沮丧。常见的模式有:

  • . 除换行符之外的 “任意单个字符”
  • * 匹配前面字符零次或多次
  • + 匹配前面字符一次或多次
  • [abc] 匹配 a, bc 中的任意一个
  • (RX1|RX2) 任何能够匹配 RX1RX2 的结果
  • ^ 行首
  • $ 行尾

结束进程

  • SIGINT : 当我们输入 Ctrl-C 时,shell 会发送一个 SIGINT 信号到进程
    • 立刻终止程序且不生成core dump
    • 可以捕获,比如:
      • Python:KeyboardInterrupt
      • C/C++:signal(SIGINT, handler)
  • SIGQUIT:“更狠”的 Ctrl-C,通常由 Ctrl-** 触发(很少用),终止进程并且生成 core dump

    • core dump 把进程崩溃时的内存状态保存成文件(用于调试)

    • 可用 gdb 分析:

      1
      
      gdb ./program core
      
  • SIGTERM 则是一个更加通用的、也更加优雅地退出信号。为了发出这个信号我们需要使用 kill 命令,

    • 标准、推荐的终止信号(终止进程,不生成 core dump)
    • 它的语法是: kill -TERM <PID>
  • SIGTSTP 信号:键入 Ctrl-Z 会让 shell 发送 SIGTSTP 信号

    • 它不是让进程退出,而是让进程“暂停”(stop)
    • 可以使用 fgbg 命令恢复暂停的工作。它们分别表示在前台继续或在后台继续。

jobs 命令会列出当前终端会话中尚未完成的全部任务。您可以使用 pid 引用这些任务(也可以用 pgrep 找出 pid)。

1
2
3
4
5
$ jobs
[1]+  Stopped   ping 8.8.8.8
$ bg %1  #在后台继续进行(百分号 + 任务编号来选定任务)
$ jobs
[1]+  Running   ping 8.8.8.8 &
1
2
3
4
5
$ jobs
[1]+  Stopped   ping 8.8.8.8
$ fg %1  #在前台继续进行
$ jobs
[1]+  Running   ping 8.8.8.8 &

训练的时候

1
2
3
4
python train.py #前台跑
#发现占着终端但你还要敲别的命令:按 Ctrl+Z 暂停
bg #让它后台继续
fg #想看它的实时输出/或需要交互时

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

1
python train.py > train.log & #在后台运行

注意⚠️❗️❗️: 后台的进程仍然是您的终端进程的子进程,一旦您关闭终端(会发送另外一个信号 SIGHUP),这些后台的进程也会终止。为了防止这种情况发生可以使用 nohup(一个用来忽略 SIGHUP 的封装)来运行程序。针对已经运行的程序,可以使用 disown

1
2
nohup python train.py > train.log 2>&1 &

在 Linux/Unix 里,每个进程默认有 3 个输出通道(文件描述符 FD):

  • 0:标准输入 stdin(默认来自键盘)
  • 1:标准输出 stdout(默认打印到终端)
  • 2:标准错误 stderr(默认也打印到终端)

所以:

  • 1 是“正常输出”
  • 2 是“报错/警告输出”

  • 2>:对 FD 2(stderr) 做重定向
  • &1:表示“重定向到 FD 1(stdout)”,这里的 & 不是后台运行的那个 &,而是“把数字当作文件描述符”的标记
1
2
3
4
5
$ python train.py > train.log 2>&1 &
$ jobs
[1]  Running  python train.py > train.log 2>&1 &
$ disown %1 #把它从当前 shell 管理中移除:
#现在你可以直接关终端/断开 SSH,作业一般不会跟着死。

别名

利用alias 可以为长命令设置别名,shell 的别名相当于一个长命令的缩写,shell 会自动将其替换成原本的命令。

1
alias alias_name="command_to_alias arg1 arg2"
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'

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

配置文件

点文件:文件名以 . 开头,例如 ~/.vimrc的文件称为$Dot\ Files$ 经常作为配置文件存在(它们默认是隐藏文件,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

配置文件管理: 用版本控制系统进行管理,然后通过脚本将其 符号链接 到需要的地方这么做有如下好处:

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

配置文件中需要放些什么: 作者们在文章中会分享他们的配置。还有一种方法就是直接浏览其他人的配置文件:您可以在这里找到无数的 dotfiles 仓库 —— 其中最受欢迎的那些可以在 这里 找到(建议您不要直接复制别人的配置)。这里 也有一些非常有用的资源。

有时您仅希望特定的配置只在某些设备上生效,可以通过if的shell脚本语言来编写, 或者include功能

1
2
3
4
5
6
7
if [[ "$(uname)" == "Linux" ]]; then {do_something}; fi

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

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

远程服务器

通过如下命令,您可以使用 ssh 连接到其他服务器:

1
ssh foo@bar.mit.edu

这里我们尝试以用户名 foo 登录服务器 bar.mit.edu

PS:服务器可以通过 URL 指定(bar.mit.edu)也可以使用 IP 指定(foobar@192.168.1.42)。

执行命令

ssh foobar@server ls 可以直接在用 foobar 的命令下执行 ls 命令。

  • ssh foobar@server ls | grep PATTERN 会在本地查询远端 ls 的输出
  • ls | ssh foobar@server grep PATTERN 会在远端对本地 ls 输出的结果进行查询。

免密登陆

我们只需要向服务器证明客户端持有对应的私钥,而不需要公开其私钥。这样就可以避免每次登录都输入密码的麻烦了

  1. 使用 ssh-keygen 命令可以生成一对密钥:
1
ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519

PS

  • 可以使用 ssh-agentgpg-agent 来给密钥设置密码
  • 如果已经有了一个可用的密钥对。要检查您是否持有密码并验证它,您可以运行 ssh-keygen -y -f /path/to/key.
  1. ssh 会查询 .ssh/authorized_keys 来确认哪些用户可以被允许登录。您可以通过下面的命令将一个公钥拷贝到这里:
1
cat .ssh/id_ed25519.pub | ssh foobar@remote 'cat >> ~/.ssh/authorized_keys'

或者

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

通过 SSH 复制文件

  • ssh+tee, 最简单的方法是执行 ssh 命令,然后通过这样的方法利用标准输入实现 cat localfile | ssh remote_server tee serverfile。回忆一下,tee 命令会将标准输出写入到一个文件;

  • scp :当需要拷贝大量的文件或目录时。

    语法如下:

    1
    
    scp path/to/local_file remote_host:path/to/remote_file
    
  • rsyncscp 进行了改进,它可以检测本地和远端的文件以防止重复拷贝。它还可以提供一些诸如符号连接、权限管理等精心打磨的功能。甚至还可以基于 --partial 标记实现断点续传。rsync 的语法和 scp 类似

SSH 配置

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

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

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

1
2
3
4
5
6
7
8
9
10
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。您可以在这里配置免密认证、修改 ssh 端口、开启 X11 转发等等。 您也可以为每个用户单独指定配置。

杂项

高延迟国际漫游: Mosh(即 mobile shell )对 ssh 进行了改进,它允许连接漫游、间歇连接及智能本地回显。

远端文件夹挂载到本地 : sshfs 可以将远端服务器上的一个文件夹挂载到本地,,然后您就可以使用本地的编辑器了

作业

  1. 通过pgreppkill 来查找PID并且终止进程
1
2
3
4
5
6
7
8
oplisty@oplistydeMacBook-Air ~ % jobs
[1]  + running    sleep 10000
oplisty@oplistydeMacBook-Air ~ % pgrep -af "sleep 10000"
37930
oplisty@oplistydeMacBook-Air ~ % pkill -f "sleep 10000"
[1]  + terminated  sleep 10000
oplisty@oplistydeMacBook-Air ~ % jobs
oplisty@oplistydeMacBook-Air ~ % 

解释:

  • -a:显示完整命令行(而不是只显示进程名)
  • -f:匹配“完整命令行”(这样能匹配到 sleep 10000 这一整串)
  1. 同一个bash: 某个进程结束后再开始另外一个进程(使用 wait 命令。尝试启动这个休眠命令,然后待其结束后再执行 ls 命令。)
1
2
3
4
sleep 60 &
pid=$! #获取pid
wait "$pid"
ls

不同的bash :

原理:用 kill -0 <pid> 做“探活”,进程存在则返回 0,不存在返回非 0。循环里 sleep 一下避免空转占 CPU。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pidwait () {
  local pid="$1"
  local interval="${2:-1}"   # 可选:轮询间隔秒,默认 1 秒

  # 参数检查
  if [[ -z "$pid" || ! "$pid" =~ ^[0-9]+$ ]]; then
    echo "usage: pidwait <pid> [interval_seconds]" >&2
    return 2
  fi

  # 等待:只要进程还存在就继续睡
  while kill -0 "$pid" 2>/dev/null; do
    sleep "$interval"
  done

  return 0
}

kill 命令成功退出时其状态码为 0 ,其他状态则是非 0。kill -0 则不会发送信号,但是会在进程不存在时返回一个不为 0 的状态码。

This post is licensed under CC BY 4.0 by the author.