00-前言

为什么要学linux?

开发的时候经常需要用到,项目完成后通常都部署在linux服务器上,对于线上的项目需要修改配置文件、进行运行状态诊断、修改用户和文件权限、进行服务器组件参数配置等,都需要用到linux的知识。

  1. 学习收益高,从 linux 作为主流服务器都几十年了,很多经典的东西一直都在用,不过时,一次学会终身受用;
  2. 后端开发、服务器、运维、数据库工程师等必备技能;
  3. 为考取 linux 证书(比如RHCSA和RHCE)做准备;
  4. linux的发展过程包含很多有趣的故事和经典哲学,可以细细品味。

主要是学什么?

文件管理、用户、权限管理、命令行操作、自动化脚本、系统状态、定时任务、故障诊断等。

linux 技能参考图

Linux 内核是由芬兰人林纳斯托瓦尔兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的(因为当时Unix太贵了),免费使用和自由传播,类 Unix 操作系统,多用户、多任务、支持多线程和多 CPU,能运行主要的 UNIX 工具软件、应用程序和网络协议,继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。目前工作中主要使用的版本是RedHat、Ubuntu、Centos,延伸阅读请搜索 Linux发行版、GNU软件、GPL协议、Linus Torvalds、开源协议。

下面这张图概述了 计算机 -> Unix -> C语言 -> GNU/Linux、FreeBSD -> Linux、Next -> Linux发行版、MacOS 的大概历史。

大家都知道计算机的发明和二战历史密码破译有关,盟军的胜利离不开科学家艾伦图灵的研究,我们都听过图灵测试和图灵奖,图灵的人生传奇而神秘,被称为计算机和人工智能之父,他对计算机的主要贡献通俗的理解就是,图灵从数学理论上证明计算机这个高科技是可行的,所以图灵也叫计算机科学之父。 现代计算机都是冯诺依曼结构,但是在冯诺依曼之前,计算机就已经被发明出来了,那就是阿塔纳索夫-贝瑞计算机,这是第一台自动电子计算机,这个计算机引入了二进制和布尔逻辑的实现,但是这个计算机没有cpu,使用真空管进行数字计算。所以阿塔纳索夫最大的贡献就是他以实际行动证明,计算机这个高科技真的可以搞出来。 冯诺依曼团队主要贡献是发表了著名的101报告,明确规定二进制替代十进制运算,把计算机分为5大组件,奠定了电子计算机逻辑结构设计的基础,导致了EDVAC计算机的面世,这是一台被证明是可靠和可生产的计算机,所以通俗的说冯诺依曼对计算机的贡献主要是,你看计算机这样设计实现出来更好,他被称为现代计算机之父。 所以这三个家伙其实都是计算机之父。

但是最初的计算机不但昂贵,而且笨拙,只能处理单个任务,他们准备给计算机弄个os操作系统来调度一下,让计算机可以为多任务,多用户来服务。MIT、贝尔实验室、美国通用一起弄了个叫Multics的项目,这个项目的愿景很大,也做出了一些成就,但是最终还是因为步子迈的太大,扯到蛋了,以失败告终。 有个牛人叫肯汤普森,因为Multics的项目失败以后,他一下没事可干了,但是他之前在Multics上写了个star trap的游戏,他想把他移植到一个叫PDP-7的小型机上玩,经过几天996后他就成功的在PDP-7上给他的游戏弄个运行环境,其实就是个os了,他把游戏给他的同事们玩,大家都对他的游戏不感兴趣,对他运行游戏的这个os很感兴趣,不断完善修改以后,他们给这个os取了个名字叫Unics,意思是反Multics,其实是一种对Multics的自嘲。 最早的os都是汇编语言编写的,但当时有人用bcpl这种高级语言也能写操作系统,肯汤普森和他的小伙伴意识到高级语言写操作系统是可行的,但是汤普森不满意bcpl,于是创造了b语言来写新的os,但是b语言还不够好,他的好朋友丹尼斯里奇改进了b语言,就是NEW-B语言(牛X语言),后来发展成了著名的c语言,你猜的没错,这两哥们就是c语言之父,他们也是1983年的图灵奖得主。BTW 谷歌的go语言也是肯汤普森带队。

这期间在1974年,用c语言重写的unix已经可以见人了,因为unix的优点很多教育机构、企业都在研究和改造unix系统,贝尔实验室的母公司AT&T看到unix发展的这么好,把母公司拆成了分公司规避了当时限制它的反垄断法,想赚点大钱。同时其他的组织比如伯克利大学开始基于unix研发免费的类unix系统,就是后来有名的BSD。这期间还经常和贝尔实验室打官司,一个说用了他们的源代码,一个根本没用都自己写的。后来重写的类unix系统叫FreeBSD、NetBSD、OpenBSD等等,BSD也是乔布斯NeXTSTEP系统的前身(以Mach和BSD为基础),而NeXTSTEP系统演化成今天的MacOS和iOS。 当时有个黑客斯托曼,非常不满那种软件商用的行为,他创建了自由软件基金会、Emacs、GNU软件,继续倡导软件开源免费的理念,也搞出了很多有用的软件,但是这个过程中他发现还是受到系统内核的诸多限制,搞的Hurd内核又非常的不科学,很尴尬。

当时有个教授特南鲍姆编写了不含AT&T源代码的类Unix系统Minix系统,在学校大家都在用来科研和教学,在芬兰上学的学生托瓦尔兹,他觉得Minix系统不满足他的要求,他开始按自己的想法修改,随着不断改造,他发现自己都已经重写了一个操作系统内核了,他把这个系统发了个邮件,这个内核简洁清晰,很多大牛都在上面添加代码,斯托曼正在因为GNU软件没有内核支持而发愁,于是邀请他加入,1994年linux 1.0 诞生了,成为GNU/Linux,为了突出他的贡献,改名为linux,关于linux的发声,有的人读liuliukesi,有的人linakesi,高兴就好。 今天已经有很多linux的发行版了。工作中常用的是 redhat、ubuntu、centos 发行版。 关于linux的故事就是这样,一个游戏、一些牛人、一堆利益造就了科技技术的进步,希望你喜欢这个不够严谨的叙述,为什么要讲这个故事? 因为这些故事可以充值信仰,我们要面向信仰学习编程。

01-linux介绍

主要的linux发行版:

1.1 REHL - Red Hat Enterprise Linux 是红帽公司的商业版本

1.2 CentOS - Community Enterprise Operating System 社区操作系统,基于 RHEL 重新编译的开源免费版

1.3 Ubuntu - 基于 Debian GNU/Linux 由 Canonical Ltd 打造

1.4 SUSE - 德国 SuSE Linux AG 公司的发行版

1.5 Debian - 致力于创建自由操作的合作组织及其作品

1.6 OEL - Oracle Enterprise Linux 对 Oracle 的软件支持较好

学习的时候推荐使用的系统是 Ubuntu (https://ubuntu.com/download),CentOS 之前较为流行,但是目前已经停止维护了,不建议使用。

02-环境搭建

可选方案:

  1. 在本地电脑上安装双系统 (不推荐)
  2. 使用虚拟机软件,比如 VirtualBox (https://www.virtualbox.org 免费,推荐)、VMware (https://www.vmware.com)、Parallels Desktop(macos)等
  3. 使用 Docker (https://www.docker.com) 或者直接使用 Docker Machine (https://github.com/docker/machine)
  4. 使用 Vagrant 工具 (https://www.vagrantup.com)
  5. 使用 ssh 命令远程登录到云服务器(比如阿里云ECS、亚马逊EC2等)上操作

安装的时候我们不需要桌面,只需要下载 server 版本即可。

ubuntu 下载地址 https://releases.ubuntu.com/20.04.1/ubuntu-20.04.1-live-server-amd64.iso centos 下载地址 http://ftp.hosteurope.de/mirror/centos.org/8.2.2004/isos/x86_64/CentOS-8.2.2004-x86_64-minimal.iso

在 VirtualBox 中新建虚拟机,在光盘设置处选择下载好的 ISO 文件作为系统系统文件,根据提示进行虚拟机的安装操作。

安装完成后需要注意几个点:

先使用 ping www.bilibili.com 看看是否可以上网,能显示 ip 则说明网络和 DNS 解析正常,忽略第 1 条和第 2 条。

第一:ubuntu 可能无法上网,在 sudo nano /etc/resolv.conf 中加入 nameserver 8.8.8.8,这种方式是临时修改,永久修改的方法是 sudo nano /etc/systemd/resolved.conf 中的 DNS 节点 (如果你会 vi 可以用 vi 修改)

第二:centos 使用 ip addr 看不到网卡,是因为网卡没有启动,编辑 sudo nano /etc/sysconfig/network-scripts/ifcfg-enp0s 把里面的 ONBOOT=no 改为 ONBOOT=yes 重启

第三:因为没有桌面,VirtualBox 的界面操作起来很小不方便,可以使用 ssh 客户端工具登录虚拟机终端。

ubuntu 开启 sshd 服务:

sudo apt install openssh-server
sudo service ssh start

centos 开启 sshd 服务:

sudo yum install openssh-server
sudo service sshd start

使用这些命令的时候会要求输入密码,以后章节会解释什么是 sudo,以及如何避免输入密码。

VirtualBox 中安装虚拟机后,默认是 NAT 网络模式,sshd 远程服务的默认端口是 22,我们可以做一个端口转发,比如把本地的 2222 端口和虚拟机的 22 端口打通,这样输入 ssh -p 2222 localhost 就可以连接到虚拟机的终端了。对于 windows 的用户可以使用 putty 或者 SecureCRT 等 ssh 客户端进行连接。你发现每次 ssh 登录时,需要输入用户的密码,以后章节会解释什么是公钥/私钥,以及如何免密码登录。

TIP

最后啰嗦一句,最好的本地开发环境是 mac os 系统(还有 Docker),如果想在开发这条路上走下去,推荐大家尽早购入 Macbook Pro.

03-第一次入壳or入坑

Shell 英文原意是壳ké(坑kēng),可以简单的理解成用户和系统的翻译官,Shell 接受你的输入传给操作系统执行,并且回显执行的结果,所以 Shell 是操作系统提供给用户的一个接口,可以运行命令、解释程序等。Shell 解释器有很多种,通常我们见到的是 Bash,还有其他的例如 zsh、ksh、tcsh、csh、dash 等,不需要去学习,注意是不需要。

在电影创战纪中sam进入废弃的地下室,在电脑上为了弄清楚是谁登录了系统(他想确认是不是他的father),输入的第一个命令就是 whoami,也就是让 Shell 告诉我们,当前登录的账号名称,如果还想知道还有谁登录了系统可以输入 w 命令(该命令还显示了系统运行时间和负荷,把 uptime 命令的事情干了)。

~ 🍎 whoami
wangbo
~ 🍎 w
15:36  up 15 days, 43 mins, 5 users, load averages: 3.02 3.32 3.18
USER     TTY      FROM              LOGIN@  IDLE WHAT
wangbo   console  -                241120  15days -
wangbo   s000     -                14:52       - w

输入 uname 可以显示系统的名称,如果想得到完整的系统的信息输入 uname -a,-a 被称为命令的参数,所以 uname 命令的作用就像是你在问 Shell:你是谁? OK,再问问 Shell 今夕是何年,输入 date,Shell 会打印当前的日期和时间,请记住作为服务器,系统时间的正确性尤为重要,如果时间不对,有些软件不会按预期工作或者会出现莫名其妙的 Bug。

~ 🍎 uname
Darwin
~ 🍎 uname -a
Darwin macpro.local 20.1.0 Darwin Kernel Version 20.1.0: Sat Oct 31 00:07:11 PDT 2020; root:xnu-7195.50.7~2/RELEASE_X86_64 x86_64
~ 🍎 date
2020年12月 9日 星期三 15时36分56秒 CST

想知道系统运行多久了? 没问题,输入 uptime 后会显示系统运行时间以及当前的负荷情况,如果你感觉到系统很慢很卡的时候,也可以输入 uptime 命令确认一下,看看是不是系统负荷太高了。第一次和 Shell 交互的感觉是不是就像和一个人对话?

~ 🍎 uptime
15:36  up 15 days, 43 mins, 5 users, load averages: 2.86 3.28 3.16

如果觉得屏幕信息显示太多了,可以输入 clear 清屏,不想玩了,输入 exit 退出当前 Shell 终端,到此你已经成功的开启了 Shell 的大门,不管是多么复杂的工作,以后都可以在 Shell 终端里输入命令完成,最重要的是你亲手去试一试。

04-环境变量是幕后黑手

当我们输入 uptime 命令后,Shell 怎么知道这个命令在哪里呢? 因为 Shell 有个上下文环境,它会在预定的目录里去查找命令,这些预定的目录可以输入 echo $PATH 来获取,PATH 就是一个环境变量,它定义了命令搜索的路径和顺序(冒号分割,从前往后查找)。如果两个目录里都有一个叫 uptime 的命令,那输入的是哪一个呢? 使用 which 命令来确定。

~ 🍎 which uptime
/usr/bin/uptime
~ 🍎 echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

如果 which 本身有冲突怎么办? 好问题,输入 which which 试试,Shell 显示 which 是个内置的命令。

~ 🍎 which which
which: shell built-in command

可以输入 env 命令查看当前的环境,必要的可以使用文件的全路径来调用(比如调用的命令所在的目录没有加入 PATH 搜索目录),比如输入 /usr/bin/uptime 执行肯定没有歧义了。

~ 🍎 env
SHELL=/bin/zsh
HOME=/Users/wangbo
LOGNAME=wangbo
USER=wangbo
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
......

如果没有 PATH 预定义设置,那么想运行 uptime 就必须每次都输入 /usr/bin/uptime 全路径,很麻烦。有了环境变量,输入命令的时候就不用输入全路径了,带来了很大的方便,但是如果环境变量设置不正确或者被破坏,那么也可能导致系统无法使用。环境变量的作用远非解决命令输入路径这么简单,但暂时还不用深入去学习。

05-常用的文件操作

linux 被发明的时候有2大核心思想,第一是一切都是文件,包括内核、设备、进程、网络等;第二是每个软件都有确定的用途。第二条很好理解,第一条是一个非常高级的抽象,可见文件在 linux 系统中的重要性,而本节的文件严格说的只能算狭义的文件,主要指用户存储在磁盘上的文档资料,比如文本、音频、视频、代码、日志等等。

初次进入 Shell 后显示 ~$ 或 ~#,其中 ~ 是特殊的目录(目录也是文件),输入 pwd 可以看到它的真实目录,这个目录就是当前登录用户的主目录(home),很多人也叫家目录,为什么要用 ~ 表示,这和早期的键盘有关,原意是编辑文本的时候回到行首(这个牵涉到 vi 的故事,关于 vi 教程点击这里)。

使用 ls 可以看到目录下的文件列表,这个命令有几个常用的参数:-l -a -h,比如 ls -l 看详细文件列表,ls -lh 文件尺寸大小友好显示(人类可读的的 K M G等),ls -lha 会显示出隐藏文件(以.开头的两个文件),有2个特殊的文件:. 表示当前目录,.. 表示上一级目录,另外注意每行的第一个d表示文件是一个目录,rwx 分别表示文件权限的读(read)、写(write)、执行(execute),后面是文件所属的用户和用户组、文件的时间,关于文件权限和用户、用户组很重要,但是现在不用深入学习,在多用户管理章节会详细介绍文件的权限和用户、用户组关系。

~ 🍎 pwd
/Users/wangbo
test 🍎 ls -lha
total 0
drwxr-xr-x   4 wangbo staff  128 12 10 09:44 .  // 当前目录
drwxrwxrwx+ 48 wangbo staff 1.5K 12 10 09:44 .. // 上一级目录 1.5K 大小
drwxr-xr-x   2 wangbo staff   64 12 10 09:44 cat
drwxr-xr-x   2 wangbo staff   64 12 10 09:44 dog
-rw-r--r--   1 wangbo staff    0 12 10 09:48 readme.txt // 没有d是一个普通文件

命令 cd 用于切换当前目录,原文是 change directory,cd 经常和~、-搭配使用,cd ~ 立刻回到用户的主目录,cd - 则切换到上次工作的目录,cd .. 回到上一层目录,cd . 什么也不会改变,因为 . 就是当前目录,cd . 就是保持原地不动。

test 🍎 ls
cat  dog  readme.txt
test 🍎 cd cat
cat 🍎 pwd
/Users/wangbo/Desktop/test/cat
cat 🍎 cd ..
test 🍎 cd dog
dog 🍎 pwd
/Users/wangbo/Desktop/test/dog
dog 🍎 cd -
~/Desktop/test
test 🍎 cd ~
~ 🍎 pwd
/Users/wangbo

有时候我们希望观察下大概的目录结构,可以用 tree 命令,如果目录和子目录很多,可以用 -L 参数限制它显示的层级,-L 后的数字表示要显示的层级数。如果你在 Mac 系统,遇到中文乱码的话,需要加一个 -N 参数。

test 🍎 tree
.
├── cat
│   ├── 1.txt
│   └── 2.txt
├── dog
│   ├── 1.txt
│   └── 2.txt
└── readme.txt

2 directories, 5 files

控制显示层数:

test 🍎 tree -L 1 // 只显示1层
.
├── cat
├── dog
└── readme.txt

2 directories, 1 file

新建目录使用 mkdir (mkdir directory),rm (remove directory) 用于删除目录,rm 有2个重要的参数 -r 和 -f,-r 表示把子目录的子目录等递归删除,-f 表示强制删除,使用 -f 参数要特别小心,以免造成数据丢失,有很多初学者甚至工作中的运维人员因为误输入了 rm -rf / 强制删除系统的根目录/导致系统被损坏了(可以把虚拟机做一个快照,然后去实验这个命令,回头用快照恢复)。cp 用于拷贝目录,带上 -R 参数表示子目录的子目录递归拷贝,需要指定要拷贝的目录和目的地。mv (move) 用于移动文件,也可以重命名。

test 🍎 mkdir backup
test 🍎 ls
backup  cat  dog  readme.txt
test 🍎 cp readme.txt backup
test 🍎 ls
backup  cat  dog  readme.txt
test 🍎 cp -R cat backup
test 🍎 tree
.
├── backup
│   ├── cat
│   │   ├── 1.txt
│   │   └── 2.txt
│   └── readme.txt
├── cat
│   ├── 1.txt
│   └── 2.txt
├── dog
│   ├── 1.txt
│   └── 2.txt
└── readme.txt

4 directories, 8 files
test 🍎 rm -rf backup
test 🍎 ls
cat  dog  readme.txt
test 🍎 mv changelog.log change.log
test 🍎 ls
backup  cat  change.log  dog  history.txt  one.txt  readme.txt

touch 命令可以新建一个空白的文件,对于简单的文本文件可以使用 echo 命令,> 是重定向,后面会详细学习。使用 cat 可以打印出文件的内容,但是有时候文件很长很大,实际生产环境有的文件上百M甚至超过1G,这时候 cat 就不好用了,需要使用 more 或者 less 命令,以缓冲区浏览的方式,关于这两个命令如何使用,请输入 more --help 和 less --help 了解,当你需要用到时候,可以详细去了解用法。请记住每个命令都可以输入 --help 或者 -h 了解它的参数和示例(有问题就用 man 命令,比如 man less),请用这种方法去试试 head 和 tail 命令是干什么的(按 q 退出帮助 j 和 h上下滚动,请注意有的简化的 linux 镜像没有包含帮助手册,可能没有这个命令),友情提示 tail -f 非常有用,在观察不断更新的文件、日志时很爽。

test 🍎 touch changelog.log
test 🍎 ls
cat  changelog.log  dog  readme.txt
test 🍎 echo 文件历史 > history.txt
test 🍎 ls
cat  changelog.log  dog  history.txt  readme.txt
test 🍎 cat history.txt
文件历史
test 🍎 more history.txt
文件历史
test 🍎 less history.txt

如果目录和子目录很多,想定位一个文件或者想确定是否存在某个文件需要用到 find 命令,这个命令在实际工作中非常非常常用,它的功能非常非常的多,比如可以使用 find . -name 以名字的方式搜索当前目录,find . -size 按大小搜索当前目录,find . -type 按文件类型查找等等,find 的用法和技巧太多需要在工作中慢慢积累。

test 🍎 pwd
/Users/wangbo/Desktop/test
test 🍎 find . -name 1.txt
./cat/1.txt
./dog/1.txt

ln 用于创建文件的链接,经常使用的是 ln -s 创建文件的符号链接(-s --symbolic),可以理解为另外一个文件的快捷方式,如果源文件被修改了,这个快捷方式的文件也会同步修改。

test 🍎 ln -s cat/1.txt one.txt
test 🍎 ls
cat  changelog.log  dog  history.txt  one.txt  readme.txt
test 🍎 echo 修改 > cat/1.txt
test 🍎 cat one.txt
修改
test 🍎 ls -lh
total 4.0K
drwxr-xr-x 4 wangbo staff 128 12 10 09:55 cat
-rw-r--r-- 1 wangbo staff   0 12 10 10:14 changelog.log
drwxr-xr-x 4 wangbo staff 128 12 10 09:55 dog
-rw-r--r-- 1 wangbo staff  13 12 10 10:14 history.txt
lrwxr-xr-x 1 wangbo staff   9 12 10 10:31 one.txt -> cat/1.txt // 箭头指向了源文件
-rw-r--r-- 1 wangbo staff   0 12 10 09:48 readme.txt

tar、zip、unzip都是文件压缩和解压缩命令,linux 中经常出现 .tar.gz 结尾的文件,就需要使用 tar 来解压缩,zip 和 unzip 通常对简单的 zip 文件更方便。tar 常用参数 -czvf,-c 表示创建,-z表示压缩,-v显示,-f指定文件,相对应的 tar -zxvf 表示解压缩,-x 表示解压,请自己试试文件解压的命令参数。

test 🍎 mkdir backup
test 🍎 tar -czvf backup/cat.tar.gz cat // 把 cat 目录压缩成 cat.tar.gz 放到 backup 目录去
a cat // 打印文件列表是因为使用 -v 参数
a cat/2.txt
a cat/1.txt
test 🍎 ls -lh backup
total 4.0K
-rw-r--r-- 1 wangbo staff 171 12 10 10:40 cat.tar.gz

对于文件操作还有一些重要的命令比如 chmod chown chgrp 等,chmod +w 可以为文件加上写权限,chmod a+rwx 可以为系统所有用户加上读写执行权限,你可能会看到 chmod 755 或者 chmod 4755 这样的命令参数,请参考后面多用户管理的内容。

test 🍎 ls -l
total 4
drwxr-xr-x 3 wangbo staff  96 12 10 10:40 backup
drwxr-xr-x 4 wangbo staff 128 12 10 09:55 cat
-rw-r--r-- 1 wangbo staff   0 12 10 10:14 changelog.log
drwxr-xr-x 4 wangbo staff 128 12 10 09:55 dog
-rw-r--r-- 1 wangbo staff  13 12 10 10:14 history.txt
lrwxr-xr-x 1 wangbo staff   9 12 10 10:31 one.txt -> cat/1.txt
-rw-r--r-- 1 wangbo staff   0 12 10 09:48 readme.txt
test 🍎 chmod a+w readme.txt
test 🍎 ls -l readme.txt
-rw-rw-rw- 1 wangbo staff 0 12 10 09:48 readme.txt // 注意新增的2个w表示用户组用户和其他用户都具有该文件的写权限

这些就是基本的文件操作了,有了这些命令基础,你可以在 shell 里到处逛一逛了,有一些系统目录很重要,如果被破坏,系统可能会瘫痪,所以你有必要先去了解一下 linux 的各个目录的作用,输入 ls -l / 看看有哪些目录,不同的 linux 发行版目录不同,总的说来如果你在自己的 home 目录 ~ 下新建一个文件夹,在里面做实验是比较安全的,毕竟 ~ 是完全属于你的地盘。

06-内存和磁盘尚能饭否

看看内存的情况输入 free -m,-m参数表示以 M 为单位显示大小,和内存有关常用的还有个 vmstat 命令(MacOS下是vm_stat),df -h 查看磁盘分区的空间占用情况,du -sh . 统计当前目录的大小,du -sh * 按每个目录单独统计,fdisk -l 可以查看磁盘分区的情况。

很多 linux 命令的结果很复杂,所以说执行 linux 命令简单,看懂结果难,所以我们最好聚焦于哪些我们常常会用到的参数和结果上,忽略那些次要的参数和结果,如果你想把买个 linux 命令的每个参数和结果都搞明白都记住,可能地球都毁灭了,所谓 linux 学海无涯,而吾生有崖,全部学,报废也。

free 主要看 free 和 cache、available,表示系统可用的内存,df 主要看 Available、Use,如果磁盘占用90%多就要去找那些不用的大文件删除掉,怎么找呢? 使用 find 和 du 命令去找,找到大文件使用 cat /dev/null > 大文件路径 清空他占用的磁盘空间,为什么不是 rm ? 因为有时候文件被进程占用,光是清空文件内容,磁盘空间并不释放,所以第一个 cat /dev/null 好使,/dev/null 就是一个黑洞,把黑洞写入文件,文件就被清空了,不用担心这些技巧,这是经验问题,通常 rm 还是会有效的,不过没有效果的时候要想起这个黑洞用法。

docker@manager:~/test$ free -m
              total        used        free      shared  buff/cache   available
Mem:            989          73         615         283         299         616
Swap:          1164           0        1164
docker@manager:~/test$ du -sh .
4.0K  .
docker@manager:~/test$ du -sh *
4.0K  backup
0 cat
0 changelog.log
0 dog
0 history.txt
docker@manager:~/test$ df -h
Filesystem                Size      Used Available Use% Mounted on
tmpfs                   890.5M    283.4M    607.2M  32% /
tmpfs                   494.7M         0    494.7M   0% /dev/shm
/dev/sda1                17.8G    174.0M     16.7G   1% /mnt/sda1
cgroup                  494.7M         0    494.7M   0% /sys/fs/cgroup
/Users                  465.7G    458.7G      7.1G  98% /Users
/dev/sda1                17.8G    174.0M     16.7G   1% /mnt/sda1/var/lib/docker

07-哪些东西在搞飞机

目前为止,你知道输入的命令都对应着磁盘上的文件,当命令被 Shell 解释执行后,操作系统就会创建对应的进程,进程干完活后会退出,也有很多进程一直不退出,长期驻留在系统内。要查看当前有哪些进程,输入 ps 命令,该命令常用的额参数组合是 ps -ef 和 ps aux,在一个进程里还可以创建进程,成为子进程,就产生了进程树的概念,输入 pstree 你讲看到一棵树,这些命令中 PID 表示进场的号码,如要强行终止某个进程,可以使用 kill PID 来执行,类似的命令还有 killall xxx 和 pkill xxx 杀死所有名称都是 xxx 的进程,你可能会看到 kill -9 PID 这样的命令,-9 表示一个信号,输入 kill -l 可查看所有的信号,你会发现第 9) 号是 SIGKILL,表示强制结束信号,信号是进程异步通讯的一种机制,有些信号具有强制性,有些信号进程可以忽略不处理。

1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

不管你使用 ps 还是 ps -ef、ps aux、pstree,你都会发现列表里有一项就是这个命令本身,因为你运行命令的时候,这个进程还未结束,所以列表里包含了它对应的进程。

docker@manager:~/test$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1   4164  1016 ?        Ss   03:33   0:09 /sbin/init
root         2  0.0  0.0      0     0 ?        S    03:33   0:00 [kthreadd]
root         4  0.0  0.0      0     0 ?        I<   03:33   0:00 [kworker/0:0H]
root         6  0.0  0.0      0     0 ?        I<   03:33   0:00 [mm_percpu_wq]
......
docker    7798  0.0  0.1   7276  1880 pts/0    R+   06:47   0:00 ps aux
docker@manager:~/test$ pstree
init-+-VBoxService
     |-acpid
     |-crond
     |-dockerd---containerd
     |-forgiving-getty---bash
     |-forgiving-getty---sleep
     |-ntpd
     |-sshd---sshd---sshd---bash---pstree // 当前进程
     |-udevd---2*[udevd]
     `-2*[udhcpc]

操作系统会为每个进程分配资源,这些资源包括单不仅限于cpu时间片、内存、文件句柄、数据缓存区等等,前面说到 uptime 可以查看系统负荷,如果系统负荷非常大,要找出是哪个进场占用了系统的资源,怎么办呢? 最简单的办法是输入 top 命令看看,这个命令会打印和不断刷新进程的信息,通常我们最关心的是 PID、%CPU、%MEM、COMMAND 这4列, 按 M 可以按照进程占用内存大小排序,按 P 可以按照进程占 CPU 排序,c 可以切换进程的全路径,h 查看帮助,w 可以把当前设置保存到文件里面,按 enter 可以立马刷新进程信息,你可以一直按一直按或按住不放,不同的发行版本 top 显示的数据不太相同,但是大同小异,这个命令还有一个增强版 htop,但是需要单独安装软件包,由于还没有讲到软件包管理这章,所以暂且知道就行了。

top - 15:06:38 up 284 days, 14:28,  1 user,  load average: 0.01, 0.07, 0.07
Tasks: 313 total,   1 running, 311 sleeping,   0 stopped,   1 zombie
Cpu(s):  1.3%us,  0.7%sy,  0.0%ni, 98.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   4048224k total,  3646004k used,   402220k free,    80040k buffers
Swap:        0k total,        0k used,        0k free,  1029896k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 1374 mongodb   20   0 6024m 365m 291m S    0  9.2   4116:39 /usr/bin/mongod --config /etc/mongod.conf
 ......

在 top 界面按下 ctrl + z 后,top 命令就被切换到了后台,可以使用 ps 和 jobs 命令确认。

ubuntu@ebs-33107:~/test$ ps
  PID TTY          TIME CMD
 5111 pts/0    00:00:00 bash
 6379 pts/0    00:00:03 top
 6622 pts/0    00:00:00 ps
ubuntu@ebs-33107:~/test$ jobs
[1]+  Stopped                 top

可以使用 fg 命令把恢复到前台显示,输入 fg 1,其中 1 是任务的序号,只有一个任务的时候可只输入 fg,bg 可以将任务在后台继续运行。当我们执行一个命令需要运行很长时间的时候,就可以把进程切换到后台,同时输入其他的命令。需要注意的是,当用户退出 shell 的时候任务会停止执行,当我们远程登录到服务器上执行任务的时候,如果希望退出了后任务依然运行,可以借助 nohup 这个命令,它的英文愿意是 no hang up,默认情况下它会把命令的输出放在当前目录的 nohup.out 文件中,默认的 & 表示把命令放到后台执行。

ubuntu@ebs-33107:~$ nohup find / -name xxx &
[1] 8427
ubuntu@ebs-33107:~$ nohup: ignoring input and appending output to ‘nohup.out’

ubuntu@ebs-33107:~$ jobs
[1]+  Running                 nohup find / -name xxx &

我们退出终端后重新进入,执行 jobs 啥也没有? 不是说好了要一直运行吗,骗子! 这里有一个坑注意一下,jobs 命令本身只能回显当前终端下的任务列表,需要使用 ps aux 或者 ps -ef 命令来确认,什么还是没有,骗子! 可能因为 find 已经完成使命结束了,试试 cat nohup.out 看看执行结果。

来一个更极端的例子,我们使用 ping localhost 查看本机的 ip 地址,退出后再执行 jobs 查看发现啥也没有,好家伙 jobs 居然不讲武德,使用 tail 和 ps 可以确认任务仍然在运行。

ubuntu@ebs-33107:~$ nohup ping localhost &
[1] 11897
ubuntu@ebs-33107:~$ nohup: ignoring input and appending output to ‘nohup.out’

ubuntu@ebs-33107:~$ jobs
[1]+  Running                 nohup ping localhost &

// 使用 tail -f nohup.out 命令查看 ping 任务可以看到不断有输出,
64 bytes from localhost (127.0.0.1): icmp_seq=93 ttl=64 time=0.097 ms
64 bytes from localhost (127.0.0.1): icmp_seq=491 ttl=64 time=0.094 ms
64 bytes from localhost (127.0.0.1): icmp_seq=748 ttl=64 time=0.088 ms
64 bytes from localhost (127.0.0.1): icmp_seq=707 ttl=64 time=0.018 ms
64 bytes from localhost (127.0.0.1): icmp_seq=94 ttl=64 time=0.080 ms
// ctrl + c 退出 tail 命令

exit  // 退出终端

//  重新登录终端后输入 jobs 啥也没有
ubuntu@ebs-33107:~$ jobs
ubuntu@ebs-33107:~$ tail -f nohup.out
64 bytes from localhost (127.0.0.1): icmp_seq=42 ttl=64 time=0.096 ms
64 bytes from localhost (127.0.0.1): icmp_seq=43 ttl=64 time=0.125 ms
64 bytes from localhost (127.0.0.1): icmp_seq=44 ttl=64 time=0.089 ms

......
// 一直在输出,说明 ping localhost 还在干活儿
// 用 ps 确认一下
ubuntu@ebs-33107:~$ ps aux | grep ping // 表示过滤出有 ping 字符串的进程,后面管道章节详细介绍
ubuntu   12757  0.0  0.0   8604   860 ?        S    16:04   0:00 ping localhost
ubuntu   13276  0.0  0.0  17776   924 pts/0    S+   16:05   0:00 grep --color=auto ping

输入 kill 12757 让它见马克思去,rm nohup.out 清理输出文件。

你可能会看到很多很多使用 nohup 命令的时候带有 >/dev/null 和 2>&1 的命令参数,这是关于输入输出重定向的知识,后面的内容会详细介绍。

到目前为止,你已经学习了大量的 linux 基础知识,虽然有的知识点只是走马观花或者只有一个概念性的东西,学习是不断积累的工程,学习方法比知识本身更重要,一个软件80%的功能我们是用不到的,所以即便你觉得某个知识点很模糊,很肤浅,也不要觉得心理不安,用到时候你再去搜索,再去看看他的帮助,重要的是建立 linux 这一系列的知识体系,哦,原来 linux 是这个样子工作的,随着不断的积累和试错,在脑子里不断修正对它最初的样子,你会发现原来一切都是那么经典。

08-多用户管理

早期的计算机智能通过纸条打孔输入程序,让计算机执行任务,所以打孔得排队,人们都希望计算机能够同时处理多个用户的命令,因此才有了 Unix 的诞生,每个人都登录到一个 Unix 的机器上,执行自己的任务,最前面我们知道可以输入 w 来查看系统登录的其他用户。怎么为其他人新建一个他自己的用户呢? 可以使用 useradd 命令,如果希望为这个用户建立一个 home 目录,则需要带上 -m 参数。试一试,但是发现报错了。

wangbo@wangbo-VirtualBox:~$ useradd -m zhangsan
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.

因为新建用户需要写入 /etc/passwd 文件,而该文件是 linux 系统的核心文件,只有超级用户才能写入,我们在前面加上 sudo 再次新建,系统可能会提示你输入当前用户的密码,sudo 的意思是以管理员的身份来执行密码(试试 sudo whoami),在 /etc/suoders 文件的最后面可以加入 wangbo ALL=(ALL) NOPASSWD: ALL 来取消每次使用 sudo 都需要密码的不变性,那么怎么修改这个文件呢? 千万不要直接打开这个文件,请使用 sudo visudo,默认情况下系统会用 nano 编辑器来打开 /etc/suoders 文件,修改完成后按 ctrl + x 退出,输入 y 确认保存;也有可能系统是用 vi 编辑器来打开,关于 vi、vim 的知识请点击快速学 vim 教程open in new window

请注意命令带上 sudo 就是以管理员的身份来执行命令,通过 mkdir 的小实验来看看,使用 sudo mkdir 建立的文件夹,它的用户和用户组默认都是 root,root 是 linux 里的超级用户,默认情况下,工程师都不会用 root 登录系统去操作。

wangbo@wangbo-VirtualBox:~/test$ mkdir normal
wangbo@wangbo-VirtualBox:~/test$ sudo mkdir special
wangbo@wangbo-VirtualBox:~/test$ ls -l
total 8
drwxrwxr-x 2 wangbo wangbo 4096 1210 17:27 normal
drwxr-xr-x 2 root   root   4096 1210 17:27 special

好了,回到正题,现在给张三建立一个账号,并且确认张三的主目录确实存在,使用 passwd 命令给 zhangsan 分配初始密码。

wangbo@wangbo-VirtualBox:~/test$ sudo useradd -m zhangsan
wangbo@wangbo-VirtualBox:~/test$ ls -l /home/zhangsan/
total 0
wangbo@wangbo-VirtualBox:~/test$ cat /etc/passwd | grep zhangsan
zhangsan:x:1001:1001::/home/zhangsan:/bin/sh

每个用户都隶属于一个用户,可以使用 id 命令来查看用户的组信息,可以使用 su zhangsan 切换到 zhangsan 用户,相当于用 zhangsan 的用户登录了系统,如果张三对你不好,你可以用他的身份做点不好的事情,如果和张三的感情彻底破裂了,可以使用 sudo userdel zhangsan 删除他。想要切换自己的账户,输入 exit 命令,不确定的话,别忘了 whoami 命令。

wangbo@wangbo-VirtualBox:~/test$ id zhangsan
uid=1001(zhangsan) gid=1001(zhangsan) groups=1001(zhangsan)
wangbo@wangbo-VirtualBox:~/test$ su zhangsan
Password:
$
$ pwd
/home/wangbo/test
$ cd ~
$ pwd
/home/zhangsan // 这是张三的主目录了
$ whoami
zhangsan
$ exit // 退出了 zhangsan 的身份
wangbo@wangbo-VirtualBox:~/test$ whoami
wangbo // 还是做回自己好

如果 zhangsan 在自己的主目录下新建一个文件,使用 ls -l 可以看到默认的权限。

$ touch myfile.txt
$ ls -l
total 0
-rw-rw-r-- 1 zhangsan zhangsan 0 1210 17:56 myfile.txt
-rw-rw-r-- // 第一个 - 表示是普通文件,接下来 rw- 表示用户 zhangsan 的权限,再接下来 rw- 表示用户组 zhangsan 其他用户成员的权限,最后 r-- 表示其他用户

r=read w=write x=execute,rw表示读写,rwx表示读写执行,作为其他用户只有 r 权限,尝试对他写入内容会报没有权限的错误。

wangbo@wangbo-VirtualBox:~$ ls -l /home/zhangsan/test/myfile.txt
-rw-rw-r-- 1 zhangsan zhangsan 16 1210 18:04 /home/zhangsan/test/myfile.txt
wangbo@wangbo-VirtualBox:~$ cat /home/zhangsan/test/myfile.txt
我是张三的
wangbo@wangbo-VirtualBox:~$ echo try to hack > /home/zhangsan/test/myfile.txt
-bash: /home/zhangsan/test/myfile.txt: Permission denied

用户 zhangsan 如果希望其他用户可以对文件进行写入,可以使用 chmod 命令,执行完后最后一段由 r-- 变为了 rw-,其中 o 的含义是 others,类似的还有 a 和 u,a 表示所有用户,u 表示用户组,所以 chmod a+w 就是为所有用户增加 w 权限,chmod a-w 就是为所有用过户删除写入权限。

zhangsan@wangbo-VirtualBox:~/test$ chmod o+w myfile.txt
zhangsan@wangbo-VirtualBox:~/test$ ls -l
total 4
-rw-rw-rw- 1 zhangsan zhangsan 16 1210 18:04 myfile.txt

现在其他用户可以修改 zhangsan 的文件了。

wangbo@wangbo-VirtualBox:~$ echo try to hack > /home/zhangsan/test/myfile.txt

张三去掉所有用户的权限后,权限位显示为 r-- r-- r--,也就是所有人都能读,但都不能写入,连 zhangsan 自己也不能写入,可以重新执行 chmod a+rw 皆大欢喜。

zhangsan@wangbo-VirtualBox:~/test$ chmod a-w myfile.txt
zhangsan@wangbo-VirtualBox:~/test$ ls -la
total 12
drwxrwxr-x 2 zhangsan zhangsan 4096 1210 18:04 .
drwxr-xr-x 3 zhangsan zhangsan 4096 1210 18:04 ..
-r--r--r-- 1 zhangsan zhangsan   12 1210 18:16 myfile.txt
zhangsan@wangbo-VirtualBox:~/test$ echo fix it > myfile.txt
bash: myfile.txt: Permission denied

同理,你会看到 chmod +rw、chmod +x、chmod o+x 等等很多用法,但更多的是 chmod 755 myfile.txt 这种形式,这是什么鬼?

755 三个数字分别代表文件隶属用户的权限、用户组的权限、其他用户的权限,那么 7 和 5 怎么来的呢? 简单,Unix 的缔造者说来,让 r=4 w=2 x=1,7 就是 rwx 读写执行,5 就是 rx 读执行,这种说法很不严谨,但你现在可以这么简单理解。那么 chmod 666 myfile.txt 啥意思呢? 双击666,不对。6 就是 rw,666 意思就是文件所有者、文件用户组、其他用户都可以读和写文件,用 ls -l 确认一下,文件的全线显示为 rw- rw- rw-。

zhangsan@wangbo-VirtualBox:~/test$ chmod 666 myfile.txt
zhangsan@wangbo-VirtualBox:~/test$ ls -l
total 4
-rw-rw-rw- 1 zhangsan zhangsan 12 1210 18:16 myfile.txt

把 755 和 666 都说清楚了,其他的数字你可以自己推理了,比如 644 表示什么? 但是更诡异的,你还会看到 chmod 4755,不是说了三个数字吗? 不讲武德。先建立一个可执行文件,为它加上 +x 执行权限。

zhangsan@wangbo-VirtualBox:~/test$ chmod +x printdate.sh
zhangsan@wangbo-VirtualBox:~/test$ ls -l printdate.sh
-rwxrwxr-x 1 zhangsan zhangsan 5 1210 18:30 printdate.sh
zhangsan@wangbo-VirtualBox:~/test$ ./printdate.sh
20201210日 星期四 18:31:21 CST

学习方法最重要,我们先给他来个 chomd 4755 看看有什么变化?

zhangsan@wangbo-VirtualBox:~/test$ chmod 4755 myfile.txt
zhangsan@wangbo-VirtualBox:~/test$ ls -la
total 16
drwxrwxr-x 2 zhangsan zhangsan 4096 1210 18:30 .
drwxr-xr-x 3 zhangsan zhangsan 4096 1210 18:30 ..
-rwsr-xr-x 1 zhangsan zhangsan   12 1210 18:16 myfile.txt // 虽然 myfile.txt 不是可执行文件,也加上了 s 权限位
-rwxrwxr-x 1 zhangsan zhangsan    5 1210 18:30 printdate.sh

我们发现第一节的权限位显示为 rws,出现了一个 s,这个位叫做 suid,suid 的作用是让其他用户在执行文件的时候具有和文件所有者相同的权限,由于脚本的杀伤力很大,很多类 Unix 系统会忽略脚本的 s 位,只对二进制执行文件生效。

zhangsan@wangbo-VirtualBox:~/test$ chmod 4755 printdate.sh
zhangsan@wangbo-VirtualBox:~/test$ ls -l
total 8
-rwsr-xr-x 1 zhangsan zhangsan 12 1210 18:16 myfile.txt
-rwsr-xr-x 1 zhangsan zhangsan  5 1210 18:30 printdate.sh // 脚本也有 s 权限位了,别人在执行的时候相当于有 zhangsan 的访问权限

为什么会有这种需求? 下面2个例子帮助你理解。

第一: 网络设备管理是系统的核心,root 超级用户写了上网的程序,其他用户也要上网,但是其他用户运行上网的程序时,程序还要访问一堆核心的配置文件,这些文件只有 root 才能访问,所以 o+x 后其他用户可以运行程序,但还是无法上网,所以加 s 后,其他用户运行程序的时候就具有和 root 用户一样的权限了,也可以访问核心文件,从而上网了。

第二:每个用户都可以运行 passwd 命令修改自己的密码,但是 /etc/passwd 是系统的核心文件,你不能说每次改自己的密码都需要 root 用户帮你改吧,大家都是有隐私的对吧,怎么办呢? 使用 suid 机制,通过学过的知识验证一下。

zhangsan@wangbo-VirtualBox:~/test$ which passwd
/usr/bin/passwd
zhangsan@wangbo-VirtualBox:~/test$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 528  2020 /usr/bin/passwd

发现 passwd 这个文件,天生具有 s 权限位,所以 s 是必须的存在,不存在 linux 用户系统无法工作,明白? 但请记住,给文件 s 权限位是非常危险的,一定要知道被授权的可执行文件究竟是在干什么事情。

和文件权限有关的还有2个重要的命令,一个是 chgrp 修改文件的用户组,一个是 chown 修改文件的所有者和用户组,chown 有个 -R 参数,表示递归修改子目录,非常重要,赶紧去试试吧。

说了这么多次用户组,用户组从来哪里来呢? 和 user 类似,有 groupadd、groupdel、groupmod 等命令,修改用户所属的用户组,需要用到 usermod 命令,使用 -G 参数,移除用户的组使用 gpasswd,如果你已经看到了这里,你已经可以自学这些命令了,学习的时候,不要怕把系统搞坏,随便折腾。

wangbo@wangbo-VirtualBox:~$ sudo groupadd testgroup
wangbo@wangbo-VirtualBox:~$ sudo usermod -G testgroup zhangsan
wangbo@wangbo-VirtualBox:~$ id zhangsan
uid=1001(zhangsan) gid=1001(zhangsan) groups=1001(zhangsan),1002(testgroup)
wangbo@wangbo-VirtualBox:~$ sudo gpasswd -d zhangsan testgroup
Removing user zhangsan from group testgroup
wangbo@wangbo-VirtualBox:~$ id zhangsan
uid=1001(zhangsan) gid=1001(zhangsan) groups=1001(zhangsan)
wangbo@wangbo-VirtualBox:~$ sudo groupdel testgroup

09-安装和卸载软件包

不同的 linux 发行版对于软件包管理系统的实现不太相同,软件包管理系统非常重要,有了软件包管理,才能给系统添加实用的软件,否则系统的可用性将大为降低。Ubuntu 使用 apt (Advanced Package Tool) 软件包(Ubuntu 现在有一个 snap 软件包管理器,但是 apt 还是经常用),以Debian为基础的发行版都用 apt,CentOS 使用 yum (Yellow dog Updater) 软件包(CentOS 8 默认使用 dnf,yum 是 dnf 的软链接),rpm 软件包是是 RedHat 的实现体系,但是他是开源的,所以很多系统都支持 rpm 软件包。

如果不使用软件包管理,还可以选择对源代码直接进行编译,但是通常软件包工具具有很好的兼容性处理和依耐性处理。还有一些软件包是提前编译好的二进制文件,只需要下载下来解压到相应的目录,设置好正确的文件权限,即可运行。

安装软件包需要提前准备一些编译工具,对于 Ubuntu 系统,可以通过 sudo apt-get install build-essential,对于 CentOS 通过 yum groupinstall "Development tools" 安装。

前面说的 htop 命令,Ubuntu 下直接输入 htop,系统提示没有找到 htop,需要安装。

wangbo@wangbo-VirtualBox:~$ htop

Command 'htop' not found, but can be installed with:

sudo snap install htop  # version 3.0.3, or
sudo apt  install htop  # version 2.2.0-2build1

See 'snap info htop' for additional versions.

运行 sudo apt install htop 安装后 which htop 确认。

wangbo@wangbo-VirtualBox:~$ sudo apt install htop
wangbo@wangbo-VirtualBox:~$ which htop
/usr/bin/htop // 可以运行了

它怎么知道去哪里下载 htop 这个软件包呢? 答案在 /etc/apt/sources.list 里,这个文件定义了获取软件包信息的地址,你可能需要经常运行 sudo apt update 来刷新软件包的信息,对于 CentOS 类似的命令是 sudo yum install htop、sudo yum update,对应的软件包源文件是 /etc/yum.repos.d/ 下的文件,macos 下有 brew 这个软件包可用。git 也是 linux 缔造者 Linus Torvalds 发明的,默认系统并不包含,试试用 apt 或 yum 安装它。

dpkg 也是 Debian linux 系统用来管理软件包的工具,Ubuntu 系统自带,对于某些 .deb 结尾的软件包,经常使用 sudo dpkg -i xx.deb 来安装,有安装就有卸载,看看这些命令大帮助文档吧,你会有答案。dpkg 通常比较倾向于系统工具,而 apt 倾向于用户工具。

rpm 包是预先在 linux 机器上编译好好并打包好的文件,安装起来快捷,但可能出现环境兼容性和依赖性问题,导致安装失败。使用 rpm -ivh xx.rpm 安装 rpm 软件包,rpm -qa 查询已经安装的包。

软件包不是万能的,有些场景我们必须手动编译来安装软件,比如软件包只有源代码没有纳入包管理系统、需要特定的版本、软件包安装失败、目标环境不能上网等等原因,因此掌握通用的编译套路是必须的,一般来讲通用的套路如下:

// 下载解压软件包
tar -zxvf xx.tar.gz
// 切换工作目录
cd xx
// 配置软件包
./configure
// 编译
make
// 安装到系统
sudo make install

下面手动编译安装 nginx 为例说明,首先下载解压。

wangbo@wangbo-VirtualBox:~$ mkdir downloads
wangbo@wangbo-VirtualBox:~$ cd downloads/
wangbo@wangbo-VirtualBox:~/downloads$ wget http://nginx.org/download/nginx-1.18.0.tar.gz
wangbo@wangbo-VirtualBox:~/downloads$ tar -zxvf nginx-1.18.0.tar.gz
wangbo@wangbo-VirtualBox:~/downloads$ cd nginx-1.18.0/
wangbo@wangbo-VirtualBox:~/downloads/nginx-1.18.0$
wangbo@wangbo-VirtualBox:~/downloads/nginx-1.18.0$ ./configure

输出一堆配置信息,你会发现 ./configure 失败了,需要 PCRE library,原来是少了 PCRE 库,先要安装一些它的依赖。

wangbo@wangbo-VirtualBox:~/downloads/nginx-1.18.0$ sudo apt-get install libpcre3 libpcre3-dev openssl libssl-dev zlib1g-dev

再次执行 ./configure,这次配置成功了,输入 make 编译。

wangbo@wangbo-VirtualBox:~/downloads/nginx-1.18.0$ make

编译也成功了,输入 sudo make install 安装到系统,需要 sudo 的原因是因为要对系统的 /usr/local 目录写入文件。

wangbo@wangbo-VirtualBox:~/downloads/nginx-1.18.0$ sudo make install

安装也成功了,看提示是安装到了 /usr/local/nginx 这里,输入 nginx,什么鬼还是招不到,这是因为 /usr/local/nginx 目录并不在环境变量 PATH 中,有2个选择,第一输入绝对路径 /usr/local/nginx/sbin/nginx,第二个选择把目录 /usr/local/nginx/sbin 添加到 PATH 中。

wangbo@wangbo-VirtualBox:~$ export PATH=$PATH:/usr/local/nginx/sbin // 需要每次进入终端生效需要写到 ~/.bashrc 或者 /etc/profile 文件中,可能需要 sudo visudo 修改 secure_path 的值
wangbo@wangbo-VirtualBox:~$ which nginx
/usr/local/nginx/sbin/nginx
wangbo@wangbo-VirtualBox:~$ which nginx
wangbo@wangbo-VirtualBox:~$ echo export "PATH=\$PATH:/usr/local/nginx/sbin" >> ~/.bashrc
wangbo@wangbo-VirtualBox:~$ source ~/.bashrc // 执行一次,否则需要重新进入 shell 才能生效
wangbo@wangbo-VirtualBox:~$ which nginx
/usr/local/nginx/sbin/nginx

恭喜你,你刚刚编译安装了世界上最著名的反向代理服务器 Nginx。

tig 是一个查看 git 提交日志的字符串界面的神器,看看它的官网的安装步骤open in new window,和上面的 nginx 的编译安装有什么不同?

10-连接符、重定向、管道什么鬼

当我们输入一个命令或者执行脚本的时候,shell 环境里有很多隐含变量,比如 $? 它表示命令执行的退出码,如果退出码为 0 通常表示命令执行是成功的,否则表示一个错误码。比如我们输入一个不存在的命令,然后用 echo 打印它的值,shell 告诉我们是127。

wangbo@wangbo-VirtualBox:~$ ls
Desktop  Documents  downloads  Downloads  Music  Pictures  Public  snap  Templates  test  Videos
wangbo@wangbo-VirtualBox:~$ echo $?
0
wangbo@wangbo-VirtualBox:~$ notfound_command
notfound_command: command not found
wangbo@wangbo-VirtualBox:~$ echo $?
127

前面章节编译的 nginx 运行需要在 /usr/local/nginx/logs 写入日志,默认不改变用户权限目录的情况,运行需要 sudo nginx 来调用,直接输入 nginx 没有权限退出,打印 $? 显示为 1。

wangbo@wangbo-VirtualBox:~$ nginx
nginx: [alert] could not open error log file: open() "/usr/local/nginx/logs/error.log" failed (13: Permission denied)
2020/12/11 09:27:58 [emerg] 11280#0: open() "/usr/local/nginx/logs/access.log" failed (13: Permission denied)
wangbo@wangbo-VirtualBox:~$ echo $?
1

很多时候我们需要把命令链接起来执行,第一个原因是为了简化输入,第二个原因是后面的命令是否运行依赖于前面的执行结果,比如前面编译软件的时候,我们经常会看到这样的命令 make && sudo make install,&& 称为连接符表示如果 make 成功了,就接着执行 sudo make install,如果 make 失败了,sudo make install 命令不会运行,与此相反的是 || 连接符,它表示前面的命令如果调用失败了,后面的命令才运行。如果执行的命令没有逻辑关系,但又想写在一起可以使用 ; 连接符,不管前面的命令是否运行成功,后面的命令都会运行。

wangbo@wangbo-VirtualBox:~$ ls && date
Desktop  Documents  downloads  Downloads  Music  Pictures  Public  snap  Templates  test  Videos
20201211日 星期五 09:35:21 CST
wangbo@wangbo-VirtualBox:~$ error_cmd && date
error_cmd: command not found
wangbo@wangbo-VirtualBox:~$ error_cmd ; date
error_cmd: command not found
20201211日 星期五 09:36:26 CST

命令有输入 stdin、输出 stdout、错误 stderr 三种 IO 类型,分别对应 0 1 2 三个数字,可以改变命令的输入、输出方向,比如 echo this is a test > 1.txt,就是将文本 this is a test 通过标准输出重定向到 1.txt 这个文件中,它默认会覆盖文件的内容,如果使用 >> 则表示追加内容到文件末尾,反过来 < 和 << 可以表示标准输入,比如 cat readme.txt 也可以写成 cat < readme.txt,表示将 readme.txt 文件读取的结果作为 cat 命令的输入,cat << END 表示从终端接受输入,遇到 END 时结束运行 cat 命令,cat 会输出刚才终端输入的字符。

wangbo@wangbo-VirtualBox:~/test$ cat << END
> this is a test
> new line
> more lines
> END
this is a test
new line
more lines

同样的道理,wc -l < readme.txt 和 wc -l << END 类似的。你可能会经常看到一些奇怪的东西,比如 2>&1、>/dev/null、>/dev/null 2>&1、&>/dev/null,接下来我们来理解它。

首先数字 1 和 2 表示 stdin 和 stderr,也就是输出和错误,&1表示引用 stdin,就好像别动我抓住了你小子的把柄了,所以 2>&1 的意思把命令的错误输出也重定向到标准输出中,意思就是合并了命令的输出和错误信息。

/dev/null 其实等同于 1>/dev/null,因为 1 是系统的默认值可以不写,把标准输出定向到黑洞。

> /dev/null 2>&1 的完整写法是 1>/dev/null > 2>&1,表示把标准输出定向到黑洞,把错误定向到标准输出,结果就是输出和错误都定向到了黑洞。

&>/dev/null 则表示讲所有的 IO 重定向到黑洞,我不关心命令执行的输出结果,给我执行一次就行了。

在使用 > 重定向的时候,比如 echo learn bash > 1.txt 的完整写法是 echo learn bash 1>1.txt,经常生省略 1 不写,但请注意 1 和 > 之前不能有空格(否则 shell 可能错误的理解命令的意图),> 和 1.txt 之间可以有空格,为了方便,通常在 1 和 >、1.txt 之间我们都不写空格,但也因人而异,你可以试试 echo learn bash 1 >1.txt 的结果,看看 1.txt 文件了是什么内容。到这里,我希望我把这几个奇怪的符号说清楚了,你有了深入理解重定向的基本知识,遇到的时候不会紧张了。对了,这几个符号经常会和 nohup 一起使用,还记得 nohup 吗? 忘了再去看看前面的关于进程的章节内容。

管道 | 是一个特殊的连接符,表示把上一个命令的输出像水管一样,输送给下一个命令,这个连接符体现了整个 Unix 的设计哲学,如果想知道当前目录或者一个文本文件一共有多少行怎么做呢? wc 命令是一个统计文本的命令,可以统计行数和单词数等,但是他本身就只做这个统计,而 ls -l 可以列出当前目录的文件列表,如果把 ls 的输出流向到 wc 命令,那就可以计算出当前目录的文件个数,组合 2 个小命令可以干一件更有意义的事情,这种组合灵活自由,可以实现很多非常有用的功能。

wangbo@wangbo-VirtualBox:~$ ls -l | wc -l // -l 表示计算行数
12
wangbo@wangbo-VirtualBox:~/test$ cat readme.txt | wc -l
4
wangbo@wangbo-VirtualBox:~/test$ cat readme.txt | wc -w // -w 表示计算单词数
10

grep 是一个搜索命令,经常把其他命令的输出结果交给他过滤,比如 ps aux | grep nginx 可以看看进程列表是不是有 nginx 进程,cat readme.txt | grep hello 看看 readme.txt 文本文件是否有 hello 关键词。由于 | 依赖于前面的命令的输出,因此它是有逻辑关系的。这两个例子太过于简单但是很能说明问题,思考一下,如何对 ls -l 的结果进行目录大小排序呢? ls -l 的输出结果中有一列表示文件的大小了,所以需要其实把 ls -l 的结果通过管道交给一个能按照这一列数字排序的命令,那就是 sort 命令。

wangbo@wangbo-VirtualBox:~/test$ man ls
wangbo@wangbo-VirtualBox:~/test$ ls -l | sort -n -k 4
drwxrwxr-x 2 wangbo wangbo 4096 1210 17:27 normal
drwxr-xr-x 2 root   root   4096 1210 17:27 special
-rw-rw-r-- 1 wangbo wangbo   53 1211 09:43 readme.txt
total 12

不过,其实你被骗了,ls 本身就可以排序,输入 ls -Sl 试试。

wangbo@wangbo-VirtualBox:~/test$ ls -Sl
total 12
drwxrwxr-x 2 wangbo wangbo 4096 1210 17:27 normal
drwxr-xr-x 2 root   root   4096 1210 17:27 special
-rw-rw-r-- 1 wangbo wangbo   53 1211 09:43 readme.txt

但是如果是对一个每行都是一个数字的文件排序,那上面的例子就非常的有说服力了。

wangbo@wangbo-VirtualBox:~/test$ cat number.txt
zhangsan 20
li       10
wangwu   30
kitty    90
lily     40
wangbo@wangbo-VirtualBox:~/test$ cat number.txt | sort -n -k 2 // -n 表示按数字大小排序 -k 是指定排序的列
li       10
zhangsan 20
wangwu   30
lily     40
kitty    90

到此我希望你 get 到了组合小命令干大事情的思想。

11-系统服务管理

当你浏览 linux 的目录的时候有没有注意到有这样一个目录 /etc/init.d/,下面有很多脚本文件,比如 /etc/init.d/ufw,它是 ubuntu 自带的一个防火墙软件,我们可以通过 sudo service ufw stop 来停止,用 sudo service ufw start 来启动,cat /etc/init.d/ufw 发现这个脚本处理了 start|status|stop|restart 等参数,换言之它是一个包装的脚本,用来对接 service 命令,这些服务脚本都是 sevice 的实例,通常是在后台运行,比如 ssh network-manager x11-common 。

很多软件包在安装的时候,都会对应产生一个包装服务,提供 start|stop|restart 参数来方便管理,用户可以自己编写一些脚本放在这个目录下面,用 service 来托管。也可以用系统工具 systemctl 来管理服务,比如 sudo systemctl stop ufw,systemd 的设计非常复杂,有一些人认为它违反了 Unix 的 keep simple keep stupid 的哲学,但是实际工作中,service 和 systemctl 都会用到。

除了直接查看目录浏览服务,还可以输入 sudo service --status-all 查看,有一些服务是自动启动的,它们在 /etc/rcX.d/ 中被软连接,其中 X 是一个数字,表示系统的启动级别,这些目录里面的服务是互斥的,每次启动只会运行一个目录里的服务,但是相同目录下的服务是有先后顺序的,你会发现这些文件中都以 K 或者 S + 数字来命名,S 表示随服务自动启动,K 表示杀死进程,数字表示启动的优先级,比如一个服务要用网络,它的优先级应该在网络服务启动以后。

上面说到系统的运行级别,你可以输入 runlevel 来确定当前系统的运行级别。

wangbo@wangbo-VirtualBox:~$ runlevel
N 5 // 运行在图形界面
运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动
运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆
运行级别2:多用户状态(没有NFS)
运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式
运行级别4:系统未使用,保留
运行级别5:X11控制台,登陆后进入图形GUI模式
运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动

可以使用 init 命令来改变运行级别,比如 init 0 关机,init 6 重启,

下面来写一个自己的服务,这个服务很简单就是打印 sevice 命令后面的参数和当前时间,脚本路径 /etc/init.d/hello,目前还没有学习 shell 脚本的知识,不用在意细节。

#!/bin/bash

case "$1" in
    start)
        echo start
        date
        ;;
    stop)
        echo stop
        date
        ;;
    restart)
        echo restart
        date
        ;;
esac

sudo chmod a+x /etc/init.d/hello 加上执行权限,由于它本身就是个 shell 脚本,可以直接运行,输入 /etc/init.d/hello start 会打印 start 和系统当前时间,输入 sudo service hello start,系统提示找不到 hello.service,还需要注册一下,输入 sudo update-rc.d hello defaults 注册一下,使用 sudo service status 获取状态发现输出了 start 和 时间,由于我们的服务不是常驻的服务,运行完后就退出了,所以系统显示 active (exited)。

wangbo@wangbo-VirtualBox:~$ sudo chmod a+x /etc/init.d/hello
wangbo@wangbo-VirtualBox:~$ sudo service hello start
Failed to start hello.service: Unit hello.service not found.
wangbo@wangbo-VirtualBox:~$ sudo service hello start
wangbo@wangbo-VirtualBox:~$ sudo service hello status
● hello.service
     Loaded: loaded (/etc/init.d/hello; generated)
     Active: active (exited) since Fri 2020-12-11 11:48:15 CST; 16s ago
       Docs: man:systemd-sysv-generator(8)
    Process: 12245 ExecStart=/etc/init.d/hello start (code=exited, status=0/SUCCESS)

1211 11:48:15 wangbo-VirtualBox systemd[1]: Starting hello.service...
1211 11:48:15 wangbo-VirtualBox hello[12245]: start
1211 11:48:15 wangbo-VirtualBox hello[12254]: 20201211日 星期五 11:48:15 CST
1211 11:48:15 wangbo-VirtualBox systemd[1]: Started hello.service.
wangbo@wangbo-VirtualBox:~$ sudo service hello restart
wangbo@wangbo-VirtualBox:~$ sudo service hello status
● hello.service
     Loaded: loaded (/etc/init.d/hello; generated)
     Active: active (exited) since Fri 2020-12-11 11:49:07 CST; 1s ago
       Docs: man:systemd-sysv-generator(8)
    Process: 12280 ExecStart=/etc/init.d/hello start (code=exited, status=0/SUCCESS)

1211 11:49:07 wangbo-VirtualBox systemd[1]: Starting hello.service...
1211 11:49:07 wangbo-VirtualBox hello[12280]: start
1211 11:49:07 wangbo-VirtualBox hello[12281]: 20201211日 星期五 11:49:07 CST
1211 11:49:07 wangbo-VirtualBox systemd[1]: Started hello.service.

再次运行 sudo service hello stop,获取状态显示服务处于 inactive (dead) 表示服务当前没有运行。

好了,这些是系统服务的基本知识,更深入的知识还需要不断的去实践,比如如何让服务开机启动,因为 linux 发行版的区别,可能实现上稍有差异或者有一些坑,都是经验问题,掌握它的设计机制,通过命令去验证自己的想法是最重要的。

12-了解shell编程

终于说到了 shell 编程,为了提高工作效率,经常我们会运行 shell 脚本来自动化的完成一些工作,shell 编程很强大但其实不需要深入学习(除非你的工作就是天天写 shell 脚本),很多年以前就有人用 shell 编写了俄罗斯方块游戏open in new window,而且还在不断更新open in new window,先看最简单的 hello world 脚本:

#!/bin/bash
#特殊注释,表示使用 bash 这种 shell 来运行这个脚本

hi="hello world"
echo $hi #打印字符串
now=`date` #记录当前时间
echo $now #打印变量

if [ $? -eq 0 ]; then
  echo 执行成功了
fi

所谓 shell 脚本不过是一堆命令的组合而已,但是编写过程中经常需要有逻辑性判断,就需要了解隐含变量、判断、循环、分支等知识,为了让脚本维护性更好,还需要了解函数知识,shell 脚本可以很简单,也可以很复杂,先学习一个大概印象即可。

上面的脚本使用了变量和 if 条件判断,变量可以显示的赋值,就像 hi,也可以来自其他命令的输入,就像now,如果不喜欢date的写法,可以写成now=$(date),注意等号两边没有空格。if 的条件判断引用了隐含变量 $? 使用 -eq 运算符判断上个命令退出码是否为0,if 可以支持的判断非常多,比如判断文件是否、数值是否相等、文件的属性、字符创的长度、变量是否相等、逻辑表达式是否成立等等。在编写脚本的时候,经常需要根据目标环境使用不同的命令或者参数,下面的脚本就是判断目标环境的 linux 系统类型。

#!/bin/bash


os=`uname -s`

if [ $os = "Linux" ] ; then
    echo "Linux"
elif [ $os = "FreeBSD" ] ; then
    echo "FreeBSD"
elif [ $os = "Solaris" ] ; then
    echo "Solaris"
else
    echo "What?"
fi

由于这个函数太过于常用,我们决定把它写到单独的文件中,以便可以重复使用,把公用的文件叫 lib.sh,并且把它封装成一个函数。

#!/bin/bash

getos() {
	os=`uname -s`

	if [ $os = "Linux" ] ; then
		echo "Linux"
	elif [ $os = "FreeBSD" ] ; then
		echo "FreeBSD"
	elif [ $os = "Solaris" ] ; then
		echo "Solaris"
	else
		echo "What?"
	fi
}

写一个测试文件 callos.sh 来调用它。

#!/bin/bash

source ./lib.sh // 执行另外一个脚本文件
getos

你可能听过说函数有参数,但是 shell 脚本中函数不能定义参数,那么如何传递参数呢? 就需要了解一些隐含变量了。比如 $1 是第一个参数,$2 是第二个参数,${10} 是第十个参数,$# 是参数的个数,$* 是传递的所有参数,$$ 是脚本运行的当前进程 PID 等等。函数返回值是函数体内部最后一条指令的 $?,但是也可以使用 return 明确返回一个数字值,是的,只能是数字,如果要返回其他类型的可以在函数中把结果赋值给全局变量,其他地方访问全局变量获取结果。

在 lib.sh 文件中增加一个打印函数参数的函数。

printargs() {
        for arg in $*; do
                echo $arg
        done
}

用了 for 循环来遍历 $*,写一个脚本来调用它。

#!/bin/bash

source ./lib.sh
printargs 1 one 2 two

你应该对 shell 脚本有了基本的认识了,记住测试的时候别忘了给你的脚本加上 x 可以执行权限。shell 脚本可以通天,但是对于网络上下载的 shell 脚本,你要小心,可能会恶意损害你的系统,不要随意以管理员身份来运行你不清楚的 shell 脚本。

13-网络操作

此章节我们学习一下网络操作,其实就是一些常用的命令和技巧而已,但是我相信对小白还有用的。当我们进入 shell 终端准备输入命令的时候,你可能会看到 $ 或 # 提示符前有一个名字,它默认是你的主机名称,你可以 ping 一下试试或者通过输入 hostname 命令来验证一下。

wangbo@wangbo-VirtualBox:~$ ping wangbo-VirtualBox
PING wangbo-VirtualBox (127.0.1.1) 56(84) bytes of data.
64 bytes from wangbo-VirtualBox (127.0.1.1): icmp_seq=1 ttl=64 time=0.013 ms
^C
--- wangbo-VirtualBox ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.013/0.013/0.013/0.000 ms
wangbo@wangbo-VirtualBox:~$ hostname
wangbo-VirtualBox
wangbo@wangbo-VirtualBox:~$ ping $(hostname)
PING wangbo-VirtualBox (127.0.1.1) 56(84) bytes of data.
64 bytes from wangbo-VirtualBox (127.0.1.1): icmp_seq=1 ttl=64 time=0.239 ms

主机名称默认是和 /etc/hosts 中127.0.0.1 对应着,在 /etc/hostname 里也有,如果你不喜欢这个名称,可以使用 sudo hostnamectl set-hostname 新名称 来修改,也可以直接使用 sudo hostname 新名称 修改,前者是永久的修改,后者是临时修改,重启后会恢复,主机名是一个机器的身份,别人可以根据这个名称定为到机器的位置,就像机器的 ip 地址一样。

wangbo@wangbo-VirtualBox:~$ sudo hostnamectl set-hostname ubuntu
wangbo@wangbo-VirtualBox:~$ hostname
ubuntu
wangbo@wangbo-VirtualBox:~$ ping ubuntu
PING ubuntu (10.0.2.15) 56(84) bytes of data.
64 bytes from ubuntu (10.0.2.15): icmp_seq=1 ttl=64 time=0.026 ms

对了,如果你要查询本机的 ip 地址,你可以输入 ifconfig 或者 ip addr,但是机器可能会有多个网卡注意鉴别,比如我的虚拟机显示是 inet 10.0.2.15/24 这样的地址,可以 ping 它测试一下,这两个命令还能直接修改网络设备的属性。

wangbo@wangbo-VirtualBox:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:67:22:7e brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s3
       valid_lft 83428sec preferred_lft 83428sec
    inet6 fe80::2a51:f883:dc65:4269/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
wangbo@wangbo-VirtualBox:~$ ping 10.0.2.15
PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.
64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.023 ms

telnet 是一个老牌的命令了,原来用于在本地完成远程主机工作,但是因为明文传输安全性不好,很少用,但我还是经常用它来测试一下某个地址+某个端口是不是可以连接,比如测试一下本机的 22 端口是否可以连接。

wangbo@wangbo-VirtualBox:~$ telnet 127.0.0.1 22
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'. // 说明本机的 22 号端口是开启的

没有开启会拒绝连接,比如测试一下 223 端口。

wangbo@wangbo-VirtualBox:~$ telnet 127.0.0.1 222
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused

wget 和 curl 经常用来下载文件,wget 还支持断点续传和后台下载,curl 的功能更丰富一下,除了下载文件外还经常用来看 http 请求头和响应头数据,系统可能默认并没有安装 curl,你需要安装后使用它。

wangbo@wangbo-VirtualBox:~/downloads$ wget http://nginx.org/download/nginx-1.18.0.tar.gz // 下载文件到当前目录
wangbo@wangbo-VirtualBox:~$ curl -O http://nginx.org/download/nginx-1.18.0.tar.gz // 也是下载文件,-O 参数表示存储
wangbo@wangbo-VirtualBox:~$ curl -i http://www.bilibili.com // -i 参数显示 http 头信息
HTTP/1.1 301 Moved Permanently
Server: Tengine
Date: Fri, 11 Dec 2020 15:47:08 GMT
Content-Type: text/html
Content-Length: 239
Connection: keep-alive
Location: https://www.bilibili.com/

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr/>Powered by Tengine<hr><center>tengine</center>
</body>
</html>

调试 http 接口时看数据更方便的是 httpieopen in new window,它的功能非常丰富,使用 sudo apt install httpie 安装它,详细功能请看官方文档,好多人用它来看 http api 接口的 json 数据。

wangbo@wangbo-VirtualBox:~$ http -v http://www.bilibili.com
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: www.bilibili.com
User-Agent: HTTPie/1.0.3



HTTP/1.1 301 Moved Permanently
Connection: keep-alive
Content-Length: 239
Content-Type: text/html
Date: Fri, 11 Dec 2020 15:50:48 GMT
Location: https://www.bilibili.com/
Server: Tengine

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr/>Powered by Tengine<hr><center>tengine</center>
</body>
</html>

lsof 命令原意是 list openfiles,还记得 linux 一切皆文件吗? lsof 列出所有打开的文件,功能非常强大,不过我一般只用来看看端口的连接情况,因为网络连接也是文件嘛,有点大材小用了。

wangbo@wangbo-VirtualBox:~$ sudo lsof -i:22 // 看看 22 号端口在搞什么飞机
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd     668   root    3u  IPv4  23094      0t0  TCP *:ssh (LISTEN)
sshd     668   root    4u  IPv6  23105      0t0  TCP *:ssh (LISTEN)
sshd    1227   root    4u  IPv4  28868      0t0  TCP wangbo-VirtualBox:ssh->_gateway:57460 (ESTABLISHED)
sshd    1425 wangbo    4u  IPv4  28868      0t0  TCP wangbo-VirtualBox:ssh->_gateway:57460 (ESTABLISHED)

如果要侦查一个进程打开了哪些文件,可以使用 -p 或者 -c 参数。

wangbo@wangbo-VirtualBox:~$ sudo lsof -p 4742 // -p 使用进程号
wangbo@wangbo-VirtualBox:~$ sudo lsof -c nginx // -c 使用名称

netstat 和 ss 都是用来获取网络 socket 的统计信息,ss 更加高效,几乎所有的 linux 都支持 netstat,但是 ss 可能没有安装,它是 iproute2 包附加的一个工具。

ss -a 查看机器的socket连接数
ss -l 查看机器的端口情况
ss -s 查看机器的网络连接数

下面的命令都可以用来统计连接数。

wangbo@wangbo-VirtualBox:~$ netstat -at | wc -l
9
wangbo@wangbo-VirtualBox:~$ ss -atr | wc -l
8
wangbo@wangbo-VirtualBox:~$ ss -atr | wc -l
8

经常需要查看某个端口的连接数。

wangbo@wangbo-VirtualBox:~$ netstat -nat | grep -i "80" | wc -l
2
wangbo@wangbo-VirtualBox:~$ netstat -nat | grep "ESTABLISHED" | grep -i "80" | wc -l
1

dig 命令用来探测网站的服务器地址,在 DNS 负载中一个网站入口会有多个 A 记录服务器。

wangbo@wangbo-VirtualBox:~$ dig www.bilibili.com

; <<>> DiG 9.16.1-Ubuntu <<>> www.bilibili.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34246
;; flags: qr rd ra; QUERY: 1, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;www.bilibili.com.		IN	A

;; ANSWER SECTION:
www.bilibili.com.	25	IN	CNAME	interface.biliapi.com.
interface.biliapi.com.	24	IN	A	118.112.227.133
interface.biliapi.com.	24	IN	A	118.112.227.132
interface.biliapi.com.	24	IN	A	118.112.227.130
interface.biliapi.com.	24	IN	A	118.112.227.129
interface.biliapi.com.	24	IN	A	118.112.227.141
interface.biliapi.com.	24	IN	A	118.112.227.142

在服务器之间传输文件,比如把本地电脑的文件上传到服务器,或者把服务器上的文件下载到本地,下面的命令从服务器上下载 readme.txt 文件到本地。

scp -P 2222 wangbo@localhost:~/test/readme.txt ./ # 下载:目标文件在前,本地目录在后 (localhost是因为 ssh 端口转发)

scp 的全称是 secure copy,数据传输使用 ssh 协议,也就是 secure shell,-P 参数可以指定 ssh 的端口,如果是 22 号默认端口可以不写,-r 参数可以实现批量传输,比如下面的命令把本地的 *.conf 文件都传到服务器的主目录。

scp -r *.conf wangbo@localhost:~/ # 上传:目标文件在后,本地文件在前 (localhost是因为 ssh 端口转发)

当无法与服务器传输的时候,可以使用 ssh -v 命令测试下是否能登录服务器。

ssh -p 2222 -v wangbo@localhost # -v 打印日志可以看出具体无法登录服务器的原因 (localhost是因为 ssh 端口转发)

ssh 后面直接跟命令可以显示在服务器上执行命令的结果,而不用登录到服务器上。

ssh -p 2222 -v wangbo@localhost ls -l
ssh -p 2222 -v wangbo@localhost date

当你在登录虚拟机或者执行这些命令大时候,总是会要求输入密码,不但繁琐而且不安全,有没有办法让服务器自动识别自己的身份呢? OpenSSH 提供了一个命令行工具叫 ssh-keygen,看名称是 ssh key 的生成器,key 就是秘钥,是一个通过加密算法得到的很大的数字(简单的理解成依靠现代计算机的能力破解需要很长时间)。ssh-keygen 生成的是2个密钥,1个是公钥,一个是私钥,也就是使用的是非对称加密(对称加密只使用1个秘钥)。私钥是你的身份证件,如果泄露了别人就可以伪造成你去做坏事,公钥是可以发给任何人的。

场景1:如果数据使用公钥变换,那么只有使用对应的私钥才能解开,比如有人想给你发送一封商业密函,世界上只有你能看,要防止别人窥探,那他可以用你的公钥把函件数据变换(称为加密),通过网络发送给你,由于网上其他人没有你的私钥,即使得到了密函的数据也无法解密查看,起到了保护密函内容的作用。

场景2:如果数据使用私钥变换,那么只有使用对应的公钥才能解开,比如你写了一篇很了不起的论文,发到网上,怎么证明这确实是你写的呢? 你可以用自己的私钥把论文数据变换(称为签名),把对应的公钥发出来,别人用公钥来解密发现真的可以解出来,说明你确实是论文的所有者,而且论文内容没有被修改过。

注意,这里为了规避学术的不严谨性,我用了变换和解开2个词(通常说法是私钥解密和签名、公钥加密和验签,而私钥加密、公钥解密被认为是一种不正确的说法),我们先大概理解一下流程和概念,能使用即可。在本地电脑上运行 ssh-keygen 一路回车,在 .ssh 下会生成你的密钥数据,使用 ssh gui 工具也可以生成密钥对。

ls -l ~/.ssh/
total 12
-rw------- 1 wangbo wangbo 2610 1214 09:45 id_rsa
-rw-r--r-- 1 wangbo wangbo  578 1214 09:45 id_rsa.pub
-rw-r--r-- 1 wangbo wangbo  222 1210 16:56 known_hosts

其中 id_rsa 是你的私钥,id_rsa.pub 是你的公钥,实际使用的时候很多人会指定加密算法,使用 -t 参数,ssh-keygen -t dsa,一般选择 dsa 和 rsa 算法,生成的过程中可以选择对私钥文件设置密码保护,防止别人读取文件,可以给 id_rsa、id_rsa.pub 都加上 600 权限。有了密钥对,就可以把公钥放在虚拟机或服务器上,就可以实现免密码登录了。过程大概如下:

第一:服务器厚道 ssh 登录的请求,发送一些测试数据给客户端;

第二:客户端使用私钥对测试数据进行签名,发给服务器;

第三:服务器收到后使用公钥解密和原始测试数据比较,相同则允许用户登录。

OpenSSH 规定服务器的公钥文件保存在 ~/.ssh/authorized_keys 文件中,用哪个用户登录,就放在哪个用户的 ~ 主目录下,这个文件每一行都可以放一个公钥,通常该文件并不存在,需要手动创建,前面说了 ssh 可以直接跟上命令,所以可以这样做。

cat ~/.ssh/id_rsa.pub | ssh wangbo@ip地址 "mkdir ~p ~/.ssh && cat >> ~/.ssh/authorized_keys"

这里有个坑,~/.ssh/authorized_keys 的文件权限必须设置成 644,表示只有文件所有者才能写入,否则 ssh 服务可能拒绝读取这个文件,导致配置失效。

chmod 644 ~/.ssh/authrozied_keys

也可以手动拷贝公钥到服务器上,OpenSSH 也提供了一个命令 ssh-copy-id 来实现公钥上传,如果你在生成密钥的时候,选择了使用密码保护密钥,那么你 ssh 登录的时候要输入这个保护秘钥的密码(不是你的 linux 用户密码),要想规避这个你需要去了解 ssh-agent 和 ssh-add 命令。

自此,你已经可以无密码 ssh 登录服务器了。

rsync 命令是一个文件同步的工具,不仅可以在本地目录同步,也可以在服务器之间同步,也就是说它会比较文件的属性,是个增量同步,不用每次都全部传输文件。rsync 支持 ssh 协议、rsync 协议,后者要求服务器启用了 rsync 的服务器进程,前者和 scp 原理相同,同步大量文件的时候可以使用 -z 参数压缩,-r 对子目录递归处理,-a 是归档模式不仅递归处理,还可以保持文件的属性不改变。默认情况下 rsync 只是保证把源文件的文件都同步到目标目录,如果目标目录有多余的文件,它并不会清理它,如果希望目标目录是一个相同的镜像,需要加上 --delete 参数,它会删除在目标目录但是不在源目录中的文件,--exclude 参数可以使用文件通配符排除文件。

到目前为止,你已经掌握和服务器打交道的常用方法。

14-常用举例

14.1 数据库备份

通常数据库具有主从和实时结构,但定期也会全量备份,下面的脚本备份1次数据库,并且同步到异地服务器,清理7天以上的备份文件。

#!/bin/sh

backup_dir=/db_backup/
cd $backup_dir
filename=all_`date +%Y%m%d`_dump
mongodump -o $filename
tar zcvf $filename.tar.gz $filename
rm -rf $filename

# 删除7天以前的备份
find $backup_dir -mtime +7 -name "*.tar.gz" -exec rm -rf {} \;

# 同步文件到异地的服务器
rsync -azv --include "*.tar.gz" --progress --delete dbuser@ip地址:/db_sync/

把该文件配置为 crontab 任务,让他定时运行,输入 crontab -e 编辑万后保存。

# 每天3点钟备份数据库
0 3 * * * /db_backup/backup.sh 1>~/dump.log &

14.2 对 nginx 日志的简单统计

用于定位异常 ip 和分析页面 (通常有更专业的运维日志工具),其中 access.log 文件是 nginx 日志标准格式。

# 访问最频繁的 10 个 ip 地址
awk '{print $1}' access.log | sort -n |uniq -c | sort -rn | head -n 10

# 定位了异常 ip 就再看看它到底访问了什么页面,进一步确定是否是异常访问
# 打印访问页面按照访问频率排序
grep '异常的ip地址' access.log |awk '{print $7}'| sort | uniq -c | sort -rn | head -n 100

# 哪些页面访问量最高
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -n 100

# 最近 10000 条记录中访问高的页面
tail -10000 access.log | awk '{print $7}' | sort | uniq -c | sort -nr | less

# 哪个时间点用户访问最活跃
awk '{print $4}' access.log | cut -c 14-15 | sort | uniq -c | sort -nr | head -n 100

14.3 Nvm (node version manager)

Nvm 是一个 node 版本管理器,类似于 pyenv、rvm、jenv 等等,观察一下他的安装脚本是怎么工作的,地址是 https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh,请用浏览器打开,如果你能完全搞懂这个脚本,你的 shell 编程已经过关了,实际工作中 shell 广泛用于安装、维护、自动化、备份、监测、构建等细节工作,通常这些工作 python 和 ruby 也能很好的完成,但似乎还是 shell 更受欢迎,shell 是随 unix 天生的,python 和 ruby 还需要额外安装,目标环境上可能没有。

最后,你可能会看到别人的 shell 长的很漂亮。

大多数是用了这个项目,地址是 https://github.com/ohmyzsh/ohmyzsh,它使用 zsh,安装方法参考 https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH,oh-my-zsh 带有很多主题 theme,你也可以自己定制主题,看看这里 https://github.com/ohmyzsh/ohmyzsh/wiki/Themes,还支持各种插件,比如 git 插件可以显示当前 git 的分支和文件状态。

Last Updated:
Contributors: Bob Wang