热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linux循环管道之给外面,尴尬,一不小心把Linux管道给整漏了,

尴尬,一不小心把Linux管道给整漏了,我很喜欢Linux系统,尤其是Linux的一些设计很漂亮,比如可以将一些复杂的问题分

尴尬,一不小心把 Linux 管道给整漏了,

0319344151-0.jpg-wh_651x-s_3152918745.jpg

我很喜欢 Linux 系统,尤其是 Linux 的一些设计很漂亮,比如可以将一些复杂的问题分解成若干小问题,通过管道符和重定向机制灵活地用现成的工具解决,写成 shell 脚本就很高效。

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

> 和 >> 重定向符的坑

先说第一个问题,执行如下命令会发生什么?

$ cat file.txt > file.txt

读取再写入同一个文件,感觉什么也不会发生对吧?

实际上,上述命令运行的结果是清空file.txt文件中的内容。

PS:有的 Linux 发行版可能会直接报错,可以执行cat 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提权之suid篇
    Linux提权之suid篇不知攻,焉知防一个在安服路上摸索的大三生,记录平时学习笔记suid前言:1.只有可以执行的二进制程序文件才 ... [详细]
  • 文章目录前言必知必会的软件服务器分类机架式服务器塔式服务器刀片式服务器三者的区别虚拟服务器(云服务器)服务器的硬件组成服务器常见的品牌前言本文是循序渐进学linux的第一课,为 ... [详细]
  • MQ的使用
    安装环境:linuxredhatactivemq版本:5.8.01.从http:activemq.apache.orgdownload.html地址下载 ... [详细]
  • 一、域名解析记录说明记录类型A:用来指定域名的IPv4地址(如:8.8.8.8),如果需要将域名指向一个IP ... [详细]
  • 13Linux基本命令和配置服务器来电后自动开机
    本节所讲内容:Linux终端介绍Shell提示符BashShell基本语法基本命令的使用:ls、pwd、cd查看系统和BIOS硬件时间Linux如何获得 ... [详细]
  • 获取原始语音系统图使用matlab生成一个测试用的单频信号,1KHz,1Vrmsclcclearall;closeall;f1000;%定义信号频 ... [详细]
  • XShell连接不了虚拟机
    本机安装好虚拟机和centeros;使用xshell连接:linuxCouldnotconnectto'127.0.0.1'(por ... [详细]
  • 导读:今天编程笔记来给各位分享关于php动态扩展怎么加载的相关内容,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!本文目录一览: ... [详细]
  • mongodb复制集部署文档 ... [详细]
  • Linux负载均衡LVS(IPVS)
    一、LVS简介LVS是LinuxVirtualServer的简称,也就是Linux虚拟服务器,是一个由章文嵩博士发起的自由软件项目,现在已经是Linux ... [详细]
  • #!usrbinkshPATHusrbin:bin:usrsbinexportPATHtime$(date%F)rq$(perl-eusePOSIXqw(strftime);pr ... [详细]
  • 开发笔记:Python之父重回决策层
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Python之父重回决策层相关的知识,希望对你有一定的参考价值。在GuidovanRossum(吉多· ... [详细]
  • 安全3AAuthentication:认证Authorzation:授权Accouting|Audition:审计用户管理用户:UID:0,不一定是root,root的uid非0时 ... [详细]
  • 系统管理部分软件包管理进程管理服务管理磁盘管理系统管理之软件包管理软件包的安装方式yumapt方式rpmdpkg方式编译安装方式二进制安装方式rpm安装方式增删改查安装:-ivh查 ... [详细]
  • Lunix历史及如何学习
    1.Lunix是什么1.1Lunix是操作系统还是应用程序Lunix是一套操作系统,它提供了一个完整的操作系统当中最底层的硬件控制与资源管理的完整架构, ... [详细]
author-avatar
尹琢抵
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有