194. Linux中的命令之进程与程序

bash和sh

区别与联系

sh

路径为 /bin/sh

sh就是Bourne shell

这个是UNIX标准的默认shell,对它评价是concise简洁 compact紧凑 fast高效,由AT&T编写,属于系统管理shell

bash

路径为 /bin/bash

bash是 GNU Bourne-Again SHell (GNU 命令解释程序 “Bourne二世”)

linux标准的默认shell ,它基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash是Bourne shell的超集,bash完全兼容Bourne shell,也就是说用Bourne shell的脚本不加修改可以在bash中执行,反过来却不行,bash的脚本在sh上运行容易报语法错误。

小结

bash是sh的完整版,bash完全兼容sh命令,反之不行

用法

具体信息请使用如下命令查看

  • 查看bash

    1
    $ man bash
  • 查看sh

    1
    $ man sh

    这个命令有点问题, 具体请自行搜索吧

解决命令的权限问题, 可以扩大权限的范围

比如要向 test.asc 文件中随便写入点内容,可以:

1
$ echo "hello world" > test

如果将 test.asc 权限设置为只有 root 用户才有权限进行写操作:

1
2
3
4
$ sudo chown root:root test
$ ll
total 4.0K
-rw-rw-r-- 1 root root 12 Jan 22 03:14 test

然后,我们使用 sudo 并配合 echo 命令再次向修改权限之后的 test.asc 文件中写入信息:

1
2
$ sudo echo "hello" >> test
bash: test: Permission denied

这时可以看到 bash 拒绝这么做,说是权限不够。这是因为重定向符号 “>” 和 “>>” 也是 bash 的命令。我们使用 sudo 只是让 echo 命令具有了 root 权限,但是没有让 “>” 和 “>>” 命令也具有 root 权限,所以 bash 会认为这两个命令都没有像 test.csv文件写入信息的权限。

解决这一问题的途径有两种。

  • 方法1

    利用 "sh -c" 命令,它可以让 bash 将一个字串作为完整的命令来执行,这样就可以将 sudo 的影响范围扩展到整条命令。具体用法如下:

    1
    $ sudo bash -c 'echo "hello" >> test'

    或者

    1
    $ sudo sh -c 'echo "hello" >> test'
  • 方法2

    利用管道和 tee 命令,该命令可以从标准输入中读入信息并将其写入标准输出或文件中,具体用法如下:

    1
    2
    $ echo "hello" | sudo tee -a test
    hello

    注意,tee 命令的 “-a” 选项的作用等同于 “>>” 命令,如果去除该选项,那么 tee 命令的作用就等同于 “>” 命令

命令解释器工作模式

命令解释器(这里是/bin/sh或/bin/bash) 有两种工作模式:交互模式和非交互模式。

交互模式

交互模式如下图所示,就是使用 ssh 连接到 Linux 服务器上,然后在终端上敲入命令就可以显示对应结果。这样与终端进行交互执行命令的方式就称为交互模式。

非交互模式

非交互模式如下图所示,非交互模式就是调用 bash 解释器,通过 bash -csh -c 后接命令的形式来解释执行命令。

安装软件

从命令行安装应用程序最常见的方法是通过软件仓库(software respositories)(软件存储的地方)使用软件包管理器。所有 Linux 应用程序都以包的形式分发,这些包只不过是与包管理系统(package management system)相关联的文件。每个 Linux 发行版都有一个包管理系统,但它们并不完全相同。

什么是包管理系统

软件包管理系统是由一组工具和文件格式组成,它们一起用于安装、更新和卸载 Linux 应用程序。最常见的两个软件包管理系统来自Red Hat和Debian。Red Hat、CentOS和Fedora都使用rpm系统(.rpm文件),而Debian、Ubuntu、Mint和Ubuntu使用dpkg(.deb文件)。Gentoo Linux使用一个名为Portage的系统,Arch Linux只使用tarball(.tar文件)。这些系统之间的主要区别在于它们如何安装和维护应用程序。

你可能很想知道一个 .rpm, .deb, 或者 .tar 文件里面放的是什么。其实这都只是一些普通的、旧的归档文件(比如zip),其中包含应用程序的代码、安装说明、依赖项(它可能依赖于哪些其他应用程序)以及配置文件应该放在哪里。读取并执行所有这些指令的软件称为包管理器(package manager)。

Debian、Ubuntu、Mint等

Debian,Ubuntu,Mint 还有其他基于 Debian 的发行版都使用 .deb 文件和 dpkg 包管理系统。在这种系统中有两种方式安装应用程序,你可以使用 apt 应用程序从软件仓库中安装,也可以使用 dpkg 应用程序从 .deb 文件中安装应用程序。

下面我们分别来看下这两种方式。

使用 apt 安装很容易:

1
$ sudo apt install app_name

使用 apt 卸载应用也很简单:

1
$ sudo apt remove app_name

要更新已安装的应用,首先需要更新应用仓库:

1
$ sudo apt update

更新完成后,就可以更新任何应用程序了:

1
$ sudo apt upgrade

如果要只更新某一个应用呢?也可以:

1
$ sudo apt update app_name

另外,如果你想要安装的应用在 Debian 仓库中不可用,但是可以下载一个 .deb 文件,那么你可以使用 dpkg(apt帮助管理)手动安装:

1
$ sudo dpkg -i app_name.deb

RHEL、CentOS、Fedora、Mageia和OpenMandriva

Red Hat 的上游项目 Fedora 和“中游”项目 CentOS 使用 dnf 软件包管理器。它有自己的语法,是RPM系统的前端。虽然语法不同,但 dnf 在机制和目标相同的意义上与 apt 相似。Mageia 和 OpenMandriva 发行版曾经专门关注用于包管理的 urpmi,现在它们的发行版中也包括 dnf

dnf 包管理器实际上是 yum 命令的继承者。长期以来,yum 都在用户服务器中使用自己的语言,因此为了避免破坏用户系统上已经存在了十多年的自定义脚本,yumdnf 现在可以互换(事实上,yum 现在是基于 dnf 的)。

安装一个应用的命令:

1
$ sudo dnf install app_name

卸载应用的命令也很简单:

1
$ sudo dnf remove app_name

更新:

1
$ sudo dnf upgrade --refresh

dnf(或 yum)命令是 RPM 打包系统的前端。如果在软件存储库中找不到应用程序,但可以直接从其供应商网站下载,则可以使用 dnf 手动安装 .rpm 文件。

1
$ sudo dnf install ./app_name.rpm

如你所见,通过命令行安装、卸载或者更新 Linux 应用一点都不难。事实上,一旦你习惯了它,你就会发现它比基于GUI的管理工具更快。

关于命令行安装的更多信息,可以访问Debian Apt wiki官网Yum cheat sheet,还有DNF wiki

查看进程

ps

Linux ps (process status)命令用于显示当前进程的状态,类似于 windows 的任务管理器。

语法

1
ps [options] [--help]

参数说明

ps 的参数非常多, 在此仅列出几个常用的参数并大略介绍含义

参数 说明
-A 列出所有的进程
-e 显示所有进程
-f 全格式
-w 显示加宽可以显示较多的资讯
-au 显示较详细的资讯
-aux 显示所有包含其他使用者的行程

au(x) 输出格式

1
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
字段 说明
USER 行程拥有者
PID pid
%CPU 占用的 CPU 使用率
%MEM 占用的记忆体使用率
VSZ 占用的虚拟记忆体大小
RSS 占用的记忆体大小
TTY 终端的次要装置号码 (minor device number of tty)
STAT 见[STAT: 该行程的状态](#STAT: 该行程的状态)
START 行程开始时间
TIME 执行的时间
COMMAND 所执行的指令

STAT: 该行程的状态

参数 说明
D 无法中断的休眠状态 (通常 IO 的进程)
R 正在执行中
S 静止状态
T 暂停执行
Z 不存在但暂时无法消除
W 没有足够的记忆体分页可分配
< 高优先序的行程
N 低优先序的行程
L 有记忆体分页分配并锁在记忆体内 (实时系统或捱A I/O)

例子

查找指定进程格式:

1
ps -ef | grep 进程关键字

grep命令是查找,中间的|是管道命令是指ps命令与grep同时执行。

例如显示所有的Java进程:

1
2
3
4
$ ps -ef | grep java
root 15702 1 5 Jul01 ? 12:53:53 /opt/jdk1.8.0_151/bin/java -Xms512m -Xmx512m -Xmn256m -Dnacos.standalone=true -Dnacos.member.list= -Djava.ext.dirs=/opt/jdk1.8.0_151/jre/lib/ext:/opt/jdk1.8.0_151/lib/ext -Xloggc:/opt/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dloader.path=/opt/nacos/plugins/health,/opt/nacos/plugins/cmdb -Dnacos.home=/opt/nacos -jar /opt/nacos/target/nacos-server.jar --spring.config.location=file:/opt/nacos/conf/,classpath:/,classpath:/config/,file:./,file:./config/ --logging.config=/opt/nacos/conf/nacos-logback.xml --server.max-http-header-size=524288 nacos.nacos
root 18199 1 0 Jun17 ? 04:45:18 /opt/jdk1.8.0_151/bin/java -Djava.util.logging.config.file=/opt/edudev/tomcat_8093_dataanalysis/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Dignore.endorsed.dirs= -classpath /opt/edudev/tomcat_8093_dataanalysis/bin/bootstrap.jar:/opt/edudev/tomcat_8093_dataanalysis/bin/tomcat-juli.jar -Dcatalina.base=/opt/edudev/tomcat_8093_dataanalysis -Dcatalina.home=/opt/edudev/tomcat_8093_dataanalysis -Djava.io.tmpdir=/opt/edudev/tomcat_8093_dataanalysis/temp org.apache.catalina.startup.Bootstrap start
root 19511 1 1 Jul09 ? 00:45:24 java -jar -Dserver.port=11288 /opt/gateway/version2/activeservice/edu-service-active-1.0.0-SNAPSHOT.jar --spring.profiles.active=test

显示进程信息

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
29
30
31
32
33
$ ps -A
PID TTY TIME CMD
1 ? 01:42:12 systemd
2 ? 00:00:00 kthreadd
3 ? 00:03:30 ksoftirqd/0
5 ? 00:00:00 kworker/0:0H
7 ? 00:01:04 migration/0
8 ? 00:00:00 rcu_bh
9 ? 04:32:00 rcu_sched
10 ? 00:00:00 lru-add-drain
11 ? 00:02:33 watchdog/0
12 ? 00:01:50 watchdog/1
13 ? 00:00:51 migration/1
14 ? 00:02:25 ksoftirqd/1
16 ? 00:00:00 kworker/1:0H
17 ? 00:01:48 watchdog/2
18 ? 00:00:48 migration/2
19 ? 00:01:58 ksoftirqd/2
...

#显示指定用户信息
$ ps -u root
PID TTY TIME CMD
1 ? 01:42:12 systemd
2 ? 00:00:00 kthreadd
3 ? 00:03:30 ksoftirqd/0
5 ? 00:00:00 kworker/0:0H
7 ? 00:01:04 migration/0
8 ? 00:00:00 rcu_bh
9 ? 04:32:00 rcu_sched
10 ? 00:00:00 lru-add-drain
11 ? 00:02:33 watchdog/0
...

显示所有进程信息,连同命令行:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ps -ef //显示所有命令,连带命令行
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:22 ? 00:00:02 /sbin/init
root 2 0 0 10:22 ? 00:00:00 [kthreadd]
root 3 2 0 10:22 ? 00:00:00 [migration/0]
root 4 2 0 10:22 ? 00:00:00 [ksoftirqd/0]
root 5 2 0 10:22 ? 00:00:00 [watchdog/0]
root 6 2 0 10:22 ? /usr/lib/NetworkManager

root 31302 2095 0 17:42 ? 00:00:00 sshd: root@pts/2
root 31374 31302 0 17:42 pts/2 00:00:00 -bash
root 31400 1 0 17:46 ? 00:00:00 /usr/bin/python /usr/sbin/aptd
root 31407 31374 0 17:48 pts/2 00:00:00 ps -ef

top

在Linux系统中,查看系统运行状态(或者故障排除),比如内存或者CPU使用率的时候,可以使用top命令,它提供了系统运行情况的信息。根据man手册,top程序提供了运行系统的动态实时视图,它可以显示系统摘要信息以及当前由Linux内核管理的进程或线程的列表。

通常,在top命令的信息中,我们只需要快速浏览一下,就可以确定当前系统的问题是什么。但是top命令的的功能远不止我们看到的这些,具体的功能会因你运行的系统版本(procps-ng, Busybox, BSD)而异,可以参照man手册。

要运行top命令,可以直接在终端中键入:

1
$ top

正在运行的进程显示在顶部屏幕的表标题下方,系统统计信息显示在其上方。如下图所示:

Z 键可以更改输入的颜色,这可以让输出更易读。按 1 键查看系统上每个CPU 核心的图形表示。反复按 1 以评估CPU核心的核心统计信息。

通过调用 top 命令,然后按 m 键,可以以图形方式显示内存使用情况。

字段说明

1
2
3
4
5
6
7
8
9
10
11
$top
top - 09:14:56 up 264 days, 20:56, 1 user, load average: 0.02, 0.04, 0.00
Tasks: 87 total, 1 running, 86 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.2%st
Mem: 377672k total, 322332k used, 55340k free, 32592k buffers
Swap: 397308k total, 67192k used, 330116k free, 71900k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 2856 656 388 S 0.0 0.2 0:49.40 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 7:15.20 ksoftirqd/0
4 root RT 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
  • 第一行

    参数 说明
    09:14:56 系统当前时间
    264 days, 20:56 系统开机到现在经过了多少时间
    1 users 当前2用户在线
    load average: 0.02, 0.04, 0.00 系统1分钟、5分钟、15分钟的CPU负载信息
  • 第二行

    参数 说明
    Tasks 任务;
    87 total 很好理解,就是当前有87个任务,也就是87个进程。
    1 running 1个进程正在运行
    86 sleeping 86个进程睡眠
    0 stopped 停止的进程数
    0 zombie 僵死的进程数
  • 第三行

    参数 说明
    Cpu(s) 表示这一行显示CPU总体信息
    0.0%us 用户态进程占用CPU时间百分比,不包含renice值为负的任务占用的CPU的时间。
    0.7%sy 内核占用CPU时间百分比
    0.0%ni 改变过优先级的进程占用CPU的百分比
    99.3%id 空闲CPU时间百分比
    0.0%wa 等待I/O的CPU时间百分比
    0.0%hi CPU硬中断时间百分比
    0.0%si CPU软中断时间百分比

    注:这里显示数据是所有cpu的平均值,如果想看每一个cpu的处理情况,按1即可;折叠,再次按1;

  • 第四行

    参数 说明
    Men 内存的意思
    8175320kk total 物理内存总量
    8058868k used 使用的物理内存量
    116452k free 空闲的物理内存量
    283084k buffers 用作内核缓存的物理内存量
  • 第五行

    参数 说明
    Swap 交换空间
    6881272k total 交换区总量
    4010444k used 使用的交换区量
    2870828k free 空闲的交换区量
    4336992k cached 缓冲交换区总量
  • 再下面就是进程信息

    参数 说明
    PID 进程的ID
    USER 进程所有者
    PR 进程的优先级别,越小越优先被执行
    NInice
    VIRT 进程占用的虚拟内存
    RES 进程占用的物理内存
    SHR 进程使用的共享内存
    S 进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
    %CPU 进程占用CPU的使用率
    %MEM 进程使用的物理内存和总内存的百分比
    TIME+ 该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
    COMMAND 进程启动命令名称

常用的 top 命令选项

如果只查找由特定用户启动的进程,可以使用 -u 选项获取该信息:

1
$ top -u 'username'

要获取系统上空闲进程的列表,请使用 -i 选项:

1
$ top -i

信息的更新时间默认为3秒,可以将更新间隔设置为以秒为单位的任意值。比如我们将其更改为5,如下所示:

1
$ top -d 5

此外,还可以在计时器上运行top。例如,以下命令将迭代次数设置为两次,然后退出:

1
$ top -n 2

top命令交互操作指令

参数 说明
q 退出top命令
<Space> 立即刷新
s 设置刷新时间间隔
c 显示命令完全模式
t: 显示或隐藏进程和CPU状态信息
m 显示或隐藏内存状态信息
l 显示或隐藏uptime信息
f 增加或减少进程显示标志
S 累计模式,会把已完成或退出的子进程占用的CPU时间累计到父进程的MITE+
P %CPU使用率排行
T MITE+排行
M %MEM排行
u 指定显示用户进程
r 修改进程renice
kkill 进程
i 只显示正在运行的进程
W 保存对top的设置到文件^/.toprc,下次启动将自动调用toprc文件的设置。
h 帮助命令。
q 退出

注:

  • 强调一下,使用频率最高的是PTM,因为通常使用top,我们就想看看是哪些进程最耗cpu资源、占用的内存最多;
  • 通过”shift + >”或”shift + <”可以向右或左改变排序列
  • 如果只需要查看内存:可用free命令。
  • 只查看uptime信息(第一行),可用uptime命令;

实例

实例1:多核CPU监控

在top基本视图中,按键盘数字“1”,可监控每个逻辑CPU的状况;

1
2
3
4
5
6
7
8
9
10
11
12
13
[rdtfr@bl685cb4-t ^]$ top
top - 09:10:44 up 20 days, 16:51, 4 users, load average: 3.82, 4.40, 4.40
Tasks: 1201 total, 10 running, 1189 sleeping, 0 stopped, 2 zombie
Cpu0 : 1.3%us, 2.3%sy, 0.0%ni, 96.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 1.3%us, 2.6%sy, 0.0%ni, 96.1%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 1.0%us, 2.0%sy, 0.0%ni, 92.5%id, 0.0%wa, 0.0%hi, 4.6%si, 0.0%st
Cpu3 : 3.9%us, 7.8%sy, 0.0%ni, 83.2%id, 0.0%wa, 0.0%hi, 5.2%si, 0.0%st
Cpu4 : 4.2%us, 10.4%sy, 0.0%ni, 63.8%id, 0.0%wa, 0.0%hi, 21.5%si, 0.0%st
Cpu5 : 6.8%us, 12.7%sy, 0.0%ni, 80.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu6 : 2.9%us, 7.2%sy, 0.0%ni, 85.3%id, 0.0%wa, 0.0%hi, 4.6%si, 0.0%st
Cpu7 : 6.2%us, 13.0%sy, 0.0%ni, 75.3%id, 0.0%wa, 0.0%hi, 5.5%si, 0.0%st
Mem: 32943888k total, 32834216k used, 109672k free, 642704k buffers
Swap: 35651576k total, 5761928k used, 29889648k free, 16611500k cached

实例2:高亮显示当前运行进程

1
在top基本视图中,按键盘“b”(打开/关闭加亮效果);

实例3:显示完整的程序命令

命令:top -c

1
2
3
4
5
6
7
8
9
10
11
12
[rdtfr@bl685cb4-t ^]$ top -c
top - 09:14:35 up 20 days, 16:55, 4 users, load average: 5.77, 5.01, 4.64
Tasks: 1200 total, 5 running, 1192 sleeping, 0 stopped, 3 zombie
Cpu(s): 4.4%us, 6.0%sy, 0.0%ni, 83.8%id, 0.2%wa, 0.0%hi, 5.5%si, 0.0%st
Mem: 32943888k total, 32842896k used, 100992k free, 591484k buffers
Swap: 35651576k total, 5761808k used, 29889768k free, 16918824k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2013 apache 18 0 403m 88m 5304 S 25.0 0.3 6:37.44 /usr/sbin/httpd
18335 pubtest 22 0 65576 996 728 R 7.8 0.0 0:00.24 netstat -naltp
16499 rdtfare 15 0 13672 2080 824 R 2.6 0.0 0:00.38 top -c
29684 rdtfare 15 0 1164m 837m 14m S 2.3 2.6 148:47.54 ./autodata data1.txt
12976 pubtest 18 0 238m 9000 1932 S 1.6 0.0 439:28.44 tscagent -s TOEV_P

实例4:显示指定的进程信息

命令:top -p pidid

1
2
3
4
5
6
7
8
/opt/app/tdv1/config#top -p 17265
top - 09:17:34 up 455 days, 17:55, 2 users, load average: 3.76, 4.56, 4.46
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 7.8%us, 1.9%sy, 0.0%ni, 89.2%id, 0.0%wa, 0.1%hi, 1.0%si, 0.0%st
Mem: 8175452k total, 8103988k used, 71464k free, 268716k buffers
Swap: 6881272k total, 4275424k used, 2605848k free, 6338184k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17265 tdv1 15 0 56504 828 632 S 0.0 0.0 195:53.25 redis-server

指定进程信息有多个时,需要结合其它工具将回车替换为,(-p 支持pid,pid,pid语法)

命令:top -p pgrep MULTI_PROCESS | tr “\n” ”,” | sed ‘s/,$//’

1
2
3
4
5
6
7
8
9
/opt/app/tdv1$top -p `pgrep java | tr "\\n" "," | sed 's/,$//'`
top - 14:05:31 up 53 days, 2:43, 9 users, load average: 0.29, 0.34, 0.22
Tasks: 3 total, 0 running, 3 sleeping, 0 stopped, 0 zombie
Cpu(s): 5.9%us, 8.2%sy, 0.0%ni, 86.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 66082088k total, 29512860k used, 36569228k free, 756352k buffers
Swap: 32767992k total, 1019900k used, 31748092k free, 15710284k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 27855 rdtfare 20 0 4454m 1.3g 5300 S 0.7 2.0 338:31.37 java
2034 jenkins 20 0 18.3g 5.2g 5284 S 0.3 8.2 56:02.38 java

使用top定位进程

使用 Shift+L 组合键可以按名称查找进程。这将在加粗的表格标题行上方创建一个提示。键入要查找的流程的名称,然后按 Enter 键或 Return 键,以查看在新排序的流程列表中突出显示的该进程的实例。

使用top停止进程

你可以使用top命令停止或者“杀掉”一个正在运行的进程。首先,使用 Shift+Lpgrep 查找要停止的进程,然后按 k 键并输入要停止进程的 ID。默认值为列表顶部的任何值,因此请确保在按下回车键之前输入正确的 PID,否则可能会停掉你本不打算停止的进程。

top命令有许多迭代,包括htop, atop, btop, 还有 ttop。此外,还有一些专用的 top 命令,比如用于电源的 powertop,以及用于网络信息的 ntop 等。

ldd

查看程序依赖库

作用

用来查看程序运行所需的共享库,常用来解决程序因缺少某个库文件而不能运行的一些问题。

示例

查看test程序运行所依赖的库:

1
2
3
4
5
6
$ldd test
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00000039a7e00000)
libm.so.6 => /lib64/libm.so.6 (0x0000003996400000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000039a5600000)
libc.so.6 => /lib64/libc.so.6 (0x0000003995800000)
/lib64/ld-linux-x86-64.so.2 (0x0000003995400000)
  • 第一列:程序需要依赖什么库
  • 第二列: 系统提供的与程序需要的库所对应的库
  • 第三列:库加载的开始地址

通过上面的信息,我们可以得到以下几个信息:

  1. 通过对比第一列和第二列,我们可以分析程序需要依赖的库和系统实际提供的,是否相匹配
  2. 通过观察第三列,我们可以知道在当前的库中的符号在对应的进程的地址空间中的开始位置

如果依赖的某个库找不到,通过这个命令可以迅速定位问题所在;

注解

原理: ldd不是个可执行程式,而只是个shell脚本; ldd显示可执行模块的dependency的工作原理,其实质是通过ld-linux.so(elf动态库的装载器)来实现的。ld-linux.so模块会先于executable模块程式工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。

lsof

一切皆文件

lsof(list open files)是一个查看当前系统文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,该文件描述符提供了大量关于这个应用程序本身的信息。

lsof打开的文件可以是:

  • 普通文件
  • 目录
  • 网络文件系统的文件
  • 字符或设备文件
  • (函数)共享库
  • 管道,命名管道
  • 符号链接
  • 网络文件(例如:NFS file、网络socket,unix域名socket)
  • 还有其它类型的文件,等等

命令参数

参数 说明
-a 列出打开文件存在的进程
-c<进程名> 列出指定进程所打开的文件
-g 列出GID号进程详情
-d<文件号> 列出占用该文件号的进程
+d<目录> 列出目录下被打开的文件
+D<目录> 递归列出目录下被打开的文件
-n<目录> 列出使用NFS的文件
-i<条件> 列出符合条件的进程。(4、6、协议、:端口、 @ip )
-p<进程号> 列出指定进程号所打开的文件
-u 列出UID号进程详情
-h 显示帮助信息
-v 显示版本信息

使用实例

实例1:无任何参数

1
2
3
4
5
6
7
8
9
10
11
$lsof | more
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
init 1 root cwd DIR 253,0 4096 2 /
init 1 root rtd DIR 253,0 4096 2 /
init 1 root txt REG 253,0 150352 1310795 /sbin/init
init 1 root mem REG 253,0 65928 5505054 /lib64/libnss_files-2.12.so
init 1 root mem REG 253,0 1918016 5521405 /lib64/libc-2.12.so
init 1 root mem REG 253,0 93224 5521440 /lib64/libgcc_s-4.4.6-20120305.so.1
init 1 root mem REG 253,0 47064 5521407 /lib64/librt-2.12.so
init 1 root mem REG 253,0 145720 5521406 /lib64/libpthread-2.12.so
...

说明:

lsof输出各列信息的意义如下:

  • COMMAND:进程的名称

  • PID:进程标识符

  • PPID:父进程标识符(需要指定-R参数)

  • USER:进程所有者

  • PGID:进程所属组

  • FD:文件描述符,应用程序通过文件描述符识别该文件。如cwdtxt等:

    标识 说明
    cwd 表示current work dirctory,即:应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改
    txt 该类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的 /sbin/init 程序
    lnn library references (AIX);
    er FD information error (see NAME column);
    jld jail directory (FreeBSD);
    ltx shared library text (code and data);
    mxx hex memory-mapped type number xx.
    m86 DOS Merge mapped file;
    mem memory-mapped file;
    mmap memory-mapped device;
    pd parent directory;
    rtd root directory;
    tr kernel trace file (OpenBSD);
    v86 VP/ix mapped file;
    0 表示标准输入
    1 表示标准输出
    2 表示标准错误

    一般在标准输出、标准错误、标准输入后还跟着文件状态模式:r、w、u等

    标识 说明
    u 表示该文件被打开并处于读取/写入模式
    r 表示该文件被打开并处于只读模式
    w 表示该文件被打开并处于
    空格 表示该文件的状态模式为unknow,且没有锁定
    - 表示该文件的状态模式为unknow,且被锁定

    同时在文件状态模式后面,还跟着相关的锁

    标识 说明
    N for a Solaris NFS lock of unknown type;
    r for read lock on part of the file;
    R for a read lock on the entire file;
    w for a write lock on part of the file;(文件的部分写锁)
    W for a write lock on the entire file;(整个文件的写锁)
    u for a read and write lock of any length;
    U for a lock of unknown type;
    x for an SCO OpenServer Xenix lock on part of the file;
    X for an SCO OpenServer Xenix lock on the entire file;
    space if there is no lock.
  • TYPE:文件类型,如DIRREG等,常见的文件类型:

    参数 说明
    DIR 表示目录
    CHR 表示字符类型
    BLK 块设备类型
    UNIX UNIX 域套接字
    FIFO 先进先出 (FIFO) 队列
    IPv4 网际协议 (IP) 套接字
  • DEVICE:指定磁盘的名称

  • SIZE:文件的大小

  • NODE:索引节点(文件在磁盘上的标识)

  • NAME:打开文件的确切名称

实例2:查找某个文件相关的进程

1
2
3
4
5
$lsof /bin/bash
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
mysqld_sa 2169 root txt REG 253,0 938736 4587562 /bin/bash
ksmtuned 2334 root txt REG 253,0 938736 4587562 /bin/bash
bash 20121 root txt REG 253,0 938736 4587562 /bin/bash

实例3:列出某个用户打开的文件信息

1
2
3
$lsof -u username

-u 选项,u是user的缩写

实例4:列出某个程序进程所打开的文件信息

1
$lsof -c mysql

-c 选项将会列出所有以mysql这个进程开头的程序的文件,其实你也可以写成 lsof | grep mysql, 但是第一种方法明显比第二种方法要少打几个字符;

实例5:列出某个用户以及某个进程所打开的文件信息

1
$lsof  -u test -c mysql

实例6:通过某个进程号显示该进程打开的文件

1
$lsof -p 11968

实例7:列出所有的网络连接

1
$lsof -i

实例8:列出所有tcp 网络连接信息

1
2
3
4
5
6
$lsof -i tcp

$lsof -n -i tcp
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
svnserve 11552 weber 3u IPv4 3799399 0t0 TCP *:svn (LISTEN)
redis-ser 25501 weber 4u IPv4 113150 0t0 TCP 127.0.0.1:6379 (LISTEN)

实例9:列出谁在使用某个端口

1
$lsof -i :3306

实例10:列出某个用户的所有活跃的网络端口

1
$lsof -a -u test -i

实例11:根据文件描述列出对应的文件信息

1
$lsof -d description(like 2)

示例:

1
2
$lsof -d 3 | grep PARSER1
tail 6499 tde 3r REG 253,3 4514722 417798 /opt/applog/open/log/HOSTPARSER1_ERROR_141217.log.001

说明: 0表示标准输入,1表示标准输出,2表示标准错误,从而可知:所以大多数应用程序所打开的文件的 FD 都是从 3 开始

实例12:列出被进程号为1234的进程所打开的所有IPV4 network files

1
$lsof -i 4 -a -p 1234

实例13:列出目前连接主机nf5260i5-td上端口为:20, 21, 80相关的所有文件信息,且每隔3秒重复执行

1
lsof -i @nf5260i5-td:20,21,80 -r 3

实例14: 解决类似umount target is busy挂载盘卸载不掉问题

通过lsof命令处理

1
2
3
# lsof /mnt/
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 16302 root cwd DIR 8,17 50 64 /mnt

找到PID对应的进程或者服务,然后杀死或者停止相应服务即可。

后台运行

Linux 命令是使用终端与系统交互的好方法。但是,有时可能需要一段时间才能完成手头的任务。这迫使用户等待相当长的时间或完全生成一个新的 shell。

幸运的是,您可以通过一些简单的方法在后台运行 Linux 命令。

&

在你的命令后添加一个 &

运行 Linux 后台命令的最简单方法是在命令后添加与号 (&)。例如,如果从终端启动 gedit 文本编辑器,则在关闭编辑器之前无法使用 shell。但是,当您在命令中添加额外的 & 时,您将能够立即使用 shell。

1
$ gedit &

对于需要长时间执行的任务,&符号可以使当前终端窗口不被占用,这样就能继续在同一个终端上工作,甚至关闭终端窗口也不会影响任务的正常执行。不过需要注意的是,如果要求这个任务输出内容到标准输出中(例如 echols),即便使用了 &,也会等待这些输出任务在前台运行完毕;当使用 &将一个进程放置到后台运行的时候,Bash 会提示这个进程的进程 ID。在 Linux 系统中运行的每一个进程都有一个唯一的进程 ID,我们可以使用进程 ID 来暂停、恢复或者终止对应的进程。

nohup

Linux 中 的nohup命令允许管理员运行不受挂断(Hang Up)信号影响的终端命令。您可以使用 nohup 在后台运行 Linux 命令。

nohup 的一个主要好处是,即使您退出 shell,您的命令也会运行。此外,它会生成执行的日志文件。在当前目录或 $HOME 中查找nohup.out。语法是:

1
nohup 命令

如: 后台运行Nmap 端口扫描

1
$ nohup sudo nmap -sS --top-ports=15 192.168.150.1/24

Q: Within zsh the command &! is a shortcut for disown, i.e. the program won’t get killed upon exiting the invoking shell.

程序退出不被杀死, 同时如果再次登录shell, 那么使用 ps -ef | grep "clash" 会显示clash 的父进程是 1, 这是因为创建的父进程被杀死, 那么由该进程创建的子进程会在父进程被杀死后, 父进程改为 1

1
nohup clash &> /dev/null &!

程序退出会被杀死的进程

1
nohup clash &> /dev/null &

fg和bg

命令由前台转后台

  1. 键入 ctrl+z 以暂停这条命令并返回客户端
  2. 键入 bg 命令让这条shell命令在后台执行
  3. 键入 disown -h 这条命令保证当终端关闭时,Shell脚本不会被杀死

更一般地

  1. ctrl+z将任务暂停,这时屏幕会打印这样一句 [1]+ Stopped ./myserver

  2. 使用 bg %1 将这个任务转入后台运行(1 是刚才暂停任务的job号,步骤1中屏幕提示方括号里面的内容,如果忘了也可以用 jobs 命令查看)

  3. 使用 disown –h %1 指定shell退出时不要发送SIGHUP给任务1(注意任务号前面有个%)

命令由后台转前台

  1. 键入 jobs 查看后台执行的命令
  2. 键入 fg %<d> 将 jobs[d] 切换到前台

disown

使用 disown 将 Linux 命令设置为后台运行

Linux 中 的disown命令可以轻松地在后台运行命令。首先,您需要使用& 运算符在后台发送任务。然后,键入disown以将其与shell分离。

1
$ gedit & disown

disown 的一个主要优点是,与 nohup 一样,当您关闭 shell 或注销时,系统不会终止您的任务。

使用 Tmux 在后台运行 Linux 命令

Tmux 是一个强大的多路复用器,它允许我们在单个窗口中运行多个终端会话。对于不熟悉它的人来说,学习 tmux 是一个很好的选择。Tmux 使在 Linux 中运行后台命令毫不费力。

1
tmux new -d 'ping -c 10 8.8.8.8 > www.itpro.net.cn.log'

当您运行上述tmux命令时,它将在单独的 shell 中执行ping命令并将其保留在后台。您可以使用此方法在后台执行任何 Linux 命令。

能够在后台运行命令使系统管理对管理员来说更有效率。您可以通过多种方式为您的任务做背景。像&和Ctrl + Z这样的 Bash 功能很方便,但系统会在 shell 关闭时终止后台作业。另一方面,即使您注销或终止 shell ,诸如nohupdisown 之类的工具也会使您的命令保持运行。

如果您将程序长时间留在后台,如果编码不当,它们可能会变成僵尸进程。这些过程会显着降低系统速度。因此,请确保每隔一段时间识别并杀死僵尸进程。

设置开机自启动

shell启动时自启动

不建议使用: 因为每次启动shell都会启动, 而像clash这样的程序只需要执行一次即可, 并且如果多个用户执行, 会导致后执行clash的shell执行失败.

  1. 打开 ~/.bashrc./.zshrc

  2. 在文件末尾添加又在每次用户登录时执行的命令, 如

    1
    nohup clash &> /dev/null &
  3. source ~/.bashrcsource ~/.zshrc以使更改生效

使用”启动应用程序偏好设置”

(Startup Applications Preferences)

  1. Startup Applications Preferences 的配置信息通常存储在 ~/.config/autostart 目录中的 .desktop 文件中. 每个启动的应用程序都有一个相应的 .desktop 文件,该文件包含有关应用程序启动方式的信息。

添加文件 vim <startup_program>.desktop, 添加如下配置

1
2
3
4
5
6
7
8
9
10
[Desktop Entry]
Type=Application
Exec=<your command>
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_US]=<My Program>
Name=<My Program>
Comment[en_US]=<My startup program>
Comment=<My startup program>

如:

1
2
3
4
5
6
7
8
9
Desktop Entry]
Type=Application
Exec=nohup clash &> /dev/null
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_US]=clash
Name=clash
Comment[en_US]=clash

Exec 行指定了要在启动时运行的程序的路径

使用systemctl方式-RECOMMNAD

采用systemctl的方式

强烈建议看看这个文档,里面介绍了一些参数的具体含义.

  1. 准备工作

    1. 写好脚本(假设脚本名为 exec_clash.sh)或将程序放入本地可以运行, 这里以clash为例, 其中, clash位于 /usr/local/bin/clash

      注: exec_clash.sh 中的 exec_clash 是可根据具体脚本的功能改为相应的名字

    2. 若是脚本, 则赋予脚本运行权限: chmod +x exec_clash.sh

  2. 配置

    1. 编写一个 <clash>.service, 内容如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      [Unit]
      Description=clash

      [Service]
      Type=simple
      User=qeuroal
      ExecStart=/usr/local/bin/clash
      Restart=always
      RestartSec=30
      StartLimitInterval=0

      [Install]
      WantedBy=multi-user.target graphical.target

      说明

      • User: 表示以哪个用户启动,不写默认是root. 而用户名也决定了程序启动去哪里读取相应的配置

      • ExecStart: 就是要执行的语句。

        由于为脚本赋予了x(可执行)权限, 因此, ExecStart后面的内容可以直接写成脚本的绝对路径

      • Restart=always: 只要不是通过systemctl stop来停止服务,任何情况下都必须要重启服务,默认值为no。还可以配置有条件重启,具体参考上述文档。

      • RestartSec=5: 重启间隔,比如某次异常后,等待5(s)再进行启动,默认值0.1(s)

      • StartLimitInterval: 无限次重启,默认是10秒内如果重启超过5次则不再重启,设置为0表示不限次数重启

    2. 拷贝 clash.service/lib/systemd/system, 需要注意权限问题

    3. 修改配置文件后需要重加载配置: sudo systemctl daemon-reload

    4. 设置开机自启: sudo systemctl enable clash.service

      之后就可以直接重启了

    5. 相关操作

      操作 命令
      启动 sudo systemctl start clash.service
      停止 sudo systemctl stop clash.service
      查看状态 sudo systemctl status clash.service
    6. 测试自动重启功能

查看程序启动命令

最佳解决方法

大多数默认应用程序将具有位于/usr/share/applications中的.desktop文件。

要了解在启动其中一个应用程序时将运行的相应终端命令,请打开文件浏览器Nautilus,然后右键单击应用程序的图标以在上下文菜单中选择“属性”。这将为您提供所需的所有详细信息(此处显示将运行gnome-control-center -overview的系统设置)

次佳解决方法

如果通过存储库或dpkg安装了应用程序,则可以使用以下命令:

1
dpkg -l | grep "<application name>"

这将搜索所有已安装的应用程序,并搜索其描述;搜索描述是这里的重要部分,因为描述通常包含应用程序的名称,即使运行它的”command”不包含应用程序名称。

例:
在GNOME中,有一个名为Disk Usage Analyzer的应用程序。但是,从终端运行它的命令不是disk-usage-analyzer。要查找其命令,您可以运行:

1
dpkg -l | grep "disk usage"

输出应包含此条目:

1
2
alaa@aa-lu:~$ dpkg -l | grep "disk usage"
ii baobab 3.6.4-0ubuntu1 i386 GNOME disk usage analyzer

看第二栏。实际运行磁盘使用率分析器的命令是baobab

参考目录

clash API 使用

教程

首先,阅读Clash的API的文档RESTful - Proxies

GET 获取所有代理

1
/proxies

PUT 切换 Selector 中选中的代理

1
/proxies/<name>

(这边的name可以为节点名称,也可以为Selector。只要在proxies/后直接加上字符串就可以,不需要引号或者:

当前接口只支持切换 Selector 中的代理

Path Parameters Body Parameters
name name
string string
代理Selector名称(大小写敏感) 要切换的代理名字

接着,curl使用指南 curl 的用法指南

最后,使用curl切换节点, 使用/proxies获取所有可用代理,然后curl发送PUT请求解决战斗。

Example

1
curl -X PUT -H "Content-Type: application/json" -d '{"name":"节点名"}' http://localhost:port/proxies/<name>

-H 添加 HTTP 请求的标头Content-Type: application/json,根据链接2,不设置标头为application/json可能会有问题。

-d 参数用于发送 POST 请求的数据体。

最后的网址为clash的external-controller的网址端口,最后Selector为要选择的proxy-groups的名称。

实际指令类似下面这条:

  • 获取命令

    1
    curl -X GET -H "Content-Type: application/json" http://127.0.0.1:9090/proxies -o get.json
  • 更改节点

    1
    curl -X PUT -H "Content-Type: application/json" -d '{"name":"香港-08"}' http://127.0.0.1:9090/proxies/国外流量 -o put.json

所有命令

1
2
3
4
5
curl -X PUT -H "Content-Type: application/json" -d '{"name":"香港-08"}' http://127.0.0.1:9090/proxies/国外流量 -o put.json
curl -X PUT -H "Content-Type: application/json" -d '{"name":"香港-08"}' http://127.0.0.1:9090/proxies/国外流量

curl -X GET -H "Content-Type: application/json" http://127.0.0.1:9090/proxies -o get.json
curl -X GET -H "Content-Type: application/json" http://127.0.0.1:9090/proxies

引用

cron创建定时任务

cron是创建定时任务的方法,定时任务称为cronjob,它是在crontab文件中创建的。这是Linux系统用户创建定时任务最简单,也是最古老的方法。

编写定时任务

要创建定时任务,可以使用 -e 选项编辑crontab:

1
$ crontab -e

这将使用默认的文本编辑器打开crontab。要设置默认的文本编辑器,可以使用编辑器环境变量:

1
$ EDITOR=nano crontab -e

cron 语法

要创建一个定时任务,可以编写一个cron表达式,后跟定时任务需要执行的命令。cron表达式在命令运行时进行调度:

  • 分钟(0 - 59)
  • 小时(0 - 23)
  • 日(1 - 31)
  • 月(1 - 12)
  • 星期(0 - 6,0是星期天)

字段中的星号(*)转换为“每个”。例如,此表达式在每个月的每一天每小时的第0分钟运行备份脚本:

1
0 * * * * /opt/backup.sh

下面这个例子在每个星期天上午的3:30执行:

1
30 3 * * 0 /opt/backup.sh

简化语法

最新版本的cron已经实现了简单的语法,而不是cron表达式:

参数 说明
@hourly 在每天的每个小时的第0分钟运行;
@weekly 在每周的星期天的第0个小时第0分钟运行;
@monthly 在每个月的第一天的第0小时第0分钟运行;

例如,下面语句在每天的午夜零点执行备份任务:

1
/opt/backup.sh @daily

怎样结束一个定时任务

一旦你开始了一个定时任务,它就被设计成永远按计划运行。要在启动定时任务后停止它,必须编辑crontab,删除触发作业的行,然后保存。

1
$ EDITOR=nano crontab -e

要停止正在运行的作业,请使用标准Linux进程命令停止正在运行的进程。

它是自动化的

一旦你编写完crontab后,保存并退出编辑器,你的定时任务就已经安排好了,cron将会完成剩余所有的工作。

终止进程

当你在使用 Linux 系统的时候,突然有一个进程导致计算机的运行速度变慢,而这个进程又是一个不怎么重要的进程,所以你想要停止这个进程。

如果命令或者进程正在前台运行,则可以使用 Ctrl+C 终端快捷方式。但是,如果进程不可见(在后台运行),则可以使用专用命令终止它。

术语“终止进程”是指在执行过程中停止进程。如果知道进程ID(PID),可以使用如下命令:

1
kill <signal> <PID>

在上面的语法中,signal 指的是要发送用于终止的 kill 信号,PID 指的是进程的ID。

还有一个 killall 命令,在本文中也会涉及。

在开始终止进程之前,你需要了解一些事情。例如,有哪些终止信号可用,如何找到PID等。

我们先来了解一下信号 (signals) 。

Linux中的终止信号

当一个进程被操作系统或用户终止时,换句话说也就是当进程不是自己完成而结束时,它将被发送一个终端信号。

以下是可用的终止信号:

**信号 ** **数字值 ** **描述 **
SIGHUP 1 信号挂起:当控制它的终端关闭时,发送给进程。
SIGINT 2 信号中断:当用户终止进程时发送给进程的信号。(例如Ctrl+X
SIGKILL 9 信号终止:立即退出进程的信号,不允许它保存当前状态。
SIGTERM 15 信号终止:发送到信号以请求终止进程。进程可以忽略此信号。但这是终止进程的首选方式,因为它可以在进程接收到SIGTERM时释放资源。
SIGSTOP 19 (for x86, ARM and most others)
17 (for Alpha)
23 (for MIPS)
24 (for PARISC)
信号停止:用于停止进程的信号,但稍后将恢复。

信号 9 和 15最常用,这里有一篇文章可了解 SIGKILLSIGTERM 的区别。

获取进程的PID

你还需要知道要终止的流程的详细信息。使用 kill 命令时,必须提供进程的ID(PID)。你可以从进程名称中获取PID:

1
pidof exact_process_name

在Linux命令行中终止进程

我们先来了解 kill 命令,因为它比 killall 更常用。

使用 kill 命令

kill 命令要求你知道要终止的进程的ID以及终止信号(可选)。终止命令的语法很简单,上文也提到过:

1
kill [signal] <PID>

终止信号是可选的,如果没有提供信号,kill 默认发送SIGTERM(15)。

我启动了sleep命令的后台进程(它给了我一个PID),我们尝试使用 kill 命令终止它。如下所示:

1
2
3
4
5
$  sleep 120 &
[1] 125686

$ kill 125686
[1] + terminated sleep 120

如果要使用终止信号,可以使用数值或信号本身:

  • 数值

    1
    2
    3
    4
    5
    $ sleep 120 &
    [1] 125759

    $ kill -9 125759
    [1] + killed sleep 120
  • 信号

    1
    2
    3
    4
    5
    $ sleep 120 &
    [1] 125746

    $ kill -SIGKILL 125746
    [1] + killed sleep 120

注:如果kill命令未指定信号,则默认情况下使用SIGTERM(15)。

使用 killall 命令

如果您不知道一个进程的PID是什么,或者要终止的进程有多个子进程,并且您希望同时终止子进程和父进程,那么可以使用killall命令。

1
killall [signal] <process-name>

kill命令类似,指定终止信号是可选的。当未指定终止信号时,killall 将发送SIGTERM(15)以正常关闭所述进程。

为了演示 killall 的使用,我们将终止两个正在运行的sleep命令。

1
2
3
4
5
6
7
8
9
$ sleep 120 &
[1] 18609

$ sleep 2000 &
[2] 18612

$ killall sleep
[1]- Terminated sleep 120
[2]+ Terminated sleep 2000

此外,你可以使用 -e 标志来查找与进程名称匹配的PID。

使用 pkill 命令

另一个 kill 命令是 pkill 命令。它是 pgrepkill 命令的“组合”。

killall 可以终止所有与进程名称匹配的进程。

pkill 使用模式匹配来匹配进程并终止它们。如下语法:

1
$ pkill [options] pattern

pkill 命令提供以下几个选项:

选项 说明
-u 特定用户所拥有的进程
-x 完全匹配模式的进程
-signal 指定终止信号(默认为SIGTERM)

假设我的计算机上有一个当前不应该运行任何进程的来宾用户,我想终止它们。该怎么做呢?

1
2
3
4
$ ps aux | grep sleep
guest 126061 0.0 0.0 6664 428 pts/3 S 10:14 0:00 sleep 50

$ sudo pkill -u guest

请注意,我使用 sudo 是因为我处理的进程不属于我当前的用户。

kill 和 killall 命令的区别

kill 命令应对的是进程ID(PID),它会根据你提供的 PID 来终止进程。而 killall 针对的是进程名称,它会终止给定进程名称的所有进程。例如,有三个 mysql 实例正在运行,killall 会终止所有的这三个进程:使用 killall mysql 命令;而如果是用 kill 的话,你需要提供这三个实例的PID来终止它们:kill PID1 PID2 PID3

简单地说,kill 命令与 PID(通常是单个 PID)一起使用,killall 命令与进程名称一起使用,并使用进程名称杀死所有进程。

来举个例子。

假如我们启动了三个名为 evince 的程序实例,让其在后台运行:

1
2
3
evince &
evince &
evince &

那么现在,我们有了三个名为 evince 的进程,但是每个进程的进程ID是不同的。

要使用 kill 命令强制停止进程,需要提供所需进程的PID

1
kill PID1

但是,如果使用 killall 的话,提供进程名称,它将杀掉所有使用该进程名称命名的实例:

1
killall evince

如下图所示:

现在我们应该能直观的注意到,kill 命令需要进程ID,killall命令需要进程名称。

使用哪个命令呢,kill 还是 killall ?

由于 kill 命令可用于单个进程,因此更安全。在使用 killall 命令之前,你需要确保没有其他类似名称的进程(并且你不想停掉的)正在运行着。

命令替换和变量替换

$()和``

在操作上,这两者都是达到相应的效果,但是建议使用$(),理由如下:

  1. ``很容易与”搞混乱,尤其对初学者来说。
  2. 在多层次的复合替换中,``必须要额外的转义字符处理(反斜线),而$( )比较直观。
  3. 最后,$( )的弊端是,并不是所有的类unix系统都支持这种方式,但反引号是肯定支持的。

eval

简介

eval 属 Shell 内建命令,通过连接参数构造命令。

eval可以读取一连串的参数,然后按照参数特性来执行。参数数目不限,使用空格分隔每个参数,构造的命令应由 Shell 读取和执行。

eval会对后面的命令进行两遍扫描

  1. 如果第一遍扫描后,命令是个普通命令,则执行此命令;
  2. 如果命令中含有变量的间接引用,则保证间接引用的语义。

也就是说,eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。因此,eval命令适用于那些一次扫描无法实现其功能的变量。

eval 执行以下两个步骤:

  1. step1. 执行变量替换,类似与C语言的宏替代;
  2. step2. 执行替换后的命令串。

分析替换原则

step1. 定义变量

1
2
3
$ a=10
$ b=a
$ c=b

step2. 运行以下命令查看结果

  • 情景1

    1
    2
    $ eval "x=$c"; echo $x
    b

    解释 eval "x=$c":

    step1. 将 $c 替换成 b

    step2. 执行 x=b

  • 情景2

    1
    2
    $ eval "x=\$$c"; echo $x
    a

    解释eval "x=\$$c":

    step1. 第一次扫描, 要执行的命令变为 x=$b

    step2. 执行 x=$b

  • 情景3

    1
    2
    $ eval "x=\$\$$c"; echo $x
    3432188b

    解释 eval "x=\$\$$c":

    step1. 第一次扫描, 要执行的命令变为 x=$$b

    step2. 执行 x=$$b (其中: $$为脚本运行的当前进程ID号, 这里为3432188)

常用示例

  1. 回显变量

    1
    2
    3
    4
    5
    $ NAME=foo
    $ echo $NAME
    foo
    $ eval echo $NAME
    foo
  2. 先替换变量再执行命令

    1
    2
    3
    4
    5
    6
    $ cat test.txt
    Hello World!
    $ command="cat test.txt"; echo $command
    cat test.txt
    $ eval $command
    Hello World!
  3. 获取传给脚本或函数的最后一个参数。

    我们知道 Shell 中使用特殊字符$#可以获取传递给脚本或函数的参数个数,使用$n获取参数(n 为数字),$1表示第一个参数,$2表示第二个参数,所以$$#表示最后一个参数。

    1
    2
    3
    4
    5
    6
    7
    8
    $ cat test.sh
    #!/usr/bin/bash
    echo \$$#
    eval echo \$$#

    $ ./test.sh firstarg lastarg
    $2
    lastarg
  4. 使用动态创建的变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/bin/bash

    total=0
    label="Looping complete. Total:"

    for n in {1..10}
    do
    eval x$n=$n
    echo "Loop" $x$n
    ((total+=$x$n))
    done

    echo $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10

    echo $label $total

    它创建了一个名为 total 的变量,其中包含我们创建的变量值的总和。然后它创建一个名为 label 的字符串变量。这是一个简单的文本字符串。

    我们将循环 10 次并创建 10 个名为 x1x10 的变量。循环主体中的 eval 语句提供“x”并采用循环计数器 $n 的值来创建变量名称。同时,它将新变量设置为循环计数器 $n 的值。

    它将新变量打印到终端窗口,然后使用新变量的值递增 total 变量。

    在循环之外,10 个新变量再次打印,全部打印在一行上。请注意,我们也可以通过它们的真实名称来引用变量,而无需使用它们名称的计算或派生版本。

    最后,我们打印 total 变量的值。

参考: