Systemd 入门教程:实战篇

一、开机启动

对于那些支持 Systemd 的软件,安装的时候,会自动在/usr/lib/systemd/system目录添加一个配置文件。

如果你想让该软件开机启动,就执行下面的命令(以httpd.service为例)。


$ sudo systemctl enable httpd

上面的命令相当于在/etc/systemd/system目录添加一个符号链接,指向/usr/lib/systemd/system里面的httpd.service文件。

这是因为开机时,Systemd只执行/etc/systemd/system目录里面的配置文件。这也意味着,如果把修改后的配置文件放在该目录,就可以达到覆盖原始配置的效果。

二、启动服务

设置开机启动以后,软件并不会立即启动,必须等到下一次开机。如果想现在就运行该软件,那么要执行systemctl start命令。


$ sudo systemctl start httpd

执行上面的命令以后,有可能启动失败,因此要用systemctl status命令查看一下该服务的状态。


$ sudo systemctl status httpd

httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled)
   Active: active (running) since 金 2014-12-05 12:18:22 JST; 7min ago
 Main PID: 4349 (httpd)
   Status: "Total requests: 1; Current requests/sec: 0; Current traffic:   0 B/sec"
   CGroup: /system.slice/httpd.service
           ├─4349 /usr/sbin/httpd -DFOREGROUND
           ├─4350 /usr/sbin/httpd -DFOREGROUND
           ├─4351 /usr/sbin/httpd -DFOREGROUND
           ├─4352 /usr/sbin/httpd -DFOREGROUND
           ├─4353 /usr/sbin/httpd -DFOREGROUND
           └─4354 /usr/sbin/httpd -DFOREGROUND

12月 05 12:18:22 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
12月 05 12:18:22 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
12月 05 12:22:40 localhost.localdomain systemd[1]: Started The Apache HTTP Server.

上面的输出结果含义如下。

  • Loaded行:配置文件的位置,是否设为开机启动
  • Active行:表示正在运行
  • Main PID行:主进程ID
  • Status行:由应用本身(这里是 httpd )提供的软件当前状态
  • CGroup块:应用的所有子进程
  • 日志块:应用的日志

三、停止服务

终止正在运行的服务,需要执行systemctl stop命令。

$ sudo systemctl stop httpd.service

有时候,该命令可能没有响应,服务停不下来。这时候就不得不”杀进程”了,向正在运行的进程发出kill信号。

$ sudo systemctl kill httpd.service

此外,重启服务要执行systemctl restart命令。


$ sudo systemctl restart httpd.service

四、读懂配置文件

一个服务怎么启动,完全由它的配置文件决定。下面就来看,配置文件有些什么内容。

前面说过,配置文件主要放在/usr/lib/systemd/system目录,也可能在/etc/systemd/system目录。找到配置文件以后,使用文本编辑器打开即可。

systemctl cat命令可以用来查看配置文件,下面以sshd.service文件为例,它的作用是启动一个 SSH 服务器,供其他用户以 SSH 方式登录。

$ systemctl cat sshd.service

[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
Type=simple
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

可以看到,配置文件分成几个区块,每个区块包含若干条键值对。

五、 [Unit] 区块:启动顺序与依赖关系。

Unit区块的Description字段给出当前服务的简单描述,Documentation字段给出文档位置。

接下来的设置是启动顺序和依赖关系,这个比较重要。

After字段:表示如果network.targetsshd-keygen.service需要启动,那么sshd.service应该在它们之后启动。

相应地,还有一个Before字段,定义sshd.service应该在哪些服务之前启动。

注意,AfterBefore字段只涉及启动顺序,不涉及依赖关系。

举例来说,某 Web 应用需要 postgresql 数据库储存数据。在配置文件中,它只定义要在 postgresql 之后启动,而没有定义依赖 postgresql 。上线后,由于某种原因,postgresql 需要重新启动,在停止服务期间,该 Web 应用就会无法建立数据库连接。

设置依赖关系,需要使用Wants字段和Requires字段。

Wants字段:表示sshd.servicesshd-keygen.service之间存在”弱依赖”关系,即如果”sshd-keygen.service”启动失败或停止运行,不影响sshd.service继续执行。

Requires字段则表示”强依赖”关系,即如果该服务启动失败或异常退出,那么sshd.service也必须退出。

注意,Wants字段与Requires字段只涉及依赖关系,与启动顺序无关,默认情况下是同时启动的。

六、[Service] 区块:启动行为

Service区块定义如何启动当前服务。

6.1 启动命令

许多软件都有自己的环境参数文件,该文件可以用EnvironmentFile字段读取。

EnvironmentFile字段:指定当前服务的环境参数文件。该文件内部的key=value键值对,可以用$key的形式,在当前配置文件中获取。

上面的例子中,sshd 的环境参数文件是/etc/sysconfig/sshd

配置文件里面最重要的字段是ExecStart

ExecStart字段:定义启动进程时执行的命令。

上面的例子中,启动sshd,执行的命令是/usr/sbin/sshd -D $OPTIONS,其中的变量$OPTIONS就来自EnvironmentFile字段指定的环境参数文件。

与之作用相似的,还有如下这些字段。

  • ExecReload字段:重启服务时执行的命令
  • ExecStop字段:停止服务时执行的命令
  • ExecStartPre字段:启动服务之前执行的命令
  • ExecStartPost字段:启动服务之后执行的命令
  • ExecStopPost字段:停止服务之后执行的命令

请看下面的例子。


[Service]
ExecStart=/bin/echo execstart1
ExecStart=
ExecStart=/bin/echo execstart2
ExecStartPost=/bin/echo post1
ExecStartPost=/bin/echo post2

上面这个配置文件,第二行ExecStart设为空值,等于取消了第一行的设置,运行结果如下。


execstart2
post1
post2

所有的启动设置之前,都可以加上一个连词号(-),表示”抑制错误”,即发生错误的时候,不影响其他命令的执行。比如,EnvironmentFile=-/etc/sysconfig/sshd(注意等号后面的那个连词号),就表示即使/etc/sysconfig/sshd文件不存在,也不会抛出错误。

6.2 启动类型

Type字段定义启动类型。它可以设置的值如下。

  • simple(默认值):ExecStart字段启动的进程为主进程
  • forking:ExecStart字段将以fork()方式启动,此时父进程将会退出,子进程将成为主进程
  • oneshot:类似于simple,但只执行一次,Systemd 会等它执行完,才启动其他服务
  • dbus:类似于simple,但会等待 D-Bus 信号后启动
  • notify:类似于simple,启动结束后会发出通知信号,然后 Systemd 再启动其他服务
  • idle:类似于simple,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合

下面是一个oneshot的例子,笔记本电脑启动时,要把触摸板关掉,配置文件可以这样写。


[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off

[Install]
WantedBy=multi-user.target

上面的配置文件,启动类型设为oneshot,就表明这个服务只要运行一次就够了,不需要长期运行。

如果关闭以后,将来某个时候还想打开,配置文件修改如下。


[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off start
ExecStop=/usr/bin/touchpad-off stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

上面配置文件中,RemainAfterExit字段设为yes,表示进程退出以后,服务仍然保持执行。这样的话,一旦使用systemctl stop命令停止服务,ExecStop指定的命令就会执行,从而重新开启触摸板。

6.3 重启行为

Service区块有一些字段,定义了重启行为。

KillMode字段:定义 Systemd 如何停止 sshd 服务。

上面这个例子中,将KillMode设为process,表示只停止主进程,不停止任何sshd 子进程,即子进程打开的 SSH session 仍然保持连接。这个设置不太常见,但对 sshd 很重要,否则你停止服务的时候,会连自己打开的 SSH session 一起杀掉。

KillMode字段可以设置的值如下。

  • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
  • process:只杀主进程
  • mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
  • none:没有进程会被杀掉,只是执行服务的 stop 命令。

接下来是Restart字段。

Restart字段:定义了 sshd 退出后,Systemd 的重启方式。

上面的例子中,Restart设为on-failure,表示任何意外的失败,就将重启sshd。如果 sshd 正常停止(比如执行systemctl stop命令),它就不会重启。

Restart字段可以设置的值如下。

  • no(默认值):退出后不会重启
  • on-success:只有正常退出时(退出状态码为0),才会重启
  • on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
  • on-abnormal:只有被信号终止和超时,才会重启
  • on-abort:只有在收到没有捕捉到的信号终止时,才会重启
  • on-watchdog:超时退出,才会重启
  • always:不管是什么退出原因,总是重启

对于守护进程,推荐设为on-failure。对于那些允许发生错误退出的服务,可以设为on-abnormal

最后是RestartSec字段。

RestartSec字段:表示 Systemd 重启服务之前,需要等待的秒数。上面的例子设为等待42秒。

七、[Install] 区块

Install区块,定义如何安装这个配置文件,即怎样做到开机启动。

WantedBy字段:表示该服务所在的 Target。

Target的含义是服务组,表示一组服务。WantedBy=multi-user.target指的是,sshd 所在的 Target 是multi-user.target

这个设置非常重要,因为执行systemctl enable sshd.service命令时,sshd.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。

Systemd 有默认的启动 Target。


$ systemctl get-default
multi-user.target

上面的结果表示,默认的启动 Target 是multi-user.target。在这个组里的所有服务,都将开机启动。这就是为什么systemctl enable命令能设置开机启动的原因。

使用 Target 的时候,systemctl list-dependencies命令和systemctl isolate命令也很有用。


# 查看 multi-user.target 包含的所有服务
$ systemctl list-dependencies multi-user.target

# 切换到另一个 target
# shutdown.target 就是关机状态
$ sudo systemctl isolate shutdown.target

一般来说,常用的 Target 有两个:一个是multi-user.target,表示多用户命令行状态;另一个是graphical.target,表示图形用户状态,它依赖于multi-user.target。官方文档有一张非常清晰的 Target 依赖关系图

八、Target 的配置文件

Target 也有自己的配置文件。


$ systemctl cat multi-user.target

[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes

注意,Target 配置文件里面没有启动命令。

上面输出结果中,主要字段含义如下。

Requires字段:要求basic.target一起运行。

Conflicts字段:冲突字段。如果rescue.servicerescue.target正在运行,multi-user.target就不能运行,反之亦然。

After:表示multi-user.targetbasic.target 、 rescue.service、 rescue.target之后启动,如果它们有启动的话。

AllowIsolate:允许使用systemctl isolate命令切换到multi-user.target

九、修改配置文件后重启

修改配置文件以后,需要重新加载配置文件,然后重新启动相关服务。


# 重新加载配置文件
$ sudo systemctl daemon-reload

# 重启相关服务
$ sudo systemctl restart foobar

Systemd (守护系统)–linux

官网: https://systemd.io/

Github:https://github.com/systemd/systemd

systemd 是什么?【来自维基百科】

systemdLinux电脑操作系统之下的一套中央化系统及设置管理程序(init),包括有守护进程程序库以及应用软件,由Lennart Poettering(英语:Lennart Poettering)带头开发。其开发目标是提供更优秀的框架以表示系统服务(英语:Service (systems architecture))间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell系统开销(英语:Computational overhead)的效果,最终代替现在常用的System VBSD风格init程序。

Systemd 是一系列工具的集合,其作用也远远不仅是启动操作系统,它还接管了后台服务、结束、状态查询,以及日志归档、设备管理、电源管理、定时任务等许多职责,并支持通过特定事件(如插入特定 USB 设备)和特定端口数据触发的 On-demand(按需)任务。

Systemd 的后台服务还有一个特殊的身份——它是系统中 PID 值为 1 的进程。

  1. 更少的进程

Systemd 提供了服务按需启动的能力,使得特定的服务只有在真定被请求时才启动。

  1. 允许更多的进程并行启动

在 SysV-init 时代,将每个服务项目编号依次执行启动脚本。Ubuntu 的 Upstart 解决了没有直接依赖的启动之间的并行启动。而 Systemd 通过 Socket 缓存、DBus 缓存和建立临时挂载点等方法进一步解决了启动进程之间的依赖,做到了所有系统服务并发启动。对于用户自定义的服务,Systemd 允许配置其启动依赖项目,从而确保服务按必要的顺序运行。

  1. 使用 CGroup 跟踪和管理进程的生命周期

在 Systemd 之间的主流应用管理服务都是使用 进程树 来跟踪应用的继承关系的,而进程的父子关系很容易通过 两次 fork 的方法脱离。

而 Systemd 则提供通过 CGroup 跟踪进程关系,引补了这个缺漏。通过 CGroup 不仅能够实现服务之间访问隔离,限制特定应用程序对系统资源的访问配额,还能更精确地管理服务的生命周期。

  1. 统一管理服务日志

Systemd 是一系列工具的集合, 包括了一个专用的系统日志管理服务:Journald。这个服务的设计初衷是克服现有 Syslog 服务的日志内容易伪造和日志格式不统一等缺点,Journald 用 二进制格式 保存所有的日志信息,因而日志内容很难被手工伪造。Journald 还提供了一个 journalctl 命令来查看日志信息,这样就使得不同服务输出的日志具有相同的排版格式, 便于数据的二次处理。

一、由来

历史上,Linux 的启动一直采用init进程。

下面的命令用来启动服务。


$ sudo /etc/init.d/apache2 start
# 或者
$ service apache2 start

这种方法有两个缺点。

一是启动时间长。init进程是串行启动,只有前一个进程启动完,才会启动下一个进程。

二是启动脚本复杂。init进程只是执行启动脚本,不管其他事情。脚本需要自己处理各种情况,这往往使得脚本变得很长。

二、Systemd 概述

Systemd 就是为了解决这些问题而诞生的。它的设计目标是,为系统的启动和管理提供一套完整的解决方案。

根据 Linux 惯例,字母d是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。使用了 Systemd,就不需要再用init了。Systemd 取代了initd,成为系统的第一个进程(PID 等于 1),其他进程都是它的子进程。

$ systemctl --version

上面的命令查看 Systemd 的版本。

Systemd 的优点是功能强大,使用方便,缺点是体系庞大,非常复杂。事实上,现在还有很多人反对使用 Systemd,理由就是它过于复杂,与操作系统的其他部分强耦合,违反”keep simple, keep stupid”的Unix 哲学

 Systemd 架构图

三、系统管理

Systemd 并不是一个命令,而是一组命令,涉及到系统管理的方方面面。

3.1 systemctl

systemctl是 Systemd 的主命令,用于管理系统。

# 重启系统
$ sudo systemctl reboot

# 关闭系统,切断电源
$ sudo systemctl poweroff

# CPU停止工作
$ sudo systemctl halt

# 暂停系统
$ sudo systemctl suspend

# 让系统进入冬眠状态
$ sudo systemctl hibernate

# 让系统进入交互式休眠状态
$ sudo systemctl hybrid-sleep

# 启动进入救援状态(单用户状态)
$ sudo systemctl rescue

3.2 systemd-analyze

systemd-analyze命令用于查看启动耗时。

systemd-analyze -h查看具体命令


# 查看启动耗时
$ systemd-analyze                                                                                       

# 查看每个服务的启动耗时
$ systemd-analyze blame

# 显示瀑布状的启动过程流
$ systemd-analyze critical-chain

# 显示指定服务的启动流
$ systemd-analyze critical-chain atd.service

3.3 hostnamectl

hostnamectl命令用于查看当前主机的信息。


# 显示当前主机的信息
$ hostnamectl

# 设置主机名。
$ sudo hostnamectl set-hostname rhel7

3.4 localectl

localectl命令用于查看本地化设置。

localectl -h用来查看具体命令


# 查看本地化设置
$ localectl

# 设置本地化参数。
$ sudo localectl set-locale LANG=en_GB.utf8
$ sudo localectl set-keymap en_GB

3.5 timedatectl

timedatectl命令用于查看当前时区设置。


# 查看当前时区设置
$ timedatectl

# 显示所有可用的时区
$ timedatectl list-timezones                                                                                   

# 设置当前时区
$ sudo timedatectl set-timezone America/New_York
$ sudo timedatectl set-time YYYY-MM-DD
$ sudo timedatectl set-time HH:MM:SS

3.6 loginctl

loginctl命令用于查看当前登录的用户。


# 列出当前session
$ loginctl list-sessions

# 列出当前登录用户
$ loginctl list-users

# 列出显示指定用户的信息
$ loginctl show-user ruanyf

四、Unit

4.1 含义

Systemd 可以管理所有系统资源。不同的资源统称为 Unit(单位)。

Unit 一共分成12种。

  • Service unit:系统服务
  • Target unit:多个 Unit 构成的一个组
  • Device Unit:硬件设备
  • Mount Unit:文件系统的挂载点
  • Automount Unit:自动挂载点
  • Path Unit:文件或路径
  • Scope Unit:不是由 Systemd 启动的外部进程
  • Slice Unit:进程组
  • Snapshot Unit:Systemd 快照,可以切回某个快照
  • Socket Unit:进程间通信的 socket
  • Swap Unit:swap 文件
  • Timer Unit:定时器

systemctl list-units命令可以查看当前系统的所有 Unit 。


# 列出正在运行的 Unit
$ systemctl list-units

# 列出所有Unit,包括没有找到配置文件的或者启动失败的
$ systemctl list-units --all

# 列出所有没有运行的 Unit
$ systemctl list-units --all --state=inactive

# 列出所有加载失败的 Unit
$ systemctl list-units --failed

# 列出所有正在运行的、类型为 service 的 Unit
$ systemctl list-units --type=service

4.2 Unit 的状态

systemctl status命令用于查看系统状态和单个 Unit 的状态。


# 显示系统状态
$ systemctl status

# 显示单个 Unit 的状态
$ sysystemctl status bluetooth.service

# 显示远程主机的某个 Unit 的状态
$ systemctl -H root@rhel7.example.com status httpd.service

除了status命令,systemctl还提供了三个查询状态的简单方法,主要供脚本内部的判断语句使用。


# 显示某个 Unit 是否正在运行
$ systemctl is-active application.service

# 显示某个 Unit 是否处于启动失败状态
$ systemctl is-failed application.service

# 显示某个 Unit 服务是否建立了启动链接
$ systemctl is-enabled application.service

4.3 Unit 管理

对于用户来说,最常用的是下面这些命令,用于启动和停止 Unit(主要是 service)。


# 立即启动一个服务
$ sudo systemctl start apache.service

# 立即停止一个服务
$ sudo systemctl stop apache.service

# 重启一个服务
$ sudo systemctl restart apache.service

# 杀死一个服务的所有子进程
$ sudo systemctl kill apache.service

# 重新加载一个服务的配置文件
$ sudo systemctl reload apache.service

# 重载所有修改过的配置文件
$ sudo systemctl daemon-reload

# 显示某个 Unit 的所有底层参数
$ systemctl show httpd.service

# 显示某个 Unit 的指定属性的值
$ systemctl show -p CPUShares httpd.service

# 设置某个 Unit 的指定属性
$ sudo systemctl set-property httpd.service CPUShares=500

4.4 依赖关系

Unit 之间存在依赖关系:A 依赖于 B,就意味着 Systemd 在启动 A 的时候,同时会去启动 B。

systemctl list-dependencies 命令列出一个 Unit 的所有依赖。


$ systemctl list-dependencies nginx.service

上面命令的输出结果之中,有些依赖是 Target 类型(详见下文),默认不会展开显示。如果要展开 Target,就需要使用--all参数。


$ systemctl list-dependencies --all nginx.service

五、Unit 的配置文件

5.1 概述

每一个 Unit 都有一个配置文件,告诉 Systemd 怎么启动这个 Unit 。

Systemd 默认从目录/etc/systemd/system/读取配置文件。但是,里面存放的大部分文件都是符号链接,指向目录/usr/lib/systemd/system/,真正的配置文件存放在那个目录。可以把自己定义的服务放在 /usr/lib/systemd/system/ 下面。配置文件主要放在 /usr/lib/systemd/system 目录,也可能在 /etc/systemd/system 目录

systemctl enable命令用于在上面两个目录之间,建立符号链接关系。


$ sudo systemctl enable clamd@scan.service
# 等同于
$ sudo ln -s '/usr/lib/systemd/system/clamd@scan.service' '/etc/systemd/system/multi-user.target.wants/clamd@scan.service'

创建软链接

ln  -s  [源文件或目录]  [目标文件或目录]

删除软链接

和删除普通的文件是一样的,删除都是使用rm来进行操作

不论是硬链接或软链接都不会将原本的目标文件完全复制一份,而只会占用非常少量的存储空间。

如果配置文件里面设置了开机启动,systemctl enable命令相当于激活开机启动。

与之对应的,systemctl disable命令用于在两个目录之间,撤销符号链接关系,相当于撤销开机启动。


$ sudo systemctl disable clamd@scan.service

配置文件的后缀名,就是该 Unit 的种类,比如sshd.socket。如果省略,Systemd 默认后缀名为.service,所以sshd会被理解成sshd.service

5.2 配置文件的状态

systemctl list-unit-files命令用于列出所有配置文件。


# 列出所有配置文件
$ systemctl list-unit-files

# 列出指定类型的配置文件
$ systemctl list-unit-files --type=service

这个命令会输出一个列表。


$ systemctl list-unit-files

UNIT FILE              STATE
chronyd.service        enabled
clamd@.service         static
clamd@scan.service     disabled

这个列表显示每个配置文件的状态,一共有四种。

  • enabled:已建立启动链接
  • disabled:没建立启动链接
  • static:该配置文件没有[Install]部分(无法执行),只能作为其他配置文件的依赖
  • masked:该配置文件被禁止建立启动链接

注意,从配置文件的状态无法看出,该 Unit 是否正在运行。这必须执行前面提到的systemctl status命令。


$ systemctl status bluetooth.service

一旦修改配置文件,就要让 SystemD 重新加载配置文件,然后重新启动,否则修改不会生效。


$ sudo systemctl daemon-reload
$ sudo systemctl restart httpd.service

5.3 配置文件的格式

配置文件就是普通的文本文件,可以用文本编辑器打开。

systemctl cat命令可以查看配置文件的内容。


$ systemctl cat atd.service

[Unit]
Description=ATD daemon

[Service]
Type=forking
ExecStart=/usr/bin/atd

[Install]
WantedBy=multi-user.target

从上面的输出可以看到,配置文件分成几个区块。每个区块的第一行,是用方括号表示的区别名,比如[Unit]。注意,配置文件的区块名和字段名,都是大小写敏感的。

每个区块内部是一些等号连接的键值对。


[Section]
Directive1=value
Directive2=value

. . .

注意,键值对的等号两侧不能有空格。

5.4 配置文件的区块

[Unit]
Description=Protect ARP list
Wants=network-online.target
After=network.target
  • 其中network.target代表有网路,network-online.target代表一个连通着的网络。

[Unit]区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系。它的主要字段如下。

  • Description:简短描述
  • Documentation:文档地址
  • Requires:当前 Unit 依赖的其他 Unit,如果它们没有运行,当前 Unit 会启动失败
  • Wants:与当前 Unit 配合的其他 Unit,如果它们没有运行,当前 Unit 不会启动失败
  • BindsTo:与Requires类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行
  • Before:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之后启动
  • After:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之前启动
  • Conflicts:这里指定的 Unit 不能与当前 Unit 同时运行
  • Condition...:当前 Unit 运行必须满足的条件,否则不会运行
  • Assert...:当前 Unit 运行必须满足的条件,否则会报启动失败

[Install]通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。它的主要字段如下。

  • WantedBy:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中
  • RequiredBy:它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中
  • Alias:当前 Unit 可用于启动的别名
  • Also:当前 Unit 激活(enable)时,会被同时激活的其他 Unit

[Service]区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块。它的主要字段如下。

  • Type:定义启动时的进程行为。它有以下几种值。
  • Type=simple:默认值,执行ExecStart指定的命令,启动主进程
  • Type=forking:以 fork 方式从父进程创建子进程,创建后父进程会立即退出
  • Type=oneshot:一次性进程,Systemd 会等当前服务退出,再继续往下执行
  • Type=dbus:当前服务通过D-Bus启动
  • Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行
  • Type=idle:若有其他任务执行完毕,当前服务才会运行
  • ExecStart:启动当前服务的命令
  • ExecStartPre:启动当前服务之前执行的命令
  • ExecStartPost:启动当前服务之后执行的命令
  • ExecReload:重启当前服务时执行的命令
  • ExecStop:停止当前服务时执行的命令
  • ExecStopPost:停止当其服务之后执行的命令
  • RestartSec:自动重启当前服务间隔的秒数
  • Restart:定义何种情况 Systemd 会自动重启当前服务,可能的值包括always(总是重启)、on-successon-failureon-abnormalon-aborton-watchdog
  • TimeoutSec:定义 Systemd 停止当前服务之前等待的秒数
  • Environment:指定环境变量

Unit 配置文件的完整字段清单,请参考官方文档

六、Target

启动计算机的时候,需要启动大量的 Unit。如果每一次启动,都要一一写明本次启动需要哪些 Unit,显然非常不方便。Systemd 的解决方案就是 Target。

简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。从这个意义上说,Target 这个概念类似于”状态点”,启动某个 Target 就好比启动到某种状态。

传统的init启动模式里面,有 RunLevel 的概念,跟 Target 的作用很类似。不同的是,RunLevel 是互斥的,不可能多个 RunLevel 同时启动,但是多个 Target 可以同时启动。


# 查看当前系统的所有 Target
$ systemctl list-unit-files --type=target

# 查看一个 Target 包含的所有 Unit
$ systemctl list-dependencies multi-user.target

# 查看启动时的默认 Target
$ systemctl get-default

# 设置启动时的默认 Target
$ sudo systemctl set-default multi-user.target

# 切换 Target 时,默认不关闭前一个 Target 启动的进程,
# systemctl isolate 命令改变这种行为,
# 关闭前一个 Target 里面所有不属于后一个 Target 的进程
$ sudo systemctl isolate multi-user.target

Target 与 传统 RunLevel 的对应关系如下。


Traditional runlevel      New target name     Symbolically linked to...

Runlevel 0           |    runlevel0.target -> poweroff.target
Runlevel 1           |    runlevel1.target -> rescue.target
Runlevel 2           |    runlevel2.target -> multi-user.target
Runlevel 3           |    runlevel3.target -> multi-user.target
Runlevel 4           |    runlevel4.target -> multi-user.target
Runlevel 5           |    runlevel5.target -> graphical.target
Runlevel 6           |    runlevel6.target -> reboot.target

它与init进程的主要差别如下。

(1)默认的 RunLevel(在/etc/inittab文件设置)现在被默认的 Target 取代,位置是/etc/systemd/system/default.target,通常符号链接到graphical.target(图形界面)或者multi-user.target(多用户命令行)。

(2)启动脚本的位置,以前是/etc/init.d目录,符号链接到不同的 RunLevel 目录 (比如/etc/rc3.d/etc/rc5.d等),现在则存放在/lib/systemd/system/etc/systemd/system目录。

(3)配置文件的位置,以前init进程的配置文件是/etc/inittab,各种服务的配置文件存放在/etc/sysconfig目录。现在的配置文件主要存放在/lib/systemd目录,在/etc/systemd目录里面的修改可以覆盖原始设置。

七、日志管理

Systemd 统一管理所有 Unit 的启动日志。带来的好处就是,可以只用journalctl一个命令,查看所有日志(内核日志和应用日志)。日志的配置文件是/etc/systemd/journald.conf

journalctl功能强大,用法非常多。


# 查看所有日志(默认情况下 ,只保存本次启动的日志)
$ sudo journalctl

# 查看内核日志(不显示应用日志)
$ sudo journalctl -k

# 查看系统本次启动的日志
$ sudo journalctl -b
$ sudo journalctl -b -0

# 查看上一次启动的日志(需更改设置)
$ sudo journalctl -b -1

# 查看指定时间的日志
$ sudo journalctl --since="2012-10-30 18:17:16"
$ sudo journalctl --since "20 min ago"
$ sudo journalctl --since yesterday
$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00"
$ sudo journalctl --since 09:00 --until "1 hour ago"

# 显示尾部的最新10行日志
$ sudo journalctl -n

# 显示尾部指定行数的日志
$ sudo journalctl -n 20

# 实时滚动显示最新日志
$ sudo journalctl -f

# 查看指定服务的日志
$ sudo journalctl /usr/lib/systemd/systemd

# 查看指定进程的日志
$ sudo journalctl _PID=1

# 查看某个路径的脚本的日志
$ sudo journalctl /usr/bin/bash

# 查看指定用户的日志
$ sudo journalctl _UID=33 --since today

# 查看某个 Unit 的日志
$ sudo journalctl -u nginx.service
$ sudo journalctl -u nginx.service --since today

# 实时滚动显示某个 Unit 的最新日志
$ sudo journalctl -u nginx.service -f

# 合并显示多个 Unit 的日志
$ journalctl -u nginx.service -u php-fpm.service --since today

# 查看指定优先级(及其以上级别)的日志,共有8级
# 0: emerg
# 1: alert
# 2: crit
# 3: err
# 4: warning
# 5: notice
# 6: info
# 7: debug
$ sudo journalctl -p err -b

# 日志默认分页输出,--no-pager 改为正常的标准输出
$ sudo journalctl --no-pager

# 以 JSON 格式(单行)输出
$ sudo journalctl -b -u nginx.service -o json

# 以 JSON 格式(多行)输出,可读性更好
$ sudo journalctl -b -u nginx.serviceqq
 -o json-pretty

# 显示日志占据的硬盘空间
$ sudo journalctl --disk-usage

# 指定日志文件占据的最大空间
$ sudo journalctl --vacuum-size=1G

# 指定日志文件保存多久
$ sudo journalctl --vacuum-time=1years

Linux htop 详解

图片来自官网

htop是一款运行于Linux系统监控与进程管理软件,用于取代Unix下传统的top。与top只提供最消耗资源的进程列表不同,htop提供所有进程的列表,并且使用彩色标识出处理器、swap和内存状态。
用户一般可以在top无法提供详尽系统信息的情况下选择安装并使用htop。比如,在查找应用程序的内存泄漏问题时。与top相比,htop提供更方便、光标控制的界面来杀死进程。

安装: sudo apt install htop

官网: https://htop.dev/

github: https://github.com/htop-dev/htop

英文手册:https://www.geeksforgeeks.org/htop-command-in-linux-with-examples/

以上图为例:

Syntax:

htop [-dChusv]

Options:

-d –delay : Used to show the delay between updates, in tenths of seconds.
-C –no-color –no-colour : Start htop in monochrome mode.
-h –help : Used to display the help message and exit.
-u –user=USERNAME : Used to show only the processes of a given use
-p –pid=PID, PID… : Used to show only the given PIDs.
-s –sort-key COLUMN : Sort by this column (use –sort-key help for a column list).
-v –version : Output version information and exit.
-F --filter=FILTER   Show only the commands matching the given filter
-U --no-unicode                 Do not use unicode but plain ASCII
-H --highlight-changes[=DELAY]  Highlight new and old processes
-M --no-mouse                   Disable the mouse
-t --tree                       Show the tree view (can be combined with -s)

htop命令的部分

从前面的输出中,我们可以清楚地看到htop的显示分为3个部分:

头部:

头部分显示系统指标,包括CPU、内存和交换利用率、运行任务、平均负载和正常运行时间。

preview

红色边框:数字 1,2,3,4分别代表CPU处理器/核,上图是一个四核的处理器

灰色边框(progress bar)

每一个CPU的总用量情况,注意这条上面会有不同的颜色:

  1. 蓝色:显示低优先级(low priority)进程使用的CPU百分比。
  2. 绿色:显示用于普通用户(user)拥有的进程的CPU百分比。
  3. 红色:显示系统进程(kernel threads)使用的CPU百分比。
  4. 橙色:显示IRQ时间使用的CPU百分比。
  5. 洋红色(Magenta):显示Soft IRQ时间消耗的CPU百分比。
  6. 灰色:显示IO等待时间消耗的CPU百分比。
  7. 青色:显示窃取时间(Steal time)消耗的CPU百分比。

黄色边框

提供了内存(Memory)和交换(Swap)使用情况。 类似于CPU中的进度条,内存监视也包含具有多种颜色的进度条:

  1. 绿色:显示内存页面占用的RAM百分比
  2. 蓝色:显示缓冲区页面占用的RAM百分比
  3. 橙色:显示缓存页面占用的RAM百分比

swap行主要显示交换分区使用情况,当你发现你的交换分区(swap)已经派上用场的时候,说明你的物理内存已经不足,需要考虑增加内存了。

蓝色边框

  • 第一行 (Tasks, thr, running)
    • 参考上面的屏幕截图,我们在计算机上运行的106个任务(tasks)被分解为113个线程(thread),其中只有1个进程处于运行(running)状态。
    • 任务(tasks)是打开的进程总数的代表,但并不是每个打开的进程都在不断消耗CPU。 每个进程都处于几种状态
      • R: Running:表示进程(process)正在使用CPU
      • S: Sleeping: 通常进程在大多数时间都处于睡眠状态,并以固定的时间间隔执行小检查,或者等待用户输入后再返回运行状态。
      • T/S: Traced/Stoped: 表示进程正在处于暂停的状态
      • Z:Zombie or defunct:已完成执行但在进程表中仍具有条目的进程。
  • 第二行 Load Average
    • 三个值是指系统在最后1分钟,最近5分钟和最后15分钟的平均负载 (0.21,0.19,0.15)
  • 第三行 Uptime
    • 表示这个系统一共运行了多长的时间,这里一共运行了78天

主体:

所有正在运行的进程。

  • PID – 描述进程的ID号
  • USER – 描述进程的所有者(谁跑的)
  • PRI – 描述Linux内核查看的进程优先级
  • NI – 描述由用户或root重置的进程优先级
  • VIR – 它描述进程正在使用的虚拟内存 (virtual memory)
  • RES – 描述进程正在消耗的物理内存(physical memory)
  • SHR – 描述进程正在使用的共享内存(shared memory)
  • S – 描述流程的当前状态 (state)
  • CPU% – 描述每个进程消耗的CPU百分比
  • MEM% – 描述每个进程消耗的内存百分比
  • TIME+ – 显示自流程开始执行以来的时间
  • Command –它与每个进程并行显示完整的命令执行 (比如/usr/lib/R)

S:进程的运行状况

(1) R 表示正在运行
(2) S 表示休眠
(3) Z 表示僵死状态
(4) N 表示该进程优先值是负数

交互命令:

显示“htop菜单选项”

F1~F10 的功能和对应的字母快捷键:

  • Arrows, Page Up, Page Down, Home, End: Scroll the process list.
  • Space: Tag or untag a process.
  • – Untag all processes (remove all tags added with the Space key).
  • – Trace process system calls.
  • F1 – Help
  • F2 – setup
  • F3 – search
  • F4 – filtering: type in part of a process command line and only processes whose names match will be shown.
  • F5 – Tree view
  • F6 – Sorting.
  • F7 – Increase the selected process’s priority. This can only be done by the superuser.
  • F8 – Decrease the selected process’s priority.
  • F9 – Kill process.
  • F10 – Quit.

  • F2进入设置界面
  • F3是搜索进程

不区分大小写,可输入进程名搜索,

  • F4 是类似于过滤的功能

输入的筛选条件会一直保存,可以按ESC键清除!

更细致的解释:man htop

帮助: htop -h

linux web服务– curl

最近实验室服务器总是会断网,打算写一个自动登录校园网的脚本,然后就看到了这个curl命令,当然最终登录校园网还是使用的python request库实现的。顺便复习复习curl的用法。

官网: https://curl.se/

cURL无处不在。它几乎隐藏在所有设备中,例如汽车,蓝光播放器等。它通过互联网协议传输任意类型数据。

在本文中,我们将揭开cURL神秘命令行工具的面纱,解释它是如何成为一种通用代码的,并举例说明其用法。

cURL是什么意思?

cURL(客户端URL)是一个开放源代码的命令行工具,也是一个跨平台的库(libcurl),用于在服务器之间传输数据,并分发给几乎所有新的操作系统。cURL编程用于需要通过Internet协议发送或接收数据的几乎任何地方。

cURL支持几乎所有的互联网协议(DICT,FILE,FTP,FTPS,GOPHER,HTTP,HTTPS,IMAP,IMAPS,LDAP,LDAPS,MQTT,POP3,POP3S,RTMP,RTMPS,RTSP,SCP,SFTP,SMB,SMBS,SMTP ,SMTPS,TELNET和TFTP)。

cURL的历史

回到90年代的黑暗时代,那时每个人仍然使用命令行工具,Daniel Sterberg希望开发一个简单的IRC脚本,该脚本可以为聊天室成员转换货币。在1997年,建立互联网协议数据传递基础的方法不多,因此Httpget(基于HTTP的传输的几百行代码)成为cURL的起源。为了纪念其基础,它率先被称为HTTPGET 1.0。

几个月后,开发出了FTP的支持,就不得不删除该名称了。现在,它被称为urlget 2.0。经过几次更新后,在1998年3月30日,名称再次更改为现在众所周知的cURL 3.0。

cURL之前其实还有相似的 wget。我们不会过多介绍细节,但是wget和cURL之间的主要区别在于它们各自的下载功能,例如前者可以从中断的传输中恢复并继续下载。

cURL的作用是什么?

cURL旨在通过互联网协议传输数据。其他所有内容均不在其范围内。它甚至不处理传输的数据,仅执行传输流程。

cURL可用于调试。例如使用“ curl -v https://oxylabs.io ”可以显示一个连接请求的详细输出,包括用户代理,握手数据,端口等详细信息。

可列出和解释的cURL命令选项太多了。幸运的是,可以使用“ curl –help”这个选项,它列出了所有curl命令行的用法,并附有简短的解释性注释。尽管这些命令行用法里没有有关如何使用cURL的相关背景知识介绍,用户也能通过列表知道一些命令行的用法。

可以替代postman工具!!!

curl 是常用的命令行工具,用来请求 Web 服务器。它的名字就是客户端(client)的 URL 工具的意思。它的功能非常强大,命令行参数多达几十种。如果熟练的话,完全可以取代 Postman 这一类的图形界面工具。

1、不带有任何参数时,curl 就是发出 GET 请求。

$ curl https://www.example.com

上面命令向www.example.com发出 GET 请求,服务器返回的内容会在命令行输出。

2、-A参数指定客户端的用户代理标头,即User-Agent。curl 的默认用户代理字符串是curl/[version]
$ curl -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36' https://google.com

上面命令将User-Agent改成 Chrome 浏览器。


$ curl -A '' https://google.com

上面命令会移除User-Agent标头。

也可以通过-H参数直接指定标头,更改User-Agent


$ curl -H 'User-Agent: php/1.0' https://google.com
3、-b参数用来向服务器发送 Cookie
$ curl -b 'foo=bar' https://google.com

上面命令会生成一个标头Cookie: foo=bar,向服务器发送一个名为foo、值为bar的 Cookie。


$ curl -b 'foo1=bar;foo2=bar2' https://google.com

上面命令发送两个 Cookie。


$ curl -b cookies.txt https://www.google.com

上面命令读取本地文件cookies.txt,里面是服务器设置的 Cookie(参见-c参数),将其发送到服务器。

4、-c参数将服务器设置的 Cookie 写入一个文件。
$ curl -c cookies.txt https://www.google.com

上面命令将服务器的 HTTP 回应所设置 Cookie 写入文本文件cookies.txt

5、-d参数用于发送 POST 请求的数据体
$ curl -d'login=emma&password=123'-X POST https://google.com/login
# 或者
$ curl -d 'login=emma' -d 'password=123' -X POST  https://google.com/login

使用-d参数以后,HTTP 请求会自动加上标头Content-Type : application/x-www-form-urlencoded。并且会自动将请求转为 POST 方法,因此可以省略-X POST

-d参数可以读取本地文本文件的数据,向服务器发送。


$ curl -d '@data.txt' https://google.com/login

上面命令读取data.txt文件的内容,作为数据体向服务器发送。

6、--data-urlencode

参数等同于-D,发送 POST 请求的数据体,区别在于会自动将发送的数据进行 URL 编码。

$ curl --data-urlencode 'comment=hello world' https://google.com/login

上面代码中,发送的数据hello world之间有一个空格,需要进行 URL 编码。

7、-e参数用来设置 HTTP 的标头Referer,表示请求的来源。
curl -e 'https://google.com?q=example' https://www.example.com

上面命令将Referer标头设为https://google.com?q=example

-H参数可以通过直接添加标头Referer,达到同样效果。


curl -H 'Referer: https://google.com?q=example' https://www.example.com
8、-F参数用来向服务器上传二进制文件。
$ curl -F 'file=@photo.png' https://google.com/profile

上面命令会给 HTTP 请求加上标头Content-Type: multipart/form-data,然后将文件photo.png作为file字段上传。

-F参数可以指定 MIME 类型。


$ curl -F 'file=@photo.png;type=image/png' https://google.com/profile

上面命令指定 MIME 类型为image/png,否则 curl 会把 MIME 类型设为application/octet-stream

-F参数也可以指定文件名。


$ curl -F 'file=@photo.png;filename=me.png' https://google.com/profile

上面命令中,原始文件名为photo.png,但是服务器接收到的文件名为me.png

9、-G参数用来构造 URL 的查询字符串
$ curl -G -d 'q=kitties' -d 'count=20' https://google.com/search

上面命令会发出一个 GET 请求,实际请求的 URL 为https://google.com/search?q=kitties&count=20。如果省略--G,会发出一个 POST 请求。

如果数据需要 URL 编码,可以结合--data--urlencode参数。


$ curl -G --data-urlencode 'comment=hello world' https://www.example.com
10、-H参数添加 HTTP 请求的标头。
$ curl -H 'Accept-Language: en-US' https://google.com

上面命令添加 HTTP 标头Accept-Language: en-US


$ curl -H 'Accept-Language: en-US' -H 'Secret-Message: xyzzy' https://google.com

上面命令添加两个 HTTP 标头。


$ curl -d '{"login": "emma", "pass": "123"}' -H 'Content-Type: application/json' https://google.com/login

上面命令添加 HTTP 请求的标头是Content-Type: application/json,然后用-d参数发送 JSON 数据。

11、-i参数打印出服务器回应的 HTTP 标头。

$ curl -i https://www.example.com

上面命令收到服务器回应后,先输出服务器回应的标头,然后空一行,再输出网页的源码。

12、-I

-I参数向服务器发出 HEAD 请求,然会将服务器返回的 HTTP 标头打印出来。


$ curl -I https://www.example.com

上面命令输出服务器对 HEAD 请求的回应。

--head参数等同于-I


$ curl --head https://www.example.com
13、-k

-k参数指定跳过 SSL 检测。


$ curl -k https://www.example.com

上面命令不会检查服务器的 SSL 证书是否正确。

14、-L

-L参数会让 HTTP 请求跟随服务器的重定向。curl 默认不跟随重定向。


$ curl -L -d 'tweet=hi' https://api.twitter.com/tweet
15、–limit-rate

--limit-rate用来限制 HTTP 请求和回应的带宽,模拟慢网速的环境。


$ curl --limit-rate 200k https://google.com

上面命令将带宽限制在每秒 200K 字节。

16、-o

-o参数将服务器的回应保存成文件,等同于wget命令。


$ curl -o example.html https://www.example.com

上面命令将www.example.com保存成example.html

17、-O

-O参数将服务器回应保存成文件,并将 URL 的最后部分当作文件名。


$ curl -O https://www.example.com/foo/bar.html

上面命令将服务器回应保存成文件,文件名为bar.html

18、-s

-s参数将不输出错误和进度信息。


$ curl -s https://www.example.com

上面命令一旦发生错误,不会显示错误信息。不发生错误的话,会正常显示运行结果。

如果想让 curl 不产生任何输出,可以使用下面的命令。


$ curl -s -o /dev/null https://google.com
19、-S

-S参数指定只输出错误信息,通常与-s一起使用。


$ curl -s -o /dev/null https://google.com

上面命令没有任何输出,除非发生错误。

20、 -u

-u参数用来设置服务器认证的用户名和密码。


$ curl -u 'bob:12345' https://google.com/login

上面命令设置用户名为bob,密码为12345,然后将其转为 HTTP 标头Authorization: Basic Ym9iOjEyMzQ1

curl 能够识别 URL 里面的用户名和密码。


$ curl https://bob:12345@google.com/login

上面命令能够识别 URL 里面的用户名和密码,将其转为上个例子里面的 HTTP 标头。


$ curl -u 'bob' https://google.com/login

上面命令只设置了用户名,执行后,curl 会提示用户输入密码。

21、-v

-v参数输出通信的整个过程,用于调试。


$ curl -v https://www.example.com

--trace参数也可以用于调试,还会输出原始的二进制数据。


$ curl --trace - https://www.example.com
22、-x

-x参数指定 HTTP 请求的代理。


$ curl -x socks5://james:cats@myproxy.com:8080 https://www.example.com

上面命令指定 HTTP 请求通过myproxy.com:8080的 socks5 代理发出。

如果没有指定代理协议,默认为 HTTP。


$ curl -x james:cats@myproxy.com:8080 https://www.example.com

上面命令中,请求的代理使用 HTTP 协议。

23、-X

-X参数指定 HTTP 请求的方法。


$ curl -X POST https://www.example.com

上面命令对https://www.example.com发出 POST 请求。

Bash 脚本入门

# Bash 脚本入门

脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样。所有能够在命令行完成的任务,都能够用脚本完成。

脚本的好处是可以重复使用,也可以指定在特定场合自动调用,比如系统启动或关闭时自动执行脚本。

Shebang 行

脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。

#!后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh/bin/bash

#!/bin/sh
# 或者
#!/bin/bash

#!与脚本解释器之间有没有空格,都是可以的。

如果 Bash 解释器不放在目录/bin,脚本就无法执行了。为了保险,可以写成下面这样。

#!/usr/bin/env bash

上面命令使用env命令(这个命令总是在/usr/bin目录),返回 Bash 可执行文件的位置。env命令的详细介绍,请看后文。

Shebang 行不是必需的,但是建议加上这行。如果缺少该行,就需要手动将脚本传给解释器。举例来说,脚本是script.sh,有 Shebang 行的时候,可以直接调用执行。

$ ./script.sh

上面例子中,script.sh是脚本文件名。脚本通常使用.sh后缀名,不过这不是必需的。

如果没有 Shebang 行,就只能手动将脚本传给解释器来执行。

$ /bin/sh ./script.sh
# 或者
$ bash ./script.sh

执行权限和路径

前面说过,只要指定了 Shebang 行的脚本,可以直接执行。这有一个前提条件,就是脚本需要有执行权限。可以使用下面的命令,赋予脚本执行权限。

# 给所有用户执行权限
$ chmod +x script.sh

# 给所有用户读权限和执行权限
$ chmod +rx script.sh
# 或者
$ chmod 755 script.sh

# 只给脚本拥有者读权限和执行权限
$ chmod u+rx script.sh

脚本的权限通常设为755(拥有者有所有权限,其他人有读和执行权限)或者700(只有拥有者可以执行)。

除了执行权限,脚本调用时,一般需要指定脚本的路径(比如path/script.sh)。如果将脚本放在环境变量$PATH指定的目录中,就不需要指定路径了。因为 Bash 会自动到这些目录中,寻找是否存在同名的可执行文件。

建议在主目录新建一个~/bin子目录,专门存放可执行脚本,然后把~/bin加入$PATH

export PATH=$PATH:~/bin

上面命令改变环境变量$PATH,将~/bin添加到$PATH的末尾。可以将这一行加到~/.bashrc文件里面,然后重新加载一次.bashrc,这个配置就可以生效了。

$ source ~/.bashrc

以后不管在什么目录,直接输入脚本文件名,脚本就会执行。

$ script.sh

上面命令没有指定脚本路径,因为script.sh$PATH指定的目录中。

env 命令

env命令总是指向/usr/bin/env文件,或者说,这个二进制文件总是在目录/usr/bin

#!/usr/bin/env NAME这个语法的意思是,让 Shell 查找$PATH环境变量里面第一个匹配的NAME。如果你不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。

/usr/bin/env bash的意思就是,返回bash可执行文件的位置,前提是bash的路径是在$PATH里面。其他脚本文件也可以使用这个命令。比如 Node.js 脚本的 Shebang 行,可以写成下面这样。

#!/usr/bin/env node

env命令的参数如下。

  • -i, --ignore-environment:不带环境变量启动。
  • -u, --unset=NAME:从环境变量中删除一个变量。
  • --help:显示帮助。
  • --version:输出版本信息。

下面是一个例子,新建一个不带任何环境变量的 Shell。

$ env -i /bin/sh

注释

Bash 脚本中,#表示注释,可以放在行首,也可以放在行尾。

# 本行是注释
echo 'Hello World!'

echo 'Hello World!' # 井号后面的部分也是注释

建议在脚本开头,使用注释说明当前脚本的作用,这样有利于日后的维护。

脚本参数

调用脚本的时候,脚本文件名后面可以带有参数。

$ script.sh word1 word2 word3

上面例子中,script.sh是一个脚本文件,word1word2word3是三个参数。

脚本文件内部,可以使用特殊变量,引用这些参数。

  • $0:脚本文件名,即script.sh
  • $1~$9:对应脚本的第一个参数到第九个参数。
  • $#:参数的总数。
  • $@:全部的参数,参数之间使用空格分隔。
  • $*:全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。

如果脚本的参数多于9个,那么第10个参数可以用${10}的形式引用,以此类推。

注意,如果命令是command -o foo bar,那么-o$1foo$2bar$3

下面是一个脚本内部读取命令行参数的例子。

#!/bin/bash
# script.sh

echo "全部参数:" $@
echo "命令行参数数量:" $#
echo '$0 = ' $0
echo '$1 = ' $1
echo '$2 = ' $2
echo '$3 = ' $3

执行结果如下。

$ ./script.sh a b c
全部参数:a b c
命令行参数数量:3
$0 =  script.sh
$1 =  a
$2 =  b
$3 =  c

用户可以输入任意数量的参数,利用for循环,可以读取每一个参数。

#!/bin/bash

for i in "$@"; do
  echo $i
done

上面例子中,$@返回一个全部参数的列表,然后使用for循环遍历。

如果多个参数放在双引号里面,视为一个参数。

$ ./script.sh "a b"

上面例子中,Bash 会认为"a b"是一个参数,$1会返回a b。注意,返回时不包括双引号。

shift 命令

shift命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数($1),使得后面的参数向前一位,即$2变成$1$3变成$2$4变成$3,以此类推。

while循环结合shift命令,也可以读取每一个参数。

#!/bin/bash

echo "一共输入了 $# 个参数"

while [ "$1" != "" ]; do
  echo "剩下 $# 个参数"
  echo "参数:$1"
  shift
done

上面例子中,shift命令每次移除当前第一个参数,从而通过while循环遍历所有参数。

shift命令可以接受一个整数作为参数,指定所要移除的参数个数,默认为1

shift 3

上面的命令移除前三个参数,原来的$4变成$1

getopts 命令

getopts命令用在脚本内部,可以解析复杂的脚本命令行参数,通常与while循环一起使用,取出脚本所有的带有前置连词线(-)的参数。

getopts optstring name

它带有两个参数。第一个参数optstring是字符串,给出脚本所有的连词线参数。比如,某个脚本可以有三个配置项参数-l-h-a,其中只有-a可以带有参数值,而-l-h是开关参数,那么getopts的第一个参数写成lha:,顺序不重要。注意,a后面有一个冒号,表示该参数带有参数值,getopts规定带有参数值的配置项参数,后面必须带有一个冒号(:)。getopts的第二个参数name是一个变量名,用来保存当前取到的配置项参数,即lha

下面是一个例子。

while getopts 'lha:' OPTION; do
  case "$OPTION" in
    l)
      echo "linuxconfig"
      ;;

    h)
      echo "h stands for h"
      ;;

    a)
      avalue="$OPTARG"
      echo "The value provided is $OPTARG"
      ;;
    ?)
      echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
      exit 1
      ;;
  esac
done
shift "$(($OPTIND - 1))"

上面例子中,while循环不断执行getopts 'lha:' OPTION命令,每次执行就会读取一个连词线参数(以及对应的参数值),然后进入循环体。变量OPTION保存的是,当前处理的那一个连词线参数(即lha)。如果用户输入了没有指定的参数(比如-x),那么OPTION等于?。循环体内使用case判断,处理这四种不同的情况。

如果某个连词线参数带有参数值,比如-a foo,那么处理a参数的时候,环境变量$OPTARG保存的就是参数值。

注意,只要遇到不带连词线的参数,getopts就会执行失败,从而退出while循环。比如,getopts可以解析command -l foo,但不可以解析command foo -l。另外,多个连词线参数写在一起的形式,比如command -lhgetopts也可以正确处理。

变量$OPTINDgetopts开始执行前是1,然后每次执行就会加1。等到退出while循环,就意味着连词线参数全部处理完毕。这时,$OPTIND - 1就是已经处理的连词线参数个数,使用shift命令将这些参数移除,保证后面的代码可以用$1$2等处理命令的主参数。

配置项参数终止符 --

---开头的参数,会被 Bash 当作配置项解释。但是,有时它们不是配置项,而是实体参数的一部分,比如文件名叫做-f--file

$ cat -f
$ cat --file

上面命令的原意是输出文件-f--file的内容,但是会被 Bash 当作配置项解释。

这时就可以使用配置项参数终止符--,它的作用是告诉 Bash,在它后面的参数开头的---不是配置项,只能当作实体参数解释。

$ cat -- -f
$ cat -- --file

上面命令可以正确展示文件-f--file的内容,因为它们放在--的后面,开头的---就不再当作配置项解释了。

如果要确保某个变量不会被当作配置项解释,就要在它前面放上参数终止符--

$ ls -- $myPath

上面示例中,--强制变量$myPath只能当作实体参数(即路径名)解释。如果变量不是路径名,就会报错。

$ myPath="-l"
$ ls -- $myPath
ls: 无法访问'-l': 没有那个文件或目录

上面例子中,变量myPath的值为-l,不是路径。但是,--强制$myPath只能作为路径解释,导致报错“不存在该路径”。

下面是另一个实际的例子,如果想在文件里面搜索--hello,这时也要使用参数终止符--

$ grep -- "--hello" example.txt

上面命令在example.txt文件里面,搜索字符串--hello。这个字符串是--开头,如果不用参数终止符,grep命令就会把--hello当作配置项参数,从而报错。

exit 命令

exit命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。

$ exit

上面命令中止当前脚本,将最后一条命令的退出状态,作为整个脚本的退出状态。

exit命令后面可以跟参数,该参数就是退出状态。

# 退出值为0(成功)
$ exit 0

# 退出值为1(失败)
$ exit 1

退出时,脚本会返回一个退出值。脚本的退出值,0表示正常,1表示发生错误,2表示用法不对,126表示不是可执行脚本,127表示命令没有发现。如果脚本被信号N终止,则退出值为128 + N。简单来说,只要退出值非0,就认为执行出错。

下面是一个例子。

if [ $(id -u) != "0" ]; then
  echo "根用户才能执行当前脚本"
  exit 1
fi

上面的例子中,id -u命令返回用户的 ID,一旦用户的 ID 不等于0(根用户的 ID),脚本就会退出,并且退出码为1,表示运行失败。

exitreturn命令的差别是,return命令是函数的退出,并返回一个值给调用者,脚本依然执行。exit是整个脚本的退出,如果在函数之中调用exit,则退出函数,并终止脚本执行。

命令执行结果

命令执行结束后,会有一个返回值。0表示执行成功,非0(通常是1)表示执行失败。环境变量$?可以读取前一个命令的返回值。

利用这一点,可以在脚本中对命令执行结果进行判断。

cd /path/to/somewhere
if [ "$?" = "0" ]; then
  rm *
else
  echo "无法切换目录!" 1>&2
  exit 1
fi

上面例子中,cd /path/to/somewhere这个命令如果执行成功(返回值等于0),就删除该目录里面的文件,否则退出脚本,整个脚本的返回值变为1,表示执行失败。

由于if可以直接判断命令的执行结果,执行相应的操作,上面的脚本可以改写成下面的样子。

if cd /path/to/somewhere; then
  rm *
else
  echo "Could not change directory! Aborting." 1>&2
  exit 1
fi

更简洁的写法是利用两个逻辑运算符&&(且)和||(或)。

# 第一步执行成功,才会执行第二步
cd /path/to/somewhere && rm *

# 第一步执行失败,才会执行第二步
cd /path/to/somewhere || exit 1

source 命令

source命令用于执行一个脚本,通常用于重新加载一个配置文件。

$ source .bashrc

source命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell。所以,source命令执行脚本时,不需要export变量。

#!/bin/bash
# test.sh
echo $foo

上面脚本输出$foo变量的值。

# 当前 Shell 新建一个变量 foo
$ foo=1

# 打印输出 1
$ source test.sh
1

# 打印输出空字符串
$ bash test.sh

上面例子中,当前 Shell 的变量foo并没有export,所以直接执行无法读取,但是source执行可以读取。

source命令的另一个用途,是在脚本内部加载外部库。

#!/bin/bash

source ./lib.sh

function_from_lib

上面脚本在内部使用source命令加载了一个外部库,然后就可以在脚本里面,使用这个外部库定义的函数。

source有一个简写形式,可以使用一个点(.)来表示。

$ . .bashrc

别名,alias 命令

alias命令用来为一个命令指定别名,这样更便于记忆。下面是alias的格式。

alias NAME=DEFINITION

上面命令中,NAME是别名的名称,DEFINITION是别名对应的原始命令。注意,等号两侧不能有空格,否则会报错。

一个常见的例子是为grep命令起一个search的别名。

alias search=grep

alias也可以用来为长命令指定一个更短的别名。下面是通过别名定义一个today的命令。

$ alias today='date +"%A, %B %-d, %Y"'
$ today
星期一, 一月 6, 2020

有时为了防止误删除文件,可以指定rm命令的别名。

$ alias rm='rm -i'

上面命令指定rm命令是rm -i,每次删除文件之前,都会让用户确认。

alias定义的别名也可以接受参数,参数会直接传入原始命令。

$ alias echo='echo It says: '
$ echo hello world
It says: hello world

上面例子中,别名定义了echo命令的前两个参数,等同于修改了echo命令的默认行为。

指定别名以后,就可以像使用其他命令一样使用别名。一般来说,都会把常用的别名写在~/.bashrc的末尾。另外,只能为命令定义别名,为其他部分(比如很长的路径)定义别名是无效的。

直接调用alias命令,可以显示所有别名。

$ alias

unalias命令可以解除别名。

$ unalias lt

参考链接

linux shell 学习笔记

之前浅浅学习过shell知识,但因为没怎么用过,所以基本上忘光了,所以在重新复习下。

Github 地址:https://github.com/wangdoc/bash-tutorial

在线阅读: https://wangdoc.com/bash/

学习 Bash,首先需要理解 Shell 是什么。Shell 这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。

具体来说,Shell 这个词有多种含义。

首先,Shell 是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为 CLI)。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。本书中,除非特别指明,Shell 指的就是命令行环境。

其次,Shell 是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本(script)。这些脚本都通过 Shell 的解释执行,而不通过编译。

最后,Shell 是一个工具箱,提供了各种小工具,供用户方便地使用操作系统的功能。

1、查看解释器

cat /etc/shells

2、修改用户解释器

usermod -s /bin/bash 用户名
or
chsh -s /bin/sh   #  ch ==change  sh == bash  改变bash

root@Administrator:~# chsh -s /bin/sh
root@Administrator:~# chsh
Changing the login shell for root
Enter the new value, or press ENTER for the default
        Login Shell [/bin/sh]: /bin/bash

4、输入输出重定向


输出重定向常见形式:


command > file  #将标准输出1重定向到 file 里
command 1> file #将标准输出1重定向到 file 里,与上面的写法功能一样
command 2> file #将标准错误输出1重定向到 file 里
command &> file #将标准输出1 与 标准错误输出2 一起重定向到 file 里

不覆盖file的输出重定向 >>

&> 为一起重定向标准输出 与 标准错误输出的简便写法

输入重定向

与输出重定向类似,将文件内容重定向到标准输入0

command <file  #重定向标准输入
command << xxx  #here document

<<xxx 这种形式被称为Here document,xxx为任意字符串,作为标签,为Here documen的起始,输入时直接在终端里输入多行内容,完成后再次输入xxx,标记输入完成。

#here document
command <<标签  
>内容
>内容
>...
>标签
 
$ head -v <<abc #<<abc 是指here document的起始
> 123 #内容
> 123 #内容
> 123 #内容
> abc #abc 表示结束
==> standard input <==  #Here document被定向为标准输入
123
123
123

5、 管道 |

“|”连接两个命令,shell会将前后两个进程的输入输出用一个管道相连,以便达到进程间通信的目的

用途,可以将多个命令连接,同时将上一个命令的输出作为下个命令的输入,送到下个命令中去

ls |grep *.txt

4、快捷键:

Bash 提供很多快捷键,可以大大方便操作。下面是一些最常用的快捷键,完整的介绍参见《行操作》一章。

  • Ctrl + L:清除屏幕并将当前行移到页面顶部。
  • Ctrl + C:中止当前正在执行的命令。
  • Shift + PageUp:向上滚动。
  • Shift + PageDown:向下滚动。
  • Ctrl + U:从光标位置删除到行首。
  • Ctrl + K:从光标位置删除到行尾。
  • Ctrl + W:删除光标位置前一个单词。
  • Ctrl + D:关闭 Shell 会话。
  • :浏览已执行命令的历史记录。

除了上面的快捷键,Bash 还具有自动补全功能。命令输入到一半的时候,可以按下 Tab 键,Bash 会自动完成剩下的部分。比如,输入tou,然后按一下 Tab 键,Bash 会自动补上ch

除了命令的自动补全,Bash 还支持路径的自动补全。有时,需要输入很长的路径,这时只需要输入前面的部分,然后按下 Tab 键,就会自动补全后面的部分。如果有多个可能的选择,按两次 Tab 键,Bash 会显示所有选项,让你选择。

光标移动

Readline 提供快速移动光标的快捷键。

  • Ctrl + a:移到行首。
  • Ctrl + b:向行首移动一个字符,与左箭头作用相同。
  • Ctrl + e:移到行尾。
  • Ctrl + f:向行尾移动一个字符,与右箭头作用相同。
  • Alt + f:移动到当前单词的词尾。
  • Alt + b:移动到当前单词的词首。

清除屏幕

Ctrl + l快捷键可以清除屏幕,即将当前行移到屏幕的第一行,与clear命令作用相同。

目录堆栈

cd –

Bash 可以记忆用户进入过的目录。默认情况下,只记忆前一次所在的目录,cd -命令可以返回前一次的目录。

pushd,popd

如果希望记忆多重目录,可以使用pushd命令和popd命令。它们用来操作目录堆栈。

pushd命令的用法类似cd命令,可以进入指定的目录。

$ pushd dirname

上面命令会进入目录dirname,并将该目录放入堆栈。

第一次使用pushd命令时,会将当前目录先放入堆栈,然后将所要进入的目录也放入堆栈,位置在前一个记录的上方。以后每次使用pushd命令,都会将所要进入的目录,放在堆栈的顶部。

popd命令不带有参数时,会移除堆栈的顶部记录,并进入新的堆栈顶部目录(即原来的第二条目录)。

下面是一个例子。

# 当前处在主目录,堆栈为空
$ pwd
/home/me

# 进入 /home/me/foo
# 当前堆栈为 /home/me/foo /home/me
$ pushd ~/foo

# 进入 /etc
# 当前堆栈为 /etc /home/me/foo /home/me
$ pushd /etc

# 进入 /home/me/foo
# 当前堆栈为 /home/me/foo /home/me
$ popd

# 进入 /home/me
# 当前堆栈为 /home/me
$ popd

# 目录不变,当前堆栈为空
$ popd

linux 查看端口占用

1、netstat -lnp|grep 端口号

2、lsof -i:端口号

3、 fuser -v -n tcp 80 命令查看80端口的占用、

结束该占用80端口的进程,使用kill命令。

1. kill 进程号:

会给进程发送一个SIGTERM信号,使进程先释放自己资源,然后停止,但是也有程序可能接收信号后,做一些其他的事情(如果程序正在等待IO,可能就不会立马做出响应。

2.kill -9 进程号:

会给进程发送一个SIGKILL信号,使该进程执行exit,不会被阻塞,所以可以有效的杀掉进程。

这里我使用kill -9 9415强制杀掉9415进程释放80端口。

linux 安装tensorflow 记录

一纸辛酸泪……..

安装tensorflow gpu需要先安装cudn 和 cudnn

现在我已经安装好了cudn,nvcc-v 也可以正常显示,但是在/usr/local/文件夹下却没有cuda文件夹.

2.问题由来

通常我们在安装cudnn时,先进行安装cuda,然后下载cudnn文件,通过

tar -xzvf cudnn-10.2-linux-x64-v7.6.5.32.tgz
#命令解压文件,会得到一个cuda文件夹,逐一执行下面的命令进行cudnn的安装
sudo cp cuda/include/cudnn*.h /usr/local/cuda/include/
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/
sudo chmod a+r /usr/local/cuda/include/cudnn.h
sudo chmod a+r /usr/local/cuda/lib64/libcudnn*
#完成后,通过下面的命令查看安装情况,如果结果如下图逐行显示版本号,则安装成功
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2

在这里插入图片描述
但是现在并没有这个文件夹该如何做呢?

3.问题定位

使用指令

whereis cuda

输出

cuda: /usr/lib/cuda /usr/include/cuda.h

找到了cuda的位置

4.问题原因

之所以在这两个位置是由于,通过sudo apt install nvidia-cuda-toolkit

这种安装方法在/usr/include和/usr/lib/cuda/lib64中安装cuda

5.问题解决

找了cuda的位置后,安装cudnn,将上述指令替换为

tar -xzvf cudnn-10.2-linux-x64-v7.6.5.32.tgz
#命令解压文件,会得到一个cuda文件夹,逐一执行下面的命令进行cudnn的安装
sudo cp cuda/include/cudnn*.h /usr/include/
sudo cp cuda/lib64/libcudnn* /usr/lib/cuda/lib64/
sudo chmod a+r /usr/include/cudnn.h
sudo chmod a+r /usr/lib/cuda/lib64/libcudnn*
#完成后,通过下面的命令查看安装情况,如果结果如下图逐行显示版本号,则安装成功
cat /usr/include/cudnn.h | grep CUDNN_MAJOR -A 2

注意需要将cuda.h与cudnn.h文件放在同一文件夹下.

nvcc和nvidia-smi显示的cuda版本不一致解释:

nvcc属于cuda的编译器,将程序编译成可执行的二进制文件,nvidia-smi全称是NVIDIA System Management Interface,是一种命令行使用工具,旨在帮助管理和监控NVIDIA GPU设备。

cuda有runtime api和driver api,两者都有对应的cuda版本,nvcc –version显示的就是前者对应的cuda版本,而nvidia-smi显示的是后者对应的cuda版本。

用于支持driver api的必要文件由GPU driver install安装,nvidia-smi就属于这一类API;而用于支持runtime api的必要文件是由cuda toolkit install安装的。nvcc是与cuda toolkit一起安装的cuda compiler-driver tool,它只知道自身构建时的cuda runtime版本,并不知道安装了什么版本的GPU driver,甚至不知道是否安装了GPU driver。

cuda toolkit install通常会集成了GPU driver install,如果你的cuda均通过cuda toolkit install来安装,那么runtime api和driver api的版本应该是一致的,也就是说,nvcc -V和nvidia-smi显示的版本正常是一样的,否则你可能使用了单独的GPU driver install来安装GPU driver,这样就会导致nvidia-smi和nvcc -V显示的版本不一样。

通常,driver api的版本能向下兼容runtime api的版本,即nvidia-smi显示的版本大于nvcc -V时通常是可以使用的。

#runtime api就是runfile类型的安装文件,driver api就是deb类型的安装文件。

tmux 使用:linux后台运行程序

最近需要远程在linux服务器跑深度学习代码,因此需要一个后台运行程序,tmux正好满足有需求,记录下tmux的使用方法:

sudo apt-get install tmux

1、终端下常用命令

命令作用
tmux new -s name新建名为name的会话
tmux ls列出所有会话列表
tmux a -t name从终端进入名为name的会话
tmux kill-session -t name销毁名为name的会话
tmux rename -t old_name new_name重命名会话
 tmux detach分离对话

2、tmux会话下常用命令

所有命令都需要先按ctrl+b,激活控制台

命令作用
ctrl+b ?显示所有可用的命令,按q返回
ctrl+b c创建新的窗口,并切换到该窗口
ctrl+b w显示所有窗口列表
ctrl+b p切换到上一个窗口
ctrl+b n切换到下一个窗口
ctrl+b ,重命名当前窗口
ctrl+b &关闭当前窗口
ctrl+b 数字键切换到指定窗口
ctrl+b d暂时断开会话(使用该命令将程序后台运行,关掉终端程序不会停止)
ctrl+b ~列出提示信息缓存;其中包含了之前tmux返回的各种提示信息
ctrl+b :进入命令行模式;此时可以输入支持的命令,例如kill-server可以关闭服务器

3、tmux窗格

​ tmux的一个窗口可以被分成多个pane(窗格),可以做出分屏效果。

命令作用
ctrl+b %将当前面板平分为左右两块
ctrl+b “将当前面板平分为上下两块
ctrl+b o切换到下一个窗格
ctrl+b up / down / left / right切换窗格
ctrl+b space对当前窗口下的所有pane重新排列布局,每按一次,换一种样式
ctrl+b z最大化当前pane,再按一次后恢复
ctrl+b x关闭当前使用中的pane,操作之后会给出是否关闭的提示,按y确认即关闭

4、tmux会话下查看历史输出

​ 按 ctrl-b ,就会进入copy mode,然后用PgUp/PgDn来浏览历史输出,按q退出。

综上所述,以下是 Tmux 的最简操作流程。

  1. 新建会话tmux new -s my_session
  2. 在 Tmux 窗口运行所需的程序。
  3. 按下快捷键Ctrl+b d将会话分离。 or 输入 tmux detach
  4. 下次使用时,重新连接到会话tmux attach-session -t my_session

作者:笛猪
链接:https://www.jianshu.com/p/de6f80b6bec0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

常用git命令汇总

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

git缓存修改:如果你没有改完这个分支,不想提交,但需要切换到别的分支,使用暂存:

git stash –include-untracked

  • 在其他分支上修改完以后再切回之前的feature分支,把暂存的修改拿出来
git stash pop

如何将自己的代码上传到git远程仓库:

  1. 打开 Git Bash。
  2. 将当前工作目录更改为您的本地仓库。
  3. 将要提交的文件暂存到本地仓库。$ git add . # Adds the file to your local repository and stages it for commit. 要取消暂存文件,请使用 'git reset HEAD YOUR-FILE'。
  4. 提交暂存在本地仓库中的文件。$ git commit -m "Add existing file" # Commits the tracked changes and prepares them to be pushed to a remote repository. 要删除此提交并修改文件,请使用 'git reset --soft HEAD~1' 并再次提交和添加文件。
  5. 推送更改(本地仓库中)到 GitHub.com。$ git push origin your-branch
  6. 注意如果https://github.com/vina_MolecularDockingScript.git不可以的话,尝试将 origin改成 git@github.com:/vina_MolecularDockingScript.git

git 比较不同分支差异

git diff  分支1 分支2 –stat (加上 –stat 是显示文件列表, 默认是文件内容diff)

git diff branch1 branch2 –stat                   //显示出所有有差异的文件列表


git diff branch1 branch2   具体文件路径   //显示指定文件的详细差异


git diff branch1 branch2                            //显示出所有有差异的文件的详细差异

git diff 被修改的文件名 就可以看到被修改过的原始内容


 

Git 出现!reject的情况

解决办法:首先将本地修改的文件备份,然后强制让远程文件覆盖本地仓库文件

Git fetch –all

Git reset –hard origin/liangxianchen

当删除本地文件后,如何与远端同步?

首先不能直接在本地按del键删除,这样对远端没有任何影响,必须使用git rm filename 命令删除,其次,删除完之后,git commit -m “delete filename” 一下,最后 git push就OK了

假设我们需要从远程pull下代码,本地修改后在传到远程完整流程:

1、本地新建文件夹:test,并在test内部打开 git

2、输入 git init 初始化git仓库

$ git config --global user.name "runoob"
$ git config --global user.email test@runoob.com

3、输入

git pull <远程主机名> <远程分支名>:<本地分支名>

比如:git pull git@10.112.55.138:CNN/code.git master:liangxianchen

把我的远程分支master拉到了本地 liangxianchen 分支

4、git branch -a 查看本地和远程分支

5、此时本地已经有了远程分支代码,可以在本地对代码进行修改和增加删除

修改完成后:

首先一定要先拉取远程分支!!!!:

比如:git pull git@10.112.55.138:CNN/code.git master: liangxianchen

然后:git add .

git commit -m “提交的备注信息”

git push git@10.112.55.138:CNN/code.git liangxianchen :liangxianchen

如果想要 将liangxianchen分支合并到master分支:

现在本地将两个分支合并,在使用

git push git@10.112.55.138:CNN/code.git master:master

注:git 还可以指定某些文件不参与git提交:gitignore,我们在git目录下创建一个.gitignore文件,然后在这个文件当中列举出我们不希望提交的文件即可。

凡是列在这个文件当中的名称,当我们在使用git add的时候都会替我们忽略掉。我们也没有必要从头开始编写这个gitignore文件,因为git当中已经替我们写好了很多模板,我们可以直接拿过来参考。

模板的地址:https://github.com/github/gitignore

一般来说,项目的log文件不需要传输,放到ignore中

git branch liangxianchen 新建分支

git checkout liangxianchen 切换分支

git 使用merge 对本地分支进行合并 并进行代码提交的流程

1、为啥需要git创建分支:

对于本地来说,其实一个分支是足够的,但假如你目前正在修改整个项目的partA,还没修改完,老板让你今天加个班,把partB改一下然后提交上去,这时候就需要分支了,你可以切换不同的分支去完成不同的工作,这样的化,你只需要把partB的分支推上去。如果你只有一个分支,那推上去的就是所有的修改。

第二种情况:如果你需要去临时修改一部分内容,可以新建分支,在该分支修改,修改完成,在切换回主分支,继续之前的工作。

1.只有当将修改内容commit后 该修改才完全生效,进行merge前需要将两个分支修改的内容都进行commit

2.假设本地两个分支   用于开发的分支:dev    用于同步远程仓库的分支:master 

3、我要同时开发几个功能

3.切换到master分支 进行 (git pull origin 远程分支) 不要在master 分支进行开发(也不要在master分支进行add commit),以此保证当在master分支进行git pull 不会产生冲突(如果不慎在master分支修改了内容, 可以先撤销所有修改,再将版本回退到没有冲突的地方)

4.在master分支拉取了最新代码后,如果没有在master分支进行过开发,那么这个分支内容就是没有冲突的最新的内容

5.切换到dev分支, 将所有的修改进行add 以及commit

dev分支的工作完成,我们就可以切换回master分支:

$ git checkout master
Switched to branch 'master'

切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变

dev分支的工作成果合并到master分支上 git merge命令用于合并指定分支到当前分支 :

git merge dev(分支名)

合并完成后,就可以放心地删除dev分支了:

$ git branch -d dev
  • merge 遇见冲突后会直接停止,等待手动解决冲突并重新提交 commit 后,才能再次 merge
  • merge 是一个合并操作,会将两个分支的修改合并在一起,默认操作的情况下会提交合并中修改的内容

二、假设远程仓库只有mater分支

1. 克隆代码

git clone https://github.com/master-dev.git  
# 这个git路径是无效的,示例而已

2. 查看所有分支

git branch --all  
# 默认只有master分支,所以会看到如下两个分支
# master[本地主分支] origin/master[远程主分支]
# 新克隆下来的代码默认master和origin/master是关联的,也就是他们的代码保持同步

3. 创建本地新的dev分支

git branch dev  # 创建本地分支
git branch  # 查看分支
# 这是会看到master和dev,而且master上会有一个星号
# 这个时候dev是一个本地分支,远程仓库不知道它的存在
# 本地分支可以不同步到远程仓库,我们可以在dev开发,然后merge到master,使用master同步代码,当然也可以同步

4. 发布dev分支

发布dev分支指的是同步dev分支的代码到远程服务器

git push origin dev:dev  # 这样远程仓库也有一个dev分支了

5. 在dev分支开发代码

git checkout dev  # 切换到dev分支进行开发
# 开发代码之后,我们有两个选择
# 第一个:如果功能开发完成了,可以合并主分支
git checkout master  # 切换到主分支
git merge dev  # 把dev分支的更改和master合并
git push  # 提交主分支代码远程
git checkout dev  # 切换到dev远程分支
git push  # 提交dev分支到远程
# 第二个:如果功能没有完成,可以直接推送
git push  # 提交到dev远程分支
# 注意:在分支切换之前最好先commit全部的改变,除非你真的知道自己在做什么

6. 删除分支

git push origin :dev  # 删除远程dev分支,危险命令哦
# 下面两条是删除本地分支
git checkout master  # 切换到master分支
git branch -d dev  # 删除本地dev分支

git取消Commit,取消add,回滚代码

git commit之后取消的操作使用reset指令进行。 1.git reset –soft HEAD^,撤销commit,但是不撤销add动作。 2.git reset –hard HEAD^,撤销commit,并且撤销add动作。 3.git reset HEAD <文件名>,撤回add动作。 4.git checkout .,丢弃本次修改内容(文件被修改了,但未执行git add操作

git reset HEAD filename 取消某个文件的add

git fetch

使用 git fetch 指令将远程分支上的最新的修改下载下来。

<div><br class=”Apple-interchange-newline”>git merge</div>





git fetch upstream
git merge upstream/master

pull=fetch+merge,pull的话,下拉远程分支并与本地分支合并。fetch只是下拉远程分支,怎么合并,可以自己再做选择。

git pull

git pull 指令实际做了两件事:git fetch 和 git merge。

将分支代码拉取到本地,修改后如何上传到git分支

如果是多人合作,推送之前最好先更新一遍代码,因为可能别人更新过该分支,防止覆盖别人更新后的代码。将最新的分支代码拉取到本地后,再进行提交

1. 创建文件夹并初始化本地仓库

mkdir test
cd test
git init

关联本地仓库和远程仓库

git remote add origin git@github.com:liqiangyz/learngit.git

说明:

  • origin表示远程库的名字,可以随意,一般默认为origin;
  • origin后面表示远程仓库的真实地址,如下图所示。我这里使用的是SSH地址,当然也可以使用HTTPS地址,复制过来就行。

git remote 删除已添加的远程仓库地址

添加远程仓库出错,因为已经存在了一个错误的地址

查看当前配置的远程仓库地址:

git remote -v

删除本地指定的远程地址:

git remote remove origin
再执行-v的查看,可以添加新的远程仓库地址了

3. 拉取分支代码

git fetch是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中。

git pull 则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge,这样可能会产生冲突,需要手动解决。

git fetch origin develop #可以使用git fetch origin 拉取全部

说明:

  • develop为我的分支名字,根据自己的分支决定。
  • 有的同学可能会用git pull,git pull = git fetch + git merge,因为pull拉取会合并本地文件,可能会导致冲突。

4. 创建本地分支,并切换到本地分支

经过上一步,在本地还看不到拉取的代码,需要手动创建一下:

不要使用git checkout -b develop,如果没有关联远程,会出问题!!!

git checkout -b develop origin/develop

说明:

git checkout表示切换分支或恢复工作树文件。
-b表示进入git checkout之前执行git branch 创建分支操作。
综合起来考虑,这一步的操作相当于执行checkout命令检出远程拉取分支,并进入该分支。
使用ls命令可以看到下拉的文件,并且使用git branch命令可以看到当前停留的分支

5. 更新分支代码

如果远程分支上有更新,可以使用pull命令对本地进行更新,如果没有,则可以跳过此步骤。

git pull origin develop(分支)

1、查看文件状态

git status
2、添加全部文件到暂存区

git add .

3、将暂存区内容添加到本地仓库中

git commit -m “cpp”

6、推送(push)本地分支到远程分支

推送之前最好先更新一遍代码!防止覆盖!!!自己去理解一下哈。

git pull origin develop
git push origin develop

一、新建代码库


# 在当前目录新建一个Git代码库
$ git init

# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]

# 下载一个项目和它的整个代码历史
$ git clone [url]

二、配置

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。


# 显示当前的Git配置
$ git config --list

# 编辑Git配置文件
$ git config -e [--global]

# 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

三、增加/删除文件


# 添加指定文件到暂存区
$ git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
$ git add [dir]

# 添加当前目录的所有文件到暂存区
$ git add .

git add -u :将文件的修改、文件的删除,添加到暂存区。

git add . :将文件的修改,文件的新建,添加到暂存区。

git add -A :将文件的修改,文件的删除,文件的新建,添加到暂存区。

# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
$ git add -p

# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

四、代码提交


# 提交暂存区到仓库区
$ git commit -m [message]

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a

# 提交时显示所有diff信息
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]

# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

五、分支


# 列出所有本地分支
$ git branch

# 列出所有远程分支
$ git branch -r

# 列出所有本地分支和远程分支
$ git branch -a

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]

# 新建一个分支,并切换到该分支
$ git checkout -b [branch]

# 新建一个分支,指向指定commit
$ git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
$ git checkout [branch-name]

# 切换到上一个分支
$ git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
$ git merge [branch]

# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

六、标签


# 列出所有tag
$ git tag

# 新建一个tag在当前commit
$ git tag [tag]

# 新建一个tag在指定commit
$ git tag [tag] [commit]

# 删除本地tag
$ git tag -d [tag]

# 删除远程tag
$ git push origin :refs/tags/[tagName]

# 查看tag信息
$ git show [tag]

# 提交指定tag
$ git push [remote] [tag]

# 提交所有tag
$ git push [remote] --tags

# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]

七、查看信息


# 显示有变更的文件
$ git status

# 显示当前分支的版本历史
$ git log

# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat

# 搜索提交历史,根据关键词
$ git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相关的每一次diff
$ git log -p [file]

# 显示过去5次提交
$ git log -5 --pretty --oneline

# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn

# 显示指定文件是什么人在什么时间修改过
$ git blame [file]

# 显示暂存区和工作区的差异
$ git diff

# 显示暂存区和上一个commit的差异
$ git diff --cached [file]

# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD

# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"

# 显示某次提交的元数据和内容变化
$ git show [commit]

# 显示某次提交发生变化的文件
$ git show --name-only [commit]

# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# 显示当前分支的最近几次提交
$ git reflog

八、远程同步


# 下载远程仓库的所有变动
$ git fetch [remote]

# 显示所有远程仓库
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]

# 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

九、撤销


# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]

# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop

十、其他


# 生成一个可供发布的压缩包
$ git archive

汇总表:

git init # 初始化本地git仓库(创建新仓库)
git config –global user.name “xxx” # 配置用户名
git config –global user.email “xxx@xxx.com” # 配置邮件
git config –global color.ui true # git status等命令自动着色
git config –global color.status auto
git config –global color.diff auto
git config –global color.branch auto
git config –global color.interactive auto
git config –global –unset http.proxy # remove proxy configuration on git
git clone git+ssh://git@192.168.53.168/VT.git # clone远程仓库
git status # 查看当前版本状态(是否修改)
git add xyz # 添加xyz文件至index
git add . # 增加当前子目录下所有更改过的文件至index
git commit -m ‘xxx’ # 提交
git commit –amend -m ‘xxx’ # 合并上一次提交(用于反复修改)
git commit -am ‘xxx’ # 将add和commit合为一步
git rm xxx # 删除index中的文件
git rm -r * # 递归删除
git log # 显示提交日志
git log -1 # 显示1行日志 -n为n行
git log -5
git log –stat # 显示提交日志及相关变动文件
git log -p -m
git show dfb02e6e4f2f7b573337763e5c0013802e392818 # 显示某个提交的详细内容
git show dfb02 # 可只用commitid的前几位
git show HEAD # 显示HEAD提交日志
git show HEAD^ # 显示HEAD的父(上一个版本)的提交日志 ^^为上两个版本 ^5为上5个版本
git tag # 显示已存在的tag
git tag -a v2.0 -m ‘xxx’ # 增加v2.0的tag
git show v2.0 # 显示v2.0的日志及详细内容
git log v2.0 # 显示v2.0的日志
git diff # 显示所有未添加至index的变更
git diff –cached # 显示所有已添加index但还未commit的变更
git diff HEAD^ # 比较与上一个版本的差异
git diff HEAD — ./lib # 比较与HEAD版本lib目录的差异
git diff origin/master..master # 比较远程分支master上有本地分支master上没有的
git diff origin/master..master –stat # 只显示差异的文件,不显示具体内容
git remote add origin git+ssh://git@192.168.53.168/VT.git # 增加远程定义(用于push/pull/fetch)
git branch # 显示本地分支
git branch –contains 50089 # 显示包含提交50089的分支
git branch -a # 显示所有分支
git branch -r # 显示所有原创分支
git branch –merged # 显示所有已合并到当前分支的分支
git branch –no-merged # 显示所有未合并到当前分支的分支
git branch -m master master_copy # 本地分支改名
git checkout -b master_copy # 从当前分支创建新分支master_copy并检出
git checkout -b master master_copy # 上面的完整版
git checkout features/performance # 检出已存在的features/performance分支
git checkout –track hotfixes/BJVEP933 # 检出远程分支hotfixes/BJVEP933并创建本地跟踪分支
git checkout v2.0 # 检出版本v2.0
git checkout -b devel origin/develop # 从远程分支develop创建新本地分支devel并检出
git checkout — README # 检出head版本的README文件(可用于修改错误回退)
git merge origin/master # 合并远程master分支至当前分支
git cherry-pick ff44785404a8e # 合并提交ff44785404a8e的修改
git push origin master # 将当前分支push到远程master分支
git push origin :hotfixes/BJVEP933 # 删除远程仓库的hotfixes/BJVEP933分支
git push –tags # 把所有tag推送到远程仓库
git fetch # 获取所有远程分支(不更新本地分支,另需merge)
git fetch –prune # 获取所有原创分支并清除服务器上已删掉的分支
git pull origin master # 获取远程分支master并merge到当前分支
git mv README README2 # 重命名文件README为README2
git reset –hard HEAD # 将当前版本重置为HEAD(通常用于merge失败回退)
git rebase
git branch -d hotfixes/BJVEP933 # 删除分支hotfixes/BJVEP933(本分支修改已合并到其他分支)
git branch -D hotfixes/BJVEP933 # 强制删除分支hotfixes/BJVEP933
git ls-files # 列出git index包含的文件
git show-branch # 图示当前分支历史
git show-branch –all # 图示所有分支历史
git whatchanged # 显示提交历史对应的文件修改
git revert dfb02e6e4f2f7b573337763e5c0013802e392818 # 撤销提交dfb02e6e4f2f7b573337763e5c0013802e392818
git ls-tree HEAD # 内部命令:显示某个git对象
git rev-parse v2.0 # 内部命令:显示某个ref对于的SHA1 HASH
git reflog # 显示所有提交,包括孤立节点
git show HEAD@{5}
git show master@{yesterday} # 显示master分支昨天的状态
git log –pretty=format:’%h %s’ –graph # 图示提交日志
git show HEAD~3
git show -s –pretty=raw 2be7fcb476
git stash # 暂存当前修改,将所有至为HEAD状态
git stash list # 查看所有暂存
git stash show -p stash@{0} # 参考第一次暂存
git stash apply stash@{0} # 应用第一次暂存
git grep “delete from” # 文件中搜索文本“delete from”
git grep -e ‘#define’ –and -e SORT_DIRENT
git gc
git fsck
参考:
https://git-scm.com/docs/git-add
https://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html
https://gist.github.com/guweigang/9848271#file-git_toturial

自己理解:

编写前

Git checkout  liangchen

#先拉去远程分支代码

Git pull origin liangxianchen:liangxianchen

Git add 我改过的代码文件  #只提交自己该过的代码

Git commit -m

Git push origin liangchen:liangxianchen(从liangchen本地分支推送到liangxianchen远程分支)

编写代码

如果想回推代码在没有提交前

Git status

git diff 被修改的文件名

就可以看到被修改过的原始内容

Git 出现!reject的情况

解决办法:首先将本地修改的文件备份,然后强制让远程文件覆盖本地仓库文件

Git fetch –all

Git reset –hard origin/liangxianchen

当删除本地文件后,如何与远端同步?

首先不能直接在本地按del键删除,这样对远端没有任何影响,必须使用git rm filename 命令删除,其次,删除完之后,git commit -m “delete filename” 一下,最后 git push就OK了