Linux管道破裂,尴尬,一不小心把 Linux 管道给整漏了
我很喜欢 Linux 系统,尤其是 Linux 的一些设计很漂亮,比如可以将一些复杂的问题分解成若干小问题,通过管道符和重定向机制灵活地用现成的工具解决,写成 shell 脚本就很高效。
本文就分享一下我在实践中使用重定向和管道符遇到的一些坑,搞明白一些底层原理,写脚本的效率能提升不少。
> 和 >> 重定向符的坑
先说第一个问题,执行如下命令会发生什么?
$ cat file.txt > file.txt
读取再写入同一个文件,感觉什么也不会发生对吧?
实际上,上述命令运行的结果是清空file.txt文件中的内容。
PS:有的 Linux 发行版可能会直接报错,可以执行cat < file.txt > file.txt绕开这个检测。
前文 Linux 进程和文件描述符 说过,程序本身没有必要关心自己的标准输入/输出指向哪里,是 shell 通过管道符和重定向符号修改了程序的标准输入/输出的位置。
所以执行cat file.txt > file.txt这个命令时,shell 会先打开file.txt,由于重定向符号是>,所以文件中的内容会被清空,然后 shell 将cat命令的标准输出设置为file.txt,这时候cat命令才开始执行。
也就是如下过程:
1、shell 打开file.txt并清空其内容。
2、shell 将cat命令的标准输出指向file.txt文件。
3、shell 执行cat命令,读了一个空文件。
4、cat命令将空字符串写入标准输出(file.txt文件)。
所以,最后的结果就是file.txt变成了空文件。
我们知道,>会清空目标文件,>>会在目标文件尾部追加内容,那么如果将重定向符>改成>>会怎样呢?
$ echo hello world > file.txt # 文件中只有一行内容
$ cat file.txt >> file.txt # 这个命令会死循环
file.txt中首先被写入一行内容,执行cat file.txt >> file.txt后预期的结果应该是两行内容。
但是很遗憾,运行结果并不符合预期,而是会死循环不断向file.txt中写入 hello world,文件很快就会变得很大,只能用 Control+C 停止命令。
这就有意思了,为什么会死循环呢?其实稍加分析就可以想到原因:
首先要回忆cat命令的行为,如果只执行cat命令,就会从命令行读取键盘输入的内容,每次按下回车,cat命令就会回显输入,也就是说,cat命令是逐行读取数据然后输出数据的。
那么,cat file.txt >> file.txt命令的执行过程如下:
1、打开file.txt,准备在文件尾部追加内容。
2、将cat命令的标准输出指向file.txt文件。
3、cat命令读取file.txt中的一行内容并写入标准输出(追加到file.txt文件中)。
4、由于刚写入了一行数据,cat命令发现file.txt中还有可以读取的内容,就会重复步骤 3。
以上过程,就好比一边遍历列表,一遍往列表里追加元素一样,永远遍历不完,所以导致我们的命令死循环。
> 重定向符和 | 管道符配合
我们经常会遇到这样的需求:截取文件的前 XX 行,其余的都删除。
在 Linux 中,head命令可以完成截取文件前几行的功能:
$ cat file.txt # file.txt 中有五行内容
1
2
3
4
5
$ head -n 2 file.txt # head 命令读取前两行
1
2
$ cat file.txt | head -n 2 # head 也可以读取标准输入
1
2
如果我们想保留文件的前 2 行,其他的都删除,可能会用如下命令:
$ head -n 2 file.txt > file.txt
但是这就犯了前文说的错误,最后file.txt会被清空,不能实现我们的需求。
那我们是这样写命令是否可以避坑呢:
$ cat file.txt | head -n 2 > file.txt
结论是不行,文件内容依然会被清空。
What?是不是管道漏了,把数据全漏掉了?
前文 Linux 进程和文件描述符 也说过管道符的实现原理,本质上就是将两个命令的标准输入和输出连接起来,让前一个命令的标准输出作为下一个命令的标准输入。
但是,如果你认为这样写命令可以得到预期的结果,那可能是因为你认为管道符连接的命令是串行执行的,这是一个常见的错误,实际上管道符连接的多个命令是并行执行的。
你可能以为,shell 会先执行cat file.txt命令,正常读取file.txt中的所有内容,然后把这些内容通过管道传递给head -n 2 > file.txt命令。
虽然这时候file.txt中的内容会被清空,但是head并没有从文件中读取数据,而是从管道读取数据,所以应该可以向file.txt正确写入两行数据。
但实际上,上述理解是错误的,shell 会并行执行管道符连接的命令,比如说执行如下命令:
$ sleep 5 | sleep 5
shell 会同时启动两个sleep进程,所以执行结果是睡眠 5 秒,而不是 10 秒。
这是有点违背直觉的,比如这种常见的命令:
$ cat filename | grep'pattern'
直觉好像是先执行cat命令一次性读取了filename中所有的内容,然后传递给grep命令进行搜索。
但实际上是cat和grep命令是同时执行的,之所以能得到预期的结果,是因为grep 'pattern'会阻塞等待标准输入,而cat通过 Linux 管道向grep的标准输入写入数据。
执行下面这个命令能直观感受到cat和grep是在同时执行的,grep在实时处理我们用键盘输入的数据:
$ cat | grep'pattern'
说了这么多,再回顾一开始的问题:
$ cat file.txt | head -n 2 > file.txt
cat命令和head会并行执行,谁先谁后不确定,执行结果也就不确定。
如果head命令先于cat执行,那么file.txt就会被先清空,cat也就读取不到任何内容;反之,如果cat先把文件的内容读取出来,那么可以得到预期的结果。
不过,通过我的实验(将这种并发情况重复 1w 次)发现,file.txt被清空这种错误情况出现的概率远大于预期结果出现的概率,这个暂时还不清楚是为什么,应该和 Linux 内核实现进程和管道的逻辑有关。
解决方案
说了这么多管道符和重定向符的特点,如何才能避免这个文件被清空的坑呢?
最靠谱的办法就是不要同时对同一个文件进行读写,而是通过临时文件的方式做一个中转。
比如说只保留file.txt文件中的头两行,可以这样写代码:
# 先把数据写入临时文件,然后覆盖原始文件
$ cat file.txt | head -n 2 > temp.txt && mvtemp.txt file.txt
这是最简单,最可靠,万无一失的方法。
你如果嫌这段命令太长,也可以通过apt/brew/yum等包管理工具安装moreutils包,就会多出一个sponge命令,像这样使用:
# 先把数据传给 sponge,然后由 sponge 写入原始文件
$ cat file.txt | head -n 2 | sponge file.txt
sponge这个单词的意思是海绵,挺形象的,它会先把输入的数据「吸收」起来,最后再写入file.txt,核心思路和我们使用临时文件时类似的,这个「海绵」就好比一个临时文件,就可以避免同时打开同一个文件进行读写的问题。
以上就是重定向和管道符的一些坑,希望能帮到你。
【编辑推荐】
【责任编辑:武晓燕 TEL:(010)68476606】
点赞 0
Linux管道破裂,尴尬,一不小心把 Linux 管道给整漏了相关推荐
- linux循环管道之给外面,尴尬,一不小心把 Linux 管道给整漏了,
尴尬,一不小心把 Linux 管道给整漏了, 我很喜欢 Linux 系统,尤其是 Linux 的一些设计很漂亮,比如可以将一些复杂的问题分解成若干小问题,通过管道符和重定向机制灵活地用现成的工具解决, ...
- linux下软件如何防破裂,linux下管道破裂的處理
管道破裂的原因解釋如下 拷貝黏貼 我寫了一個服務器程序,在Linux下測試,然后用C++寫了客戶端用千萬級別數量的短鏈接進行壓力測試. 但是服務器總是莫名退出,沒有core文件. 最后問題確定為, 對 ...
- linux通信管道破裂,Linux下进程通信之管道
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把 ...
- linux过滤输出内容,Linux内容整理--过滤器、输入输出及管道
Linux内容整理--过滤器.输入输出及管道1.过滤器 Linux中的应用工具分为三种: 1.交互工具 2.过滤器 3.编辑器 能够接受数据,过滤再输出的工具,称为过滤器. 对过滤器和进程,存在着输入 ...
- linux 重定向_Unix/Linux编程实践之IO重定向和管道
I/O重定向的原理模型 ls > test.file是如何工作的?shell是如何告诉程序把结果输出到文件,而不是屏幕? 在who | sort > user.file中,shell是如何 ...
- 管道半双工通信程序linux,Linux进程间通信的几种方法-半双工管道,命名管道,消息队列...
1.半双工管道 简单实现 半双工管道可以实现父进程和子进程之间或者子进程之间(前提是有共同的祖先)的通信 因为是半双工,所以两端不可能同时读取,而是一端读一端取,而且当一端分配到读任务后,那么他就固定 ...
- Linux中的pipe(管道)与named pipe(FIFO 命名管道)
catalogue 1. pipe匿名管道 2. named pipe(FIFO)有名管道 1. pipe匿名管道 管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入 ...
- Linux进程通信(一)——pipe管道
本章内容 采用pipe管道如何进行进程之间的通信 pipe管道进程通信的规则和限制 Linux中pipe管道的实现机制和管理pipe管道的结构体 什么是进程通信 进程通信就是两个进程之间进行数据交换, ...
- linux中管道的概念,浅谈Linux管道
管道(pipe)是一个我们在学习Linux命令行的时候就会引入的一个很重要的概念.管道是UNIX环境中历史最悠久的进程间通信方式,从本质上说,管道也是一种文件,也是遵循UNIX的"一切皆文件 ...
最新文章
- 火爆全网!《算法刷题宝典》资源,免费下载!(含代码数据)
- linux 位置参数数组,JavaScript数组详解
- 与微信、APP正面刚?三大运营商联合发布5G消息白皮书
- 轻量目录访问协议 工具 openldap 简介
- python的类与模块_Python类与模块属性
- CGRectInset CGRectoffset UIEdgeInsetsInsetRect 这三个函数的使用情况
- (20)Verilog HDL并行块:fork-join
- css样式让样式失效,如何让css样式失效
- php 爬取一个人的网易云评论,爬取网易云音乐某一个人的评论
- springboot获取视频时长以及截取视频第一帧
- 图解 FAT 文件系统之基础知识(一)
- 如何用 Unity 编写像炸弹人一样的游戏
- SQL语句中对时间字段进行区间查询
- jinkens搭建及部署项目
- 计算机网络 --- 概述(学习笔记)
- uniapp配置全局样式
- 可视化 | Echarts基础异步加载数据交互组件数据集
- 链接生成二维码( QRCode )
- 大数据时代物联网技术发展前景与应用分析
- 基于蓝牙与Android设备的控制系统设计
热门文章
- Android Studio模拟器运行出现VT-x提示,无法打开模拟器的解决办法
- Windows如何查看自己系统的位数
- 双向最大匹配算法思想详解,分词器及全文检索工具及Lucene框架简介
- 「Adobe国际认证」PHOTOSHOP选区是什么以及为什么要使用选区?
- Android 解决华为虚拟键冲突遮挡底部按钮
- 如何利用SEO推介火狐狸浏览器
- Android 保存bitmap到相册
- 高仿微信相册,高仿三星相册。目的是把手机里的图片放到Ucloud对象存储里节省手机空间20G
- 从小米Q3财报再看雷军的手机双品牌战略
- python医院管理系统代码_Python+MySQL开发医院网上预约系统(课程设计)一