丙午🐎年

acc8226 的博客

wget

wget 支持 HTTP,HTTPS 和 FTP 协议,可以使用 HTTP 代理。所谓的自动下载是指,wget 可以在用户退出系统的之后在后台执行。这意味这你可以登录系统,启动一个 wget 下载任务,然后退出系统,wget 将在后台执行直到任务完成

wget 可以跟踪 HTML 页面上的链接依次下载来创建远程服务器的本地版本,完全重建原始站点的目录结构。这又常被称作”递归下载”。

wget 非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性.如果是由于网络的原因下载失败,wget 会不断的尝试,直到整个文件下载完毕。如果是服务器打断下载过程,它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。

curl

cURL 技术支持库是:libcurl。这就意味着你可以基于 cURL 编写整个程序,允许你基于 libcurl 库中编写图形环境的下载程序,访问它所有的功能。

cURL 宽泛的网络协议支持可能是其最大的卖点。cURL 支持访问 HTTP 和 HTTPS 协议,能够处理 FTP 传输。它支持 LDAP 协议,甚至支持 Samba 分享。实际上,你还可以用 cURL 收发邮件。

cURL 也有一些简洁的安全特性。cURL 支持安装许多 SSL/TLS 库,也支持通过网络代理访问,包括 SOCKS。这意味着,你可以越过 Tor 来使用cURL。

cURL 同样支持让数据发送变得更容易的 gzip 压缩技术。

curl --help 查看帮助

curl 的简单方法
curl -X METHOD -H HEADER -i

HTTP 动词
curl 默认的 HTTP 动词是 GET,使用 -X 参数可以支持其他动词。
$ curl -X POST www.qq.com
$ curl -X DELETE www.qq.com

显示响应 header 信息
$ curl -i www.qq.com
-i 参数可以显示 http response 的头信息,连同网页代码一起。

增加头信息
$ curl --header "Content-Type:application/json" http://example.com

支持重定向 Follow redirects
-L 参数,curl 就会跳转到新的网址。
$ curl -L www.qq.com

若不加 -L 则不会自动重定向

1
2
3
4
5
6
7
8
curl www.qq.com
<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<center><h1>302 Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

输出到文件, 可以使用 -o 参数:
$ curl -o [文件名] www.qq.com

curl 常用命令总结

1
2
3
4
5
6
curl命令  访问网站 url
-I/--head 显示响应头信息
-m/--max-time 访问超时的时间
-o/--output 记录访问信息到文件
-s/--silent 沉默模式访问,就是不输出信息
-w/--write-out 以固定特殊的格式输出,例如:%{http_code},输出状态码

利用 curl 命令返回值确定网站是否正常
curl -I -m 5 -s -w "%{http_code}\n" -o /dev/null www.baidu.com
若返回 200 则表示成功.

进行 get 请求
curl www.ithome.com

进行 post 请求
设置 http 代理
curl --proxy 10.5.3.9:80 https://www.so.com

curl 案例

Shell 脚本 curl 发起 http 请求,保存到文件

1
2
3
zhang@ThinkPad:~$ RESULT=$(curl -s "http://baidu.com")
zhang@ThinkPad:~$ echo $RESULT
<html> <meta http-equiv="refresh" content="0;url=http://www.baidu.com/"> </html>

总结

cURL 与 wget:到底哪一个才更适合你

wget 简单直接。这意味着你能享受它超凡的下载速度。wget 是一个独立的程序,无需额外的资源库,更不会做其范畴之外的事情。如果你想做一些更复杂的使用,直觉告诉你,你应该选择 cRUL。

cURL 是一个多功能工具。当然,它可以下载网络内容,但同时它也能做更多别的事情。你可以把 cURL 想象成一个精简的命令行网页浏览器。它支持几乎你能想到的所有协议,可以交互访问几乎所有在线内容。唯一和浏览器不同的是,cURL 不会渲染接收到的相应信息。

遇到问题

curl: (6) Could not resolve host: www.baidu.com

都说是 dns 的问题。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# vim /etc/resolv.conf

# 原先的 DNS
# Generated by NetworkManager
nameserver 8.8.8.8

# 修改后的 DNS
# Generated by NetworkManager
nameserver 114.114.114.114
nameserver 8.8.8.8

参考

http://www.ruanyifeng.com/blog/2019/09/curl-reference.html

Linux 中也难免遇到某个程序无响应的情况,可以通过一些命令来帮助我们让系统能够更流畅的运行。 而在此之前,我们需要对进程的基础知识有一定的了解,才能更好、更有效率的使用 Linux 提供的工具。

进程与程序

首先程序与进程是什么?程序与进程又有什么区别?

程序(procedure):不太精确地说,程序就是执行一系列有逻辑、有顺序结构的指令,帮我们达成某个结果。就如我们去餐馆,给服务员说我要牛肉盖浇饭,她执行了做牛肉盖浇饭这么一个程序,最后我们得到了这么一盘牛肉盖浇饭。它需要去执行,不然它就像一本武功秘籍,放在那里等人翻看。
进程(process):进程是程序在一个数据集合上的一次执行过程,在早期的 UNIX、Linux 2.4 及更早的版本中,它是系统进行资源分配和调度的独立基本单位。同上一个例子,就如我们去了餐馆,给服务员说我要牛肉盖浇饭,她执行了做牛肉盖浇饭这么一个程序,而里面做饭的是一个进程,做牛肉汤汁的是一个进程,把牛肉汤汁与饭混合在一起的是一个进程,把饭端上桌的是一个进程。它就像是我们在看武功秘籍这么一个过程,然后一个篇章一个篇章地去练。

简单来说,程序是为了完成某种任务而设计的软件,比如 vim 是程序。什么是进程呢?进程就是运行中的程序。

程序只是一些列指令的集合,是一个静止的实体,而进程不同,进程有以下的特性:

  • 动态性:进程的实质是一次程序执行的过程,有创建、撤销等状态的变化。而程序是一个静态的实体。
  • 并发性:进程可以做到在一个时间段内,有多个程序在运行中。程序只是静态的实体,所以不存在并发性。
  • 独立性:进程可以独立分配资源,独立接受调度,独立地运行。
  • 异步性:进程以不可预知的速度向前推进。
  • 结构性:进程拥有代码段、数据段、PCB(进程控制块,进程存在的唯一标志)。也正是因为有结构性,进程才可以做到独立地运行。

**并发:**在一个时间段内,宏观来看有多个程序都在活动,有条不紊的执行(每一瞬间只有一个在执行,只是在一段时间有多个程序都执行过)
**并行:**在每一个瞬间,都有多个程序都在同时执行,这个必须有多个 CPU 才行

引入进程是因为传统意义上的程序已经不足以描述 OS 中各种活动之间的动态性、并发性、独立性还有相互制约性。程序就像一个公司,只是一些证书,文件的堆积(静态实体)。而当公司运作起来就有各个部门的区分,财务部,技术部,销售部等等,就像各个进程,各个部门之间可以独立运做,也可以有交互(独立性、并发性)。

而随着程序的发展越做越大,又会继续细分,从而引入了线程的概念,当代多数操作系统、Linux 2.6 及更新的版本中,进程本身不是基本运行单位,而是线程的容器。就像上述所说的,每个部门又会细分为各个工作小组(线程),而工作小组需要的资源需要向上级(进程)申请。

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。因为线程中几乎不包含系统资源,所以执行更快、更有效率。

简而言之,一个程序至少有一个进程,一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。就如下图所示:

进程的分类

大概明白进程是个什么样的存在后,我们需要进一步了解的就是进程分类。可以从两个角度来分:

以进程的功能与服务的对象来分;
以应用程序的服务类型来分;

第一个角度来看,我们可以分为用户进程与系统进程:

用户进程:通过执行用户程序、应用程序或称之为内核之外的系统程序而产生的进程,此类进程可以在用户的控制下运行或关闭。
系统进程:通过执行系统内核程序而产生的进程,比如可以执行内存资源分配和进程切换等相对底层的工作;而且该进程的运行不受用户的干预,即使是 root 用户也不能干预系统进程的运行。

第二角度来看,我们可以将进程分为交互进程、批处理进程、守护进程

交互进程:由一个 shell 终端启动的进程,在执行过程中,需要与用户进行交互操作,可以运行于前台,也可以运行在后台。
批处理进程:该进程是一个进程集合,负责按顺序启动其他的进程。
守护进程:守护进程是一直运行的一种进程,在 Linux 系统启动时启动,在系统关闭时终止。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。例如 httpd 进程,一直处于运行状态,等待用户的访问。还有经常用的 cron(在 centOS 系列为 crond)进程,这个进程为 crontab 的守护进程,可以周期性的执行用户设定的某些任务。

进程的衍生

进程有这么多的种类,那么进程之间定是有相关性的,而这些有关联性的进程又是如何产生的,如何衍生的?

就比如我们启动了终端,就是启动了一个 bash 进程,我们可以在 bash 中再输入 bash 则会再启动一个 bash 的进程,此时第二个 bash 进程就是由第一个 bash 进程创建出来的,他们之间又是个什么关系?

我们一般称呼第一个 bash 进程是第二 bash 进程的父进程,第二 bash 进程是第一个 bash 进程的子进程,这层关系是如何得来的呢?

关于父进程与子进程便会提及这两个系统调用 fork() 与 exec()

fork-exec是由 Dennis M. Ritchie 创造的
fork() 是一个系统调用(system call),它的主要作用就是为当前的进程创建一个新的进程,这个新的进程就是它的子进程,这个子进程除了父进程的返回值和 PID 以外其他的都一模一样,如进程的执行代码段,内存信息,文件描述,寄存器状态等等
exec() 也是系统调用,作用是切换子进程中的执行程序也就是替换其从父进程复制过来的代码段与数据段

子进程就是父进程通过系统调用 fork() 而产生的复制品,fork() 就是把父进程的 PCB 等进程的数据结构信息直接复制过来,只是修改了 PID,所以一模一样,只有在执行 exec() 之后才会不同,而早先的 fork() 比较消耗资源后来进化成 vfork(),效率高了不少,感兴趣的同学可以查查为什么。

这就是子进程产生的由来。简单的实现逻辑就如下方所示【注释1】

1
2
3
4
5
6
7
8
9
pid_t p;

p = fork();
if (p == (pid_t) -1)
/* ERROR */
else if (p == 0)
/* CHILD */
else
/* PARENT */

既然子进程是通过父进程而衍生出来的,那么子进程的退出与资源的回收定然与父进程有很大的相关性。当一个子进程要正常的终止运行时,或者该进程结束时它的主函数 main() 会执行 exit(n); 或者 return n,这里的返回值 n 是一个信号,系统会把这个 SIGCHLD 信号传给其父进程,当然若是异常终止也往往是因为这个信号。

在将要结束时的子进程代码执行部分已经结束执行了,系统的资源也基本归还给系统了,但若是其进程的进程控制块(PCB)仍驻留在内存中,而它的 PCB 还在,代表这个进程还存在(因为 PCB 就是进程存在的唯一标志,里面有 PID 等消息),并没有消亡,这样的进程称之为僵尸进程(Zombie)。

如图中第四列标题是 S,S 表示的是进程的状态,而在下属的第三行的 Z 表示的是 Zombie 的意思。( ps 命令将在后续详解)

正常情况下,父进程会收到两个返回值:exit code(SIGCHLD 信号)与 reason for termination 。之后,父进程会使用 wait(&status) 系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的 PCB;而如若父进程没有这么做的话,子进程的 PCB 就会一直驻留在内存中,一直留在系统中成为僵尸进程(Zombie)。

虽然僵尸进程是已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,在进程列表中保留一个位置,记载该进程的退出状态等信息供其父进程收集,从而释放它。但是 Linux 系统中能使用的 PID 是有限的,如果系统中存在有大量的僵尸进程,系统将会因为没有可用的 PID 从而导致不能产生新的进程。

另外如果父进程结束(非正常的结束),未能及时收回子进程,子进程仍在运行,这样的子进程称之为孤儿进程。在 Linux 系统中,孤儿进程一般会被 init 进程所“收养”,成为 init 的子进程。由 init 来做善后处理,所以它并不至于像僵尸进程那样无人问津,不管不顾,大量存在会有危害。

进程 0 是系统引导时创建的一个特殊进程,也称之为内核初始化,其最后一个动作就是调用 fork() 创建出一个子进程运行 /sbin/init 可执行文件,而该进程就是 PID=1 的进程 1,而进程 0 就转为交换进程(也被称为空闲进程),进程 1 (init 进程)是第一个用户态的进程,再由它不断调用 fork() 来创建系统里其他的进程,所以它是所有进程的父进程或者祖先进程。同时它是一个守护程序,直到计算机关机才会停止。

通过以下的命令我们可以很明显的看到这样的结构

1
pstree

或者从此图我们可以更加形象的看清子父进程的关系

通过以上的显示结果我们可以看的很清楚,init 为所有进程的父进程或者说是祖先进程

我们还可以使用这样一个命令来看,其中 pid 就是该进程的一个唯一编号,ppid 就是该进程的父进程的 pid,command 表示的是该进程通过执行什么样的命令或者脚本而产生的

1
ps -fxo user,ppid,pid,pgid,command

可以在图中看见我们执行的 ps 就是由 zsh 通过 fork-exec 创建的子进程而执行的

使用这样的一个命令我们也能清楚的看见 init 如上文所说是由进程 0 这个初始化进程来创建出来的子进程,而其他的进程基本是由 init 创建的子进程,或者是由它的子进程创建出来的子进程。所以 init 是用户进程的第一个进程也是所有用户进程的父进程或者祖先进程。(ps 命令将在后续课程详解)

就像一个树状图,而 init 进程就是这棵树的根,其他进程由根不断的发散,开枝散叶

进程组与 Sessions

每一个进程都会是一个进程组的成员,而且这个进程组是唯一存在的,他们是依靠 PGID(process group ID)来区别的,而每当一个进程被创建的时候,它便会成为其父进程所在组中的一员。

一般情况,进程组的 PGID 等同于进程组的第一个成员的 PID,并且这样的进程称为该进程组的领导者,也就是领导进程,进程一般通过使用 getpgrp() 系统调用来寻找其所在组的 PGID,领导进程可以先终结,此时进程组依然存在,并持有相同的 PGID,直到进程组中最后一个进程终结。

与进程组类似,每当一个进程被创建的时候,它便会成为其父进程所在 Session 中的一员,每一个进程组都会在一个 Session 中,并且这个 Session 是唯一存在的,

Session 主要是针对一个 tty 建立,Session 中的每个进程都称为一个工作(job)。每个会话可以连接一个终端(control terminal)。当控制终端有输入输出时,都传递给该会话的前台进程组。Session 意义在于将多个 jobs 囊括在一个终端,并取其中的一个 job 作为前台,来直接接收该终端的输入输出以及终端信号。 其他 jobs 在后台运行。

前台(foreground)就是在终端中运行,能与你有交互的

后台(background)就是在终端中运行,但是你并不能与其任何的交互,也不会显示其执行的过程

工作管理

bash(Bourne-Again shell)支持工作控制(job control),而 sh(Bourne shell)并不支持。

并且每个终端或者说 bash 只能管理当前终端中的 job,不能管理其他终端中的 job。比如我当前存在两个 bash 分别为 bash1、bash2,bash1 只能管理其自己里面的 job 并不能管理 bash2 里面的 job

我们都知道当一个进程在前台运作时我们可以用 ctrl + c来终止它,但是若是在后台的话就不行了。

我们可以通过 & 这个符号,让我们的命令在后台中运行

1
ls &

图中所显示的 [1] 236分别是该 job 的 job number 与该进程的 PID,而最后一行的 Done 表示该命令已经在后台执行完毕。

我们还可以通过 ctrl + z 使我们的当前工作停止并丢到后台中去

被停止并放置在后台的工作我们可以使用这个命令来查看

1
jobs

其中第一列显示的为被放置后台 job 的编号,而第二列的  表示最近(刚刚、最后)被放置后台的 job,同时也表示预设的工作,也就是若是有什么针对后台 job 的操作,首先对预设的 job,- 表示倒数第二(也就是在预设之前的一个)被放置后台的工作,倒数第三个(再之前的)以后都不会有这样的符号修饰,第三列表示它们的状态,而最后一列表示该进程执行的命令

我们可以通过这样的一个命令将后台的工作拿到前台来

1
2
3
#后面不加参数提取预设工作,加参数提取指定工作的编号
#ubuntu 在 zsh 中需要 %,在 bash 中不需要 %
fg [%jobnumber]

之前我们通过 ctrl + z 使得工作停止放置在后台,若是我们想让其在后台运作我们就使用这样一个命令

1
2
#与fg类似,加参则指定,不加参则取预设
bg [%jobnumber]

既然有方法将被放置在后台的工作提至前台或者让它从停止变成继续运行在后台,当然也有方法删除一个工作,或者重启等等

1
2
3
4
5
# kill 的使用格式如下
kill -signal %jobnumber

# signal 从 1-64 个信号值可以选择,可以这样查看
kill -l

其中常用的有这些信号值

信号值 作用
-1 重新读取参数运行,类似与 restart
-2 如同 ctrl+c 的操作退出
-9 强制终止该任务
-15 正常的方式终止该任务

注意
若是在使用 kill + 信号值然后直接加 pid,你将会对 pid 对应的进程进行操作
若是在使用 kill + 信号值然后 %jobnumber,这时所操作的对象是 job,这个数字就是就当前 bash 中后台的运行的 job 的 ID。

更换源

中科大源、清华源或阿里源可任意选择其一:

https://mirrors.ustc.edu.cn
https://mirrors.tuna.tsinghua.edu.cn
https://mirrors.aliyun.com/ubuntu

Ubuntu 的软件源配置文件在 /etc/apt/sources.list。请将系统自带的该文件做个备份。

方式一:使用 sed 命令进行替换

http://archive.ubuntu.com/ 替换为 http://mirrors.ustc.edu.cn 即可。可以使用如下命令:

1
sudo sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

方式二:直接覆盖

以 20.04 LTS 为例,copy 内容覆盖掉 /sources.list 即可。

然后旧版本请运行 sudo apt-get update,新版本请运行 sudo apt update 更新索引以生效。

启用 ssh

先通过 bash 进入子系统修改配置

1
2
3
4
Port = 22 # 默认是 22 端口,如果和 windows 端口冲突或你想换成其他的否则不用动
#ListenAddress 0.0.0.0 # 如果需要指定监听的 IP 则去除最左侧的井号,并配置对应 IP,默认即监听 PC 所有 IP
PermitRootLogin no # 如果你需要用 root 直接登录系统则此处改为 yes
PasswordAuthentication yes # 将 no 改为 yes 表示使用帐号密码方式登录

如果文件不存在说明尚未安装,则执行安装 apt get install openssh-server

之后使用 service ssh start 即可。

然后客户端 ssh 用户名@localhost 可进行登录即可。

ssh 相关知识

  • 查看 ssh 服务状态
    service ssh start
  • 查看 ssh 服务状态
    service ssh status
  • 查看 ssh 服务状态
    service ssh restart
  • 查看 ssh 服务状态
    service ssh stop
  • 生成对应的 rsa, ecdsa, ed25519 三种类型的秘钥

启用 lrzsz

1
sudo apt get install lrzsz

centos 中

sudo yum install lrzsz

常用命令

更新和升级包

1
sudo apt update && sudo apt upgrade

查看 Description 加上 -d
查看当前系统版本,查看版本信息加上 -c
查询全部信息,加上 -a

1
lsb_release -a

遇到过的问题

System has not been booted with systemd as init system

原因是你想用 systemd 命令来管理 Linux 上的服务,但你的系统并没有使用 systemd,(很可能)使用的是经典的 SysV init(sysvinit)系统。

答案很简单,就是不要使用 systemctl 命令,而是使用等同的 sysvinit 命令。相反,可以使用对应的 sysvinit 命令。

Systemd command Sysvinit command
systemctl start service_name service service_name start
systemctl stop service_name service service_name stop
systemctl restart service_name service service_name restart
systemctl status service_name service service_name status
systemctl enable service_name service service_name on
systemctl disable service_name service service_name off

Raspberry Pi

许多操作系统可用于 Operating system images – Raspberry Pi,包括 Raspberry Pi OS,我们的官方支持的操作系统,以及来自其他组织的操作系统。

Raspberry Pi Imager 是为 microSD 卡安装操作系统的一种快速简便的方法,可以与您的 Raspberry Pi 一起使用。或者,从下面的操作系统中选择,可以手动下载和安装。

Raspberry Pi Desktop for PC and Mac
使用 Raspberry Pi 的 Debian 桌面系统是我们的 PC 和 Mac 操作系统。它提供 Raspberry Pi 操作系统桌面,以及大多数推荐的 Raspberry Pi 操作系统软件,适用于任何 PC 或苹果 Mac 电脑。
如果你的旧电脑已经不能运行现代的商业操作系统了,那么可以尝试一下使用 Raspberry Pi 桌面的 Debian: 它可以让你的电脑再次使用。

如何烧录树莓派 OS / 优麒麟(树莓派版)到您的 microSD 存储卡

  1. 树莓派官网 下载 raspberry pi image 工具
  2. 选择镜像
  3. 选择 sd 卡
  4. 点击 write

与树莓派搭配使用的工具: SDFormatter 介绍
https://www.sdcard.org/downloads/formatter/index.html

SDFormatter 是一个简单和有用的 SD 卡修复工具也是内存卡修复工具,专门设计为 TF 卡/手机 SD 卡/ SD 卡 / SDHC 卡 / SDXC 内存卡修复使用,此内存卡修复工具除了可以在 WIN 系统上运行,还支持苹果 MACOSX 系统上运行。

注:本工具只能格式化修复你的内存卡,不能恢复你的内存卡数据,但可以让你的内存卡起死回生,所以你也可以先进行数据恢复再使用本工具修复!

参考

dpkg 命令的详细使用教程_阿力 php 的博客-CSDN 博客_dpkg 命令
dpkg 命令的用法_小绵羊的学习之路的博客-CSDN 博客_dpkg 命令

顺序执行

简单的顺序执行你可以使用;来完成,比如上述操作你可以:

1
2
$ sudo apt-get update;sudo apt-get install some-tool;some-tool
# 让它自己运行

选择执行
可以从$?环境变量获取上一次命令的返回结果
成功则返回 0( 看做 false) , 失败为 1 (看做 true)

&&就是用来实现选择性执行的,它表示如果前面的命令执行结果返回 0 (false)则执行后面的,否则不执行

|| 就是用来实现选择性执行的,它表示如果前面的命令执行结果返回 非0 (true)则执行后面的,否则不执行

如果有则做 -> 为0(false) 再做 ||

1
which cowsay>/dev/null && cowsay -f head-in ohch~

如果没有才做 -> 为1(true)再做 &&

1
which cowsay>/dev/null || echo "cowsay has not been install, please run 'sudo apt-get install cowsay' to install"

管道

管道是什么?管道是一种通信机制,通常用于进程间的通信(也可通过 socket 进行网络通信),它表现出来的形式就是将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)。

管道又分为匿名管道和具名管道(这里将不会讨论在源程序中使用系统调用创建并使用管道的情况,它与命令行的管道在内核中实际都是采用相同的机制)。我们在使用一些过滤程序时经常会用到的就是匿名管道,在命令行中由|分隔符表示,|在前面的内容中我们已经多次使用到了。具名管道简单的说就是有名字的管道,通常只会在源程序中用到具名管道。下面我们就将通过一些常用的可以使用管道的"过滤程序"来帮助你熟练管道的使用。

有太多内容,屏幕不能完全显示,这时候可以使用滚动条或快捷键滚动窗口来查看。不过这时候可以使用管道:

1
ls -al /etc | less

通过管道将前一个命令(ls)的输出作为下一个命令(less)的输入,然后就可以一行一行地看。

cut 命令

cut 命令,打印每一行的某一字段

打印 /etc/passwd 文件中以:为分隔符的第 1 个字段和第 6 个字段分别表示用户名和其家目录:

1
cut /etc/passwd -d ':' -f 1,6
1
2
3
4
5
6
7
8
# 前五个(包含第五个)
$ cut /etc/passwd -c -5
# 前五个之后的(包含第五个)
$ cut /etc/passwd -c 5-
# 第五个
$ cut /etc/passwd -c 5
# 2 到 5之间的(包含第五个)
$ cut /etc/passwd -c 2-5

grep 命令

grep 命令是很强大的,也是相当常用的一个命令,它结合正则表达式可以实现很复杂却很高效的匹配和查找,不过在学习正则表达式之前,这里介绍它简单的使用,而关于正则表达式后面将会有单独一小节介绍到时会再继续学习 grep 命令和其他一些命令。

一般形式

1
grep [命令选项]... 用于匹配的表达式 [文件]...

还是先体验一下,我们搜索 /home/shiyanlou 目录下所有包含 “shiyanlou” 的文本文件,并显示出现在文本中的行号:

1
grep -rnI "shiyanlou" ~

-r 参数表示递归搜索子目录中的文件,-n 表示打印匹配项行号,-I 表示忽略二进制文件。这个操作实际没有多大意义,但可以感受到 grep 命令的强大与实用。

当然也可以在匹配字段中使用正则表达式,下面简单的演示:

1
2
# 查看环境变量中以 "yanlou" 结尾的字符串
export | grep ".*yanlou$"

其中$就表示一行的末尾。

wc 命令

wc 命令用于统计并输出一个文件中行、单词和字节的数目,比如输出/etc/passwd文件的统计信息:

1
wc /etc/passwd

分别只输出行数、单词数、字节数、字符数和输入文本中最长一行的字节数:

1
2
3
4
5
6
7
8
9
10
# 行数
$ wc -l /etc/passwd
# 单词数
$ wc -w /etc/passwd
# 字节数
$ wc -c /etc/passwd
# 字符数
$ wc -m /etc/passwd
# 最长行字节数
$ wc -L /etc/passwd

再来结合管道来操作一下,下面统计 /etc 下面所有目录数:

1
$ ls -dl /etc/*/ | wc -l

sort 命令

这个命令前面我们也是用过多次,功能很简单就是将输入按照一定方式排序,然后再输出,它支持的排序有按字典排序,数字排序,按月份排序,随机排序,反转排序,指定特定字段进行排序等等。

默认为字典排序:

1
cat /etc/passwd | sort

反转排序:

1
cat /etc/passwd | sort -r

按特定字段排序:

1
cat /etc/passwd | sort -t':' -k 3

上面的 -t 参数用于指定字段的分隔符,这里是以 “:” 作为分隔符;-k 字段号 用于指定对哪一个字段进行排序。这里 /etc/passwd 文件的第三个字段为数字,默认情况下是以字典序排序的,如果要按照数字排序就要加上 -n 参数:

1
cat /etc/passwd | sort -t':' -k 3 -n

uniq 去重命令

uniq 命令可以用于过滤或者输出重复行。

  • 过滤重复行

我们可以使用 history 命令查看最近执行过的命令(实际为读取${SHELL}_history 文件,如我们环境中的~/.zsh_history 文件),不过你可能只想查看使用了哪个命令而不需要知道具体干了什么,那么你可能就会要想去掉命令后面的参数然后去掉重复的命令:

1
history | cut -c 8- | cut -d ' ' -f 1 | uniq

然后经过层层过滤,你会发现确是只输出了执行的命令那一列,不过去重效果好像不明显,仔细看你会发现它确实去重了,只是不那么明显,之所以不明显是因为 uniq 命令只能去连续重复的行,不是全文去重,所以要达到预期效果,我们先排序:

1
2
history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq
# 或者$ history | cut -c 8- | cut -d ' ' -f 1 | sort -u

这就是 Linux/UNIX 哲学吸引人的地方,大繁至简,一个命令只干一件事却能干到最好。

  • 输出重复行
1
2
3
4
# 输出重复过的行(重复的只输出一个)及重复次数
$ history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq -dc
# 输出所有重复的行
$ history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq -D

重定向怎么用
文件描述符(0,1,2)

在更多了解 Linux 的重定向之前,我们需要先知道一些基本的东西,前面我们已经提到过 Linux 默认提供了三个特殊设备,用于终端的显示和输出,分别为 stdin(标准输入,对应于你在终端的输入),stdout(标准输出,对应于终端的输出),stderr(标准错误输出,对应于终端的输出)。

文件描述符 设备文件 说明
0 /dev/stdin 标准输入
1 /dev/stdout 标准输出
2 /dev/stderr 标准错误

文件描述符:文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于 UNIX、Linux 这样的操作系统。

我们可以这样使用这些文件描述符:

默认使用终端的标准输入作为命令的输入和标准输出作为命令的输出

1
2
$ cat
(按Ctrl+C退出)

将 cat 的连续输出(heredoc 方式)重定向到一个文件

1
2
3
4
5
6
7
8
9
10
11
$ mkdir Documents
$ cat > Documents/test.c <<EOF
#include <stdio.h>

int main()
{
printf("hello world\n");
return 0;
}

EOF

将一个文件作为命令的输入,标准输出作为命令的输出

1
cat Documents/test.c

将 echo 命令通过管道传过来的数据作为 cat 命令的输入,将标准输出作为命令的输出

1
echo 'hi' | cat

将 echo 命令的输出从默认的标准输出重定向到一个普通文件

1
2
echo 'hello shiyanlou' > redirect
cat redirect

此处输入图片的描述

初学者这里要注意不要将管道和重定向混淆,管道默认是连接前一个命令的输出到下一个命令的输入,而重定向通常是需要一个文件来建立两个命令的连接,你可以仔细体会一下上述第三个操作和最后两个操作的异同点。

2 标准错误重定向

重定向标准输出到文件,这是一个很实用的操作,另一个很实用的操作是将标准错误重定向,标准输出和标准错误都被指向伪终端的屏幕显示,所以我们经常看到的一个命令的输出通常是同时包含了标准输出和标准错误的结果的。比如下面的操作:

1
2
3
4
5
# 使用cat 命令同时读取两个文件,其中一个存在,另一个不存在
$ cat Documents/test.c hello.c
# 你可以看到除了正确输出了前一个文件的内容,还在末尾出现了一条错误信息
# 下面我们将输出重定向到一个文件
$ cat Documents/test.c hello.c > somefile

遗憾的是,这里依然出现了那条错误信息,这正是因为如我上面说的那样,标准输出和标准错误虽然都指向终端屏幕,实际它们并不一样。那有的时候我们就是要隐藏某些错误或者警告,那又该怎么做呢。这就需要用到我们前面讲的文件描述符了:

1
2
3
4
# 将标准错误重定向到标准输出,再将标准输出重定向到文件,注意要将重定向到文件写到前面
$ cat Documents/test.c hello.c > somefile 2>&1
# 或者只用 bash 提供的特殊的重定向符号"&"将标准错误和标准输出同时重定向到文件
$ cat Documents/test.c hello.c &>somefilehell

注意你应该在输出重定向文件描述符前加上**&**,否则 shell 会当做重定向到一个文件名为 1 的文件中

此处输入图片的描述

使用 tee 命令同时重定向到多个文件

你可能还有这样的需求,除了需要将输出重定向到文件,也需要将信息打印在终端。那么你可以使用 tee 命令来实现:

1
echo 'hello shiyanlou' | tee hello

永久重定向

你应该可以看出我们前面的重定向操作都只是临时性的,即只对当前命令有效,那如何做到“永久”有效呢,比如在一个脚本中,你需要某一部分的命令的输出全部进行重定向,难道要让你在每个命令上面加上临时重定向的操作嘛,当然不需要,我们可以使用 exec 命令实现“永久”重定向。exec 命令的作用是使用指定的命令替换当前的 Shell,即使用一个进程替换当前进程,或者指定新的重定向:

1
2
3
4
5
6
7
8
# 先开启一个子 Shell
$ zsh
# 使用 exec 替换当前进程的重定向,将标准输出重定向到一个文件
$ exec 1>somefile
# 后面你执行的命令的输出都将被重定向到文件中,直到你退出当前子 shell,或取消 exec 的重定向(后面将告诉你怎么做)
$ ls
$ exit
$ cat somefile

创建输出文件描述符

在 Shell 中有 9 个文件描述符。上面我们使用了也是它默认提供的 0,1,2 号文件描述符。另外我们还可以使用 3-8 的文件描述符,只是它们默认没有打开而已。你可以使用下面命令查看当前 Shell 进程中打开的文件描述符:

1
cd /dev/fd/;ls -Al

同样使用 exec 命令可以创建新的文件描述符:

1
2
3
4
5
6
7
8
$ zsh
$ exec 3>somefile
# 先进入目录,再查看,否则你可能不能得到正确的结果,然后再回到上一次的目录
$ cd /dev/fd/;ls -Al;cd -
# 注意下面的命令 > 与 & 之间不应该有空格,如果有空格则会出错
$ echo "this is test" >&3
$ cat somefile
$ exit

关闭文件描述符

1
2
exec 3>&-
cd /dev/fd;ls -Al;cd -

如上面我们打开的 3 号文件描述符,可以使用如下操作将它关闭.

完全屏蔽命令的输出

在 Linux 中有一个被称为**“黑洞”**的设备文件,所有导入它的数据都将被“吞噬”。

在类 UNIX 系统中,/dev/null,或称空设备,是一个特殊的设备文件,它通常被用于丢弃不需要的输出流,或作为用于输入流的空文件,这些操作通常由重定向完成。读取它则会立即得到一个 EOF。

我们可以利用 /dev/null 屏蔽命令的输出:

1
cat Documents/test.c 1>/dev/null 2>&1

上面这样的操作将使你得不到任何输出结果。

xargs

xargs 是一条 UNIX 和类 UNIX 操作系统的常用命令。它的作用是将参数列表转换成小块分段传递给其他命令,以避免参数列表过长的问题。

这个命令在有些时候十分有用,特别是当用来处理产生大量输出结果的命令如 find,locate 和 grep 的结果,详细用法请参看 man 文档。

1
cut -d: -f1 < /etc/passwd | sort | xargs echo

上面这个命令用于将 /etc/passwd 文件按 : 分割取第一个字段排序后,使用 echo 命令生成一个列表。

0%