本文就分享一下我在实践中使用重定向和管道符遇到的一些坑,搞明白一些底层原理,写脚本的效率能提升不少。

我很喜欢 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 && mv temp.txt file.txt

这是最简单,最可靠,万无一失的方法。

你如果嫌这段命令太长,也可以通过apt/brew/yum等包管理工具安装moreutils包,就会多出一个sponge命令,像这样使用:

# 先把数据传给 sponge,然后由 sponge 写入原始文件
$ cat file.txt | head -n 2 | sponge file.txt

sponge这个单词的意思是海绵,挺形象的,它会先把输入的数据「吸收」起来,最后再写入file.txt,核心思路和我们使用临时文件时类似的,这个「海绵」就好比一个临时文件,就可以避免同时打开同一个文件进行读写的问题。

以上就是重定向和管道符的一些坑,希望能帮到你。

Linux重定向和管道符使用避坑指南相关推荐

  1. linux离线安装python3.6_Linux下安装Python3.6及避坑指南

    Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装Python3之前,需要将这些依赖库先行安装好. yum -y install zlib-dev ...

  2. 罗技无线网卡linux,Linux(Ubuntu)装罗技LMS避坑指南

    Linux(Ubuntu)装罗技LMS避坑指南 前言:搜索音质好的播放软件时发现MPD竟然可以直接命令行操作.连界面都是在终端里的.满满的极客范,心动.搞起!结果-搞了几个小时.各种折腾.就是读取不到 ...

  3. Linux重定向与管道符

    Linux重定向与管道符 1.标准输入与输出 1)什么是重定向 将原本要输出到屏幕的数据信息,重新定向到某个指定的文件中.比如:每天凌晨定时备份数据,希望将备份数据的结果保存到某个文件中.这样第二天通 ...

  4. python避坑指南_Linux下Python3.6的安装及避坑指南

    Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装 Python 3之前,需要将这些依赖库先行安装好. yum -y install zlib-d ...

  5. HarmonyOS 开发避坑指南

    Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...

  6. 【吐血经验】在 windows 上安装 spark 遇到的一些坑 | 避坑指南

    在 windows 上安装 spark 遇到的一些坑 | 避坑指南 最近有个活:给了我一个阿里云桌面(windows 10系统),让我在上面用 scala + spark 写一些东西. 总是报错不断, ...

  7. centos8安装之centos8.3 制作U盘启动 避坑指南

    centos8安装之centos8.3 制作U盘启动 避坑指南 最近打算用物理机安装centos8.3,之前用vmware虚拟机安装过centos8,也没有什么问题,但是这次安装centos8.3偏偏 ...

  8. HarmonyOS实战 —基于hi3861芯片鸿蒙2.0的避坑指南

    HarmonyOS实战 -基于hi3861芯片鸿蒙2.0的避坑指南 特别说明:本文章与卡片开发无关,想看卡片开发的不用往下读了 最近学习鸿蒙设备开发的过程中遇到了很多问题,因为目前几乎所有设备开发教程 ...

  9. Ununtu 18.04 安装Carla 0.9.13 以及Carla ros bridge 超级避坑指南(更新于2022.10.20)

    Carla0.9.13 以及Carla ros bridge 超级避坑指南 Carla0.9.13 以及Carla ros bridge 超级避坑指南 站在巨人肩膀前进 显卡驱动问题 首先就是虚幻4的 ...

最新文章

  1. 酷讯出来的张一鸣为什么做了新闻不是旅游?
  2. 使用IntelliJ IDEA开发SpringMVC网站(一)开发环境
  3. python字典嵌套列表怎么访问值的某个元素_通过键列表访问嵌套字典项?
  4. 规划System Center 2012 R2 Operations manager
  5. OD反汇编EXE添加一个启动时的消息框
  6. java执行命令行命令
  7. Error connecting to database: No such file or directory
  8. SliderBar4.0常用滑块滚动效果封装
  9. 快速突破面试算法之排序篇
  10. Mac系统添加MySQL配置文件
  11. linux 什么是SO文件
  12. 《国际学术论文写作与发表》课后题
  13. c语言输出0.000000或乱码,深究
  14. 给IT新人的15点建议:苦逼程序员的辛酸反省与总结
  15. ISP——坏点矫正(Defective Pixel Correction,DPC)
  16. React —— Descriptions(根据自己需求判断要显示哪些)
  17. 华为云耀服务器与弹性云服务器的区别
  18. mysql5.7出现:ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)问题解决
  19. margin-left:-100%理解
  20. 关于OpenCV的个人小心得

热门文章

  1. 如何轻松阅读 GitHub 上的项目源码?
  2. 爬取一万条b站评论看《工作细胞》
  3. Django博客系统(首页分类数据展示)
  4. MySQL数据库中的事务(四大特性)
  5. python注释#、Ctrl+/
  6. 图像滤波常用算法实现及原理解析
  7. 这个北航妹子也太卷了...
  8. 综述:解决目标检测中的样本不均衡问题
  9. 5 种将死的编程语言!
  10. 推荐搜索系统论文干货集锦