Skip to the content.

TCP协议

协议分层

网络系统模型:常见的分法有七层模型、五层模型和四层模型。

七层模型

层数 名字 功能 典型设备
7 应用层 提供应用程序间通信 应用程序,如 FTP、SMTP、HTTP
6 表示层 处理数据格式、数据加密等 编码方式、图像编码解码、URL 字段传输编码等
5 会话层 建立、维护和关联会话 建立会话,如 session 认证、断点续传
4 传输层 建立主机端到端连接 进程和端口
3 网络层 寻址和路由选择 路由器、防火墙、多层交换机
2 数据链路层 提供介质访问、链路管理 网卡、网桥、交换机
1 物理层 比特流传输 中继器、集线器、网线和 HUB

五层模型

层数 名字 功能
5 应用层 确定进程之间通信的性质以满足用户需求
4 运输层 负责主机间不同进程的通信,如 TCP 和 UDP
3 网络层 负责分组交换网中不同主机间的通信。
作用为:发送数据时,将运输层中的报文段或用户数据报封装成 IP 数据报,并选择合适路由
2 数据链路层 负责将网络层的 IP 数据报组装成帧。
1 物理层 透明地传输比特流

四层模型

层数 名字 功能
4 应用层 DNS、FTP、HTTP、TELENT、SMTP 等
3 传输层 TCP、UDP
2 网间层 TCMP、IP
1 网络接口 ARP、RARP

TCP头部

TCP 头部里每一个字段都为管理 TCP 连接和控制数据流起了重要作用。

1️⃣16 位端口号(port number):告知主机该报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行 TCP 通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号。所有知名服务使用的端口号都定义在 /etc/services 文件中。

2️⃣32 位序号(sequence number):一次 TCP 通信(从 TCP 连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机 A 和主机 B 进行 TCP 通信,A 发送给 B 的第一个 TCP 报文段中,序号值被系统初始化为某个随机值 ISN(Initial Sequence Number,初始序号值)。那么在该传输方向上(从 A 到 B),后续的 TCP 报文段中序号值将被系统设置成 ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个 TCP 报文段传送的数据是字节流中的第 1025~2048 字节,那么该报文段的序号值就是 ISN+1025。另外一个传输方向(从 B 到 A)的 TCP 报文段的序号值也具有相同的含义。

3️⃣32 位确认号(acknowledgement number):用作对另一方发送来的 TCP 报文段的响应。其值是收到的 TCP 报文段的序号值加 1。假设主机 A 和主机 B 进行 TCP 通信,那么 A 发送出的 TCP 报文段不仅携带自己的序号,而且包含对 B 发送来的 TCP 报文段的确认号。反之,B 发送出的 TCP 报文段也同时携带自己的序号和对 A 发送来的报文段的确认号。

4️⃣16 位窗口大小(window size):是 TCP 流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。

TCP状态流转

网络上的传输是没有连接的,TCP 也是一样的。而 TCP 所谓的“连接”,其实只不过是在通信的双方维护一个“连接状态”。

TCP 连接的建立可以简单地称为 3 次握手,而连接的中止则可以称为 4 次握手。

三次握手

1️⃣第一次握手:建立连接时,客户端发送 SYN 包(SYN=J)到服务器,并进入 SYN_SEND 状态,等待服务器确认

2️⃣第二次握手:服务器收到 SYN 包,必须确认客户的 SYN(ACK=J+1),同时自己也发送一个 SYN 包(SYN=K),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态

3️⃣第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ACK=K+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成 3 次握手

sequenceDiagram
participant c as 客户端
participant s as 服务器端
c-->>s:SYN=1, seq=client_isn
s-->s:连接允许,并将接收的 client_isn+1 作为 ack 发送过去
s-->>c:SYN=1, seq=server_isn, ack=client_isn+1
c-->c:ACK
c-->>s:SYN=0, seq=client_isn+1, ack=server_isn+1

对于建连接的 3 次握手,主要是要初始化 Sequence Number 的初始值。通信的双方要互相通知对方自己的初始化的 Sequence Number——所以叫 SYN。这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序(TCP 会用这个序号来拼接数据)。

四次挥手

TCP 有一个特别的概念叫作半关闭,这个概念是说,TCP 的连接是全双工(可以同时发送和接收)连接,因此在关闭连接的时候,必须关闭传和送两个方向上的连接。客户机给服务器一个 FIN 的 TCP 报文,然后服务器返回给客户端一个确认 ACK 报文,并且发送一个 FIN 报文,当客户机回复 ACK 报文后(4 次握手),此时,两台主机上用于该连接的所有资源都被释放了。

sequenceDiagram
participant c as 客户端
participant s as 服务器端
c-->>s:FIN
c-->c:FIN_WAITING_1
s-->>c:ACK
c-->c:FIN_WAITING_2
s-->>c:FIN
c-->>s:ACK
c-->c:TIME_WAIT,经过等待后连接关闭

对于 4 次挥手,其实仔细看则是两次,因为 TCP 是全双工的,所以,发送方和接收方都需要 FIN 和 ACK。只不过,有一方是被动的(客户端主动关闭,服务器端知道该关了也被迫关闭了?),所以看上去就成了所谓的 4 次挥手。如果两边同时断连接,那就会就进入到 CLOSING 状态,然后到达 TIME_WAIT 状态。

TCP超时重传

1️⃣数据顺利到底对端,对端顺利响应 ACK;

2️⃣数据包中途丢失;(异常)

3️⃣数据包顺利到达,但 ACK 报文中途丢失;(异常)

4️⃣数据包梳理到达对端,但对端异常未响应 ACK 或被对端丢弃。(异常)

TCP 有个超时重传机制。如果长时间没得到对方的正确回应就会重传。而重传超时时间参数非常重要:

为了设置合适的超时重传时间,TCP 协议使用自适应算法(Adaptive Retransmission Algorithm)以适应互联网分组传输时延的变化。这种算法的基本要点是 TCP 监视每个连接的性能(即传输时延),由每一个 TCP 的连接情况推算出合适的 RTO 值,当连接时延性能变化时,TCP 也能够相应地自动修改 RTO 的设定,以适应这种网络的变化。

为了动态地设置,TCP 引入了 RTT(Round Trip Time),也就是连接往返时间,指发送端从发送 TCP 包开始到接收它的立即响应所耗费的传输时间。

RTT 的算法有两种

1️⃣RFC793 记录的经典算法

2️⃣Karn/Partridge 算法

TCP滑动窗口

TCP 的滑动窗口主要有两个作用:一是提供 TCP 的可靠性;二是提供 TCP 的流量控制特性(如果对方接受能力不够就缩小滑动窗口的大小)。

TCP 是双工的协议,会话的双方都可以同时接收、发送数据。TCP 会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。其中各自的“接收窗口”大小取决于应用、系统、硬件的限制(TCP 传输速率不能大于应用的数据处理速率)。

如果 TCP 滑动窗口最边上的数据完成了传输,则窗口会向后滑动,滑动至最后一个没有完成传输的字节上。

应用程序在需要(如内存不足)时,可以通过 API 通知 TCP 协议栈缩小 TCP 的接收窗口。然后 TCP 协议栈在下个时间段发送时包含新的窗口大小通知给对端,对端按通知的窗口来改变发送窗口,以此达到减缓发送速率的目的。但是现代的 OS 可以智能的改变 TCP 窗口的大小,无需手工干预。

TCP拥塞控制

如果一下接收到太多的数据网络就会发送拥塞,拥塞的结果就是丢包。

注意区分拥塞控制和流量控制。流量控制指点对点通信量的控制。

TCP 的拥塞控制由 4 个核心算法组成:慢开始(Slow Start)、拥塞避免(Congestion Voidance)、快速重传(Fast Retransmit)和快速恢复(Fast Recovery)。

注意区分拥塞窗口和滑动窗口:滑动窗口与拥塞窗口的区别就在于,滑动窗口取决于接收端的缓存大小,拥塞窗口取决于当前网络的负载能力。滑动窗口位于传输层(区别于数据链路层的),拥塞控制更关注网络层。

慢开始和拥塞避免

拥塞控制算法流程

1️⃣TCP 连接初始化,将拥塞窗口设置为 1

2️⃣执行慢开始算法,cwind 按指数规律增长,直到 cwind=ssthress 时,开始执行拥塞避免算法,cwnd 按线性规律增长。

3️⃣当网络发生拥塞,把 ssthresh 值更新为拥塞前 ssthresh 值的一半,cwnd 重新设置为 1,按照步骤2️⃣执行。

为什么这么设计

发送方维持一个叫作拥塞窗口 cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口可能小于拥塞窗口。

慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。

在刚刚开始发送报文段时可先将拥塞窗口 cwnd 设置为一个最大报文段的 MSS 的数值。在每收到一个对新报文段确认后,将拥塞窗口增加至多一个 MSS 的数值,当 rwind 足够大的时候,为了防止拥塞窗口 cwind 的增长引起网络拥塞,还需要另外一个变量,即慢开始门限 ssthresh。

if cwnd<ssthresh:
	使用慢开始算法
elif cwnd>ssthresh:
	使用拥塞避免算法
elif cwnd == sshthresh:
	慢开始算法与拥塞避免算法任选

拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1,而不是加倍。

快重传和快恢复

快重传

快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方,不必非得等超时重传定时器),而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到 3 个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。

快恢复算法

1️⃣当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把 ssthresh 门限减半。但是接下去并不执行慢开始算法。

2️⃣考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将 cwnd 设置为 ssthresh 的大小,然后执行拥塞避免。

小结

从整体上来讲,TCP 拥塞控制窗口变化的原则是加法增大、乘法减小。可以看出 TCP 的该原则可以较好地保证流之间的公平性,因为一旦出现丢包,那么立即减半退避,可以给其他新建的流留有足够的空间,从而保证整个的公平性。

网络IO模型

四种网络IO模型

为了解决网络 IO 中的问题,学者们提出了 4 种网络 IO 模型:

①阻塞 IO 模型;

②非阻塞 IO 模型;

③多路 IO 复用模型;

④异步 IO 模型。

阻塞IO

阻塞和非阻塞的概念描述的是用户线程调用内核 IO 操作的方式:阻塞是指 IO 操作需要彻底完成后才返回到用户空间;而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,不需要等到 IO 操作彻底完成。

非阻塞IO

当用户进程发出 read 操作时,如果内核中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个错误。从用户进程角度讲,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。当用户进程判断结果是一个错误时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据复制到了用户内存中,然后返回正确的返回值。

这样需要不断的轮询查看数据是否准备好,也很消耗 CPU 资源。

多路复用IO

多路 IO 复用,有时也称为事件驱动 IO。它的基本原理就是有个函数(如 select)会不断地轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程

当用户进程调用了 select,那么整个进程会被阻塞,而同时,内核会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从内核拷贝到用户进程。

这个模型和阻塞 IO 的模型其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select 和 recvfrom),而阻塞 IO 只调用了一个系统调用(recvfrom)。但是,用 select 的优势在于它可以同时处理多个连接。所以,如果处理的连接数不是很高的话,使用 select/epoll 的 Web server 不一定比使用多线程的阻塞 IO 的 Web server 性能更好,可能延迟还更大;select/epoll 的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

异步IO

用户进程发起 read 操作之后,立刻就可以开始去做其他的事;而另一方面,从内核的角度,当它收到一个异步的 read 请求操作之后,首先会立刻返回,所以不会对用户进程产生任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存中,当这一切都完成之后,内核会给用户进程发送一个信号,返回 read 操作已完成的信息。

select/poll/epoll

C10K问题

新到来一个 TCP 连接,就需要分配一个进程。假如有 C10K,就需要创建 1W 个进程,可想而知单机是无法承受的。那么如何突破单机性能是高性能网络编程必须要面对的问题,进而这些局限和问题就统称为 C10K 问题

select

int select(int maxfdp,
           fd_set *readfds,
           fd_set *writefds,
           fd_set *errorfds,
           struct timeval*timeout);

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生(全部都要拷贝到内核里,开销大!),检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。

poll

对比 select 进行了改进,poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。

总结:遍历开销大,用户态与内核态之间拷贝文件描述符集合开销大。

epoll

epoll 通过两个方面,很好解决了 select/poll 的问题。

网络分析工具

ping、tcpdump、netstat 和 lsof 这 4 个网络分析工具的使用。

ping

ping 属于一个通信协议,是 TCP/IP 协议的一部分。利用 ping 命令可以检查网络是否连通,可以很好地帮助分析和判定网络故障。

ping 的原理:利用网络上机器 IP 地址的唯一性,给目标 IP 地址发送一个数据包,再要求对方返回一个同样大小的数据包来确定两台网络机器是否连接相通以及时延是多少。

1️⃣使用 ipconfig/all 观察本地网络设置是否正确。

2️⃣ping 127.0.0.1,来检查本地的 TCP/IP 协议有没有设置好。

3️⃣ping 本机 IP 地址,这样是为了检查本机的 IP 地址是否设置有误。

4️⃣ping 本网网关或本网 IP 地址,这样的是为了检查硬件设备是否有问题,也可以检查本机与本地网络连接是否正常。(在非局域网中这一步骤可以忽略)

5️⃣ping 本地 DNS 地址,这样做是为了检查本地 DNS 服务器是否工作正常。

6️⃣ping 远程 IP 地址,这主要是检查本网或本机与外部的连接是否正常。ping 远程 IP 地址还可以用来测试网络延时。

tcpdump

tcpdump 可以将网络中传送的数据包的“头”完全截获下来提供分析。提供and、or、not 等逻辑语句来帮助去掉无用的信息,是一种免费的网络分析工具。

tcpdump 支持相当多的不同参数,如使用 -i 参数指定 tcpdump 监听的网络界面,这在计算机具有多个网络界面时非常有用;使用 -c 参数指定要监听的数据包数量,使用 -w 参数指定将监听到的数据包写入文件中保存,等等。

tcpdump [ -adeflnNOpqStvx ] [ -c 数量 ] 
		[ -F 文件名 ][ -i 网络接口 ] 
		[ -r 文件名] [ -s snaplen ]
		[ -T 类型 ] [ -w 文件名 ] 
		[表达式 ]

截取某主机相关的包

# linux 下执行 tcpdump
sudo tcpdump host 172.31.181.31

# telnet 下执行建立连接命令
Microsoft Telnet> o 172.31.181.31 1005

截取多个主机相关的包

在命令行中使用括号时,一定要添加 “\”

# linux 下执行 tcpdump
sudo tcpdump host 172.31.181.31 and \(171.31.181.31 or 161.31.181.31 \)

# telnet 下执行建立连接命令
Microsoft Telnet> o 172.31.181.31 1005

截取某端口相关的包

tcpdump port 6666

截取网卡 eth0 上通过的包

tcpdump -ieth0

获取主机 210.27.48.1 接收或发出的 telnet 包

tcpdump tcp port 23 host 210.27.48.1

netstat

netstat 命令用于显示与 IP、TCP、UDP 和 ICMP 协议相关的统计数据,一般用于检验本机各端口的网络连接情况。netstat 是在内核中访问网络及相关信息的程序,它能提供 TCP 连接、对 TCP 和 UDP 的监听及获取进程内存管理的相关报告。

PS C:\Users\69546> netstat

活动连接

  协议  	本地地址          		外部地址          状态
  TCP    127.0.0.1:10282        xxx:51124        ESTABLISHED
  TCP    127.0.0.1:49677        xxx:49678        ESTABLISHED
  TCP    127.0.0.1:49678        xxx:49677        ESTABLISHED
  TCP    127.0.0.1:49679        xxx:49680        ESTABLISHED
  TCP    127.0.0.1:49680        xxx:49679        ESTABLISHED
  TCP    127.0.0.1:49681        xxx:49682        ESTABLISHED
  TCP    127.0.0.1:49682        xxx:49681        ESTABLISHED
  TCP    127.0.0.1:49693        xxx:65001        ESTABLISHED
  TCP    127.0.0.1:51124        xxx:10282        ESTABLISHED
  TCP    127.0.0.1:65001        xxx:49693        ESTABLISHED
  TCP    192.168.0.129:49820    17.248.165.143:https   CLOSE_WAIT

常见参数

-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服务状态
-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到

lsof

lsof(list open file)是一个列出当前系统打开文件的工具。在 Linux 环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。

应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过 lsof 工具能够查看这个列表对系统监测以及排错将是很有帮助的。

lsof  filename 显示打开指定文件的所有进程
lsof -a 表示两个参数都必须满足时才显示结果
lsof -c string   显示COMMAND列中包含指定字符的进程所有打开的文件
lsof -u username  显示所属user进程打开的文件
lsof -g gid 显示归属gid的进程情况
lsof +d /DIR/ 显示目录下被进程打开的文件
lsof +D /DIR/ 同上,但是会搜索目录下的所有目录,时间相对较长
lsof -d FD 显示指定文件描述符的进程
lsof -n 不将IP转换为hostname,缺省是不加上-n参数
lsof -i 用以显示符合条件的进程情况
# 查看 80 端口的运行情况
lsof -i :80

# 查看所属root用户进程所打开的文件,文件类型为.txt
lsof -a -u root -d txt

# 监控打开的文件和设备。查看设备/dev/tty1被哪些进程占用的命令
lsof /dev/tty1

# 监控程序。如查看指定程序server打开的文件
lsof -c server

# 监控用户。比如查看指定用户sharexu打开的文件
lsof -u sharexu

Linux三剑客

awk 入门教程 - 阮一峰的网络日志 (ruanyifeng.com)

[Linux awk 命令 菜鸟教程 (runoob.com)](https://www.runoob.com/linux/linux-comm-awk.html)

grep、sed、awk,三者的功能都是处理文本,但侧重点各不相同,其中属 awk 功能最强大,但也最复杂。

grep

grep 可以用正则表达式进行搜索。基本格式如下:

grep [option] pattern file

# 查询xxx进程
ps -ef | grep xxx

grep 常用参数

参数 实例 说明
-A<行数 x> grep -A2 4 a.txt 显示包含数字 4 的那一行内容和 4 后面的 2 行内容。
-B<行数 x> grep -B2 4 a.txt 显示包含数字 4 的那一行内容和 4 前面的 2 行内容。
-C<行数 x> grep -C2 4 a.txt 显示包含数字 4 的那一行内容,并显 4 前后 2 行(1+2+2=5 行)的内容。
-c grep -c 1 a.txt
grep -c main base_grammar/*
统计 a.txt 中包含数字 1 的行数
统计 bas_grammar 目录下,每个文件包含单词 main 的行数。
-e grep -e a -e b a.txt 实现多个选项间的逻辑 or 关系,显示“包含 a 或 b ”的行的内容。
-F grep -F ‘aa*’ a.txt pattern 视为固定字符串,显示 a.txt 中包含字符串 ‘aa*’ 的行的内容。’*’ 不代表正则了。
-i   忽略大小写
-o   只显示匹配到的字符串
-s   不显示错误信息
-v grep -v aa* a.txt 显示未被匹配到的行中的内容
-w grep -w AaA a.txt
grep -i -w AaA a.txt
匹配整个单词,完全符合(这一行的内容都一样)的单词才会被匹配到。
匹配过程忽略大小写。

sed

创建文件 sed.txt

touch sed.txt
# 写入内容
This is a test txt
Hello World
Test sed balabala

增加内容

# 在第三行后面追加内容 Hello(第四行追击了内容 Hello)
sed -e 3a\Hello sed.txt

但是注意,这个只是将文字处理了,没有写入到文件里,文件里还是之前的内容。

# 在出现了 Test 的行的后面追加 TTTTsed -e /Test/a\TTTT sed.txt

# 只在出现了 test(忽略大小写) 的行后面追加 TTTTsed -e /'[t|T]est'/a\TTTT sed.txt

# 也可以用 ""sed -e "/Test/a TTTT" sed.txt
This is a test txt
Hello
Hello World
Test sed balabala
TTTT

# 将结果重定向到其他文件中sed -e "/Test/a TTTT" sed.txt  > newsed.txt

插入内容

a 类似,sed 3i\TTTT sed.txt 是在第三行前面插入 TTTT:

sed 3i\TTTT sed.txt
This is a test txt
Hello
TTTT
Hello World
Test sed balabala

sed '/Test/i TTTT' sed.txt 是在所有匹配到 test 的行前面插入:

sed '/Test/i TTT' sed.txt
This is a test txt
Hello
Hello World
TTT
Test sed balabala

可以看出插入的用法和增加很相似。

删除内容

# 删除包含 test 或 Test 的行sed '/[t|T]est/d' sed.txt
Hello
Hello World

替换内容

替换则是使用 c,这一行中有一个符合的,那么整行都被替换为 ???

cat sed.txt
This is a test txt
Hello
Hello World
Test sed balabala

# 将 test/Test 替换为 ????sed '/[t|T]est/c ???' sed.txt
???
Hello
Hello World
???

➜ sed '/World/c ???' sed.txt
This is a test txt
Hello
???
Test sed balabala

➜ cat new.txt
TestTestTest
Hello
Hello World
TestTestTest

➜ sed '/[t|T]est/c !!!' new.txt
!!!
Hello
Hello World
!!!

还有一种替换是 s,sed 's/old/new/g',old 表示想要匹配的字符,new 表示想要替换的字符。这里的 /g 的意思是一行中的每一次匹配,因为一行中可能匹配到很多次

cat new.txt
TestTestTest
Hello
Hello World
TestTestTest

➜ sed 's/Test/test/g' new.txt
testtesttest
Hello
Hello World
testtesttest

写入文件

使用重定向 ‘>’ 或者是 ‘-i’ 将变动写入文件中。

cat sed.txt
This is a test txt
Hello
Hello World
Test sed balabala

# c 是这个有匹配的,那么这一行全部修改为 Thatsed '/This/c That' -i sed.txt

➜ cat sed.txt
That
Hello
Hello World
Test sed balabala

awk

awk 是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。

基本用法

# 格式
awk 动作 文件名

# 示例,打印 demo.txt 中的所有行 $0 表示当前行
awk '{print $0}' demo.txt

简单示例

# 每行有三个单词,如第一行有单词1、hello、threecat demo.txt
1 hello three
2 world three
3 awk three
4 what three
5 ? three

# 打印每行的第一个单词和第三个单词awk '{print $1,$3}' demo.txt
1 three
2 three
3 three
4 three
5 three

指定分隔符

# 使用 sed 将空格替换为,sed 's/ /,/g' demo.txt > copy.txt

➜ cat copy.txt
1,hello,three
2,world,three
3,awk,three
4,what,three
5,?,three

➜ awk -F, '{print $1,$3}' copy.txt
1 three
2 three
3 three
4 three
5 three

# 使用空格和逗号两个分隔符
awk -F '[ ,]' xxx

# 指定分隔符示例cat tmp.txt
HELLO:WORLD

➜ awk -F ':' '{print $1,$2}' tmp.txt
HELLO WORLD

变量

awk 中有不少内置的变量,比如 $NF 代表的是分割后的字段数量,相当于取最后一个,${NF-1} 表示倒数第二个字段。

awk '{print $NF}' demo.txt
three
three
three
three
three

常见的内置变量

变量 说明
FILENAME 当前文件名
FS 字段分隔符,默认是空格和制表符
RS 行分隔符,用于分割每一行,默认是换行符
OFS 输出字段的分隔符,用于打印时分隔字段,默认为空格
ORS 输出记录的分隔符,用于打印时分隔记录,默认为换行符
OFMT 数字输出的格式,默认为 %.6g

函数

# 将第二行的单词转为大写awk '{print toupper($2)}' demo.txt
HELLO
WORLD
AWK
WHAT
?

常见函数

函数 说明
toupper 字符转为大写
tolower 字符转为小写
length 返回字符串长度
substr 返回子字符串
sin 正弦
cos 余弦
sqrt 平方根
rand 随机数

条件

awk 允许指定输出条件,只输出符合条件的行。输出条件要写在动作的前面。

# awk '条件 动作' 文件名
# 显示不为 ?,所有列第二行单词的大写awk '{if($2!="?") print toupper($2)}' demo.txt
HELLO
WORLD
AWK
WHAT

后记

一些无聊的命令练习

# 1️⃣echo hello world | sed 's/l/~/g' > tmp.txt
➜ cat tmp.txt
he~~o wor~d

# 2️⃣echo hello world | awk '{print toupper($1),toupper($2)}' > tmp.txt
➜ cat tmp.txt
HELLO WORLD

➜ echo hello world | awk '{print toupper($0)}' > tmp.txt
➜ cat tmp.txt
HELLO WORLD