首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
V2EX  ›  Linux

cat file.txt > file.txt 导致 file.txt 被清空

  •  
  •   HeiXiaoBai · 183 天前 · 5157 次点击
    这是一个创建于 183 天前的主题,其中的信息可能已经有所发展或是发生改变。
    这个操作为什么会导致这样的结果?
    cat file.txt | grep xxx | sed xxx > file.txt 也是一样
    40 回复  |  直到 2019-06-13 10:01:40 +08:00
        1
    Jirajine   183 天前 via Android
    因为搜索之后无匹配内容?
        2
    momocraft   183 天前   ♥ 1
    因爲寫 shell (the interpreter) 的人不想 (也做不到) 幫你保護文件

    熟悉命令的坑是 unix 用戶自己的責任, 詳見 unix 痛恨者指南
        3
    vuuv   183 天前 via Android   ♥ 18
    因为>是 bash 内部的 IO 重定向标记。当前登录的 bash 会先清空(如果是>>则不清空。)了 file.txt 然后才会 fork 出 cat grep sed 等三个进程。并根据管道符的指示把前一个命令的 stdout 重定向到后者的 stdin。最终把 sed 的 stdout 写入 file.txt 。
        4
    ericFork   183 天前
    你这个需求还是用 sponge 吧,来自 moreutils 这个包
        5
    kwlokip   183 天前 via Android
    你这个命令有什么问题?
        6
    HeiXiaoBai   183 天前 via Android
    @vuuv #3
    看起来有点绕
    理解起来是,遇到重定向符>,会先清除重定向的目标文件,然后才执行前面的命令,然后前面的命令因为文件已经是空的,输出自然是空的?
        7
    vuuv   183 天前 via Android   ♥ 1
    @HeiXiaoBai #6 是的。你先要求 bash 清空了文件。然后 cat 就读到空文件了。
    如果你希望匹配的内容出现在原文件结尾,那么使用>>。追加写入是不会改变已有内容的。
    如果你希望只出现匹配的内容,建议换下后面的文件名。
        8
    HeiXiaoBai   183 天前 via Android
    @vuuv #7
    懂了,多谢,之前还以为是先执行前面,到重定向符才清空,所以才觉得奇怪
        9
    pkookp8   183 天前 via Android
    两个箭头表示 append,一个箭头等于 open
        10
    Meltdown   183 天前 via Android   ♥ 1
    Stackoverflow 上有个一模一样的问题…
        11
    Hardrain   183 天前
    如果你想让上一个命令输出到 stdout 的内容 append 到一个文件,用>>
        12
    also24   183 天前
    直接回答 >> 的朋友们,请注意看楼主的文件名
        13
    ADMlN   183 天前
    清空文件方法 +1
        14
    CEBBCAT   183 天前 via Android
    搜一下就有,不是个好问题,学习一下如何使用搜索工具吧。
        15
    geelaw   183 天前 via iPhone
    cat file.txt > file.txt 肯定会让 file.txt 清空,但对于有多个管道的情况则不是“天然”会导致 file.txt 清空的(除非文档指出了一些实现细节)。

    对于 cmd1 arg1 ... argn > out,shell 惟一正常的实现方式是打开 out 之后 exec,因此 out 会在 cmd1 启动之前就清空。

    如果是 cmd1 args1 | cmd2 args2 > out,那么 shell 启动 cmd1、cmd2 以及打开 out 的顺序、方式可以有很多种,在文档没有规定如何实现的情况下,这可能和 shell 具体是怎么写的有关,即使确定了 shell 的实现,实际结果也可以取决于一些竞态条件的结果。
        16
    yejianmail   182 天前 via Android
    这波操作不是一条 sed -i 就搞定的么?
        17
    HeiXiaoBai   182 天前
    @yejianmail 不是,我就是单纯的对这个重定向符为什么会这样有疑惑而已
        18
    ps1aniuge   182 天前
    问题 :cat file.txt | grep xxx > file.txt 会被清空

    我的看法:
    1shell 命令不规范,垃圾。幺蛾子。
    2 在 powershell 中,用 cat file.txt | grep xxx > file.txt 也会被清空。这是因为 [最大的兼容] 。
    3shell 的话,可以使用多条命令,避过这个幺蛾子。如:
    a=`cat /tmp/sf.txt |grep power`
    echo $a > /tmp/sf.txt



    powershell 本身没有这个问题,powershell 用 get-content 打开文件,用 set-content 保存文件。
    cat file.txt | grep xxx | set-content file.txt #linux 的 cat

    get-content file.txt | grep xxx | set-content file.txt

    结论:
    shell 命令不规范,powershell 用 [set-content] ,代替 [>] ,治疗了这个不规范。

    powershell 不学 [<] ,powershell 不会 [<<] ,powershell 不懂 [EOF] ,却照样 觉得自己 很牛 x。
    大家一定要记住, [<] , [>] , [<<<] , [>>] 这些 shell 中的重定向符号,是 shell 中的邪教,把人带坏,
    是你成为脚本大师路上的坑。这些坑的唯一作用是“烧你脑”!
        19
    msg7086   182 天前   ♥ 2
    @ps1aniuge

    cat file.txt | grep xxx | tee file.txt
    tee 命令用于写入文件,是 1974 年写出来的,Linux 和 Windows 下都有。

    用>覆盖文件是因为>优先于程序执行,PowerShell 里也是这样的,这是>的本质所决定的。如果 PowerShell 发明了>,他也会做成这样。如果你不理解为什么>会优先于程序执行,需要学习一下大学操作系统课程中关于文件描述符的部分。
        20
    msg7086   182 天前   ♥ 5
    @HeiXiaoBai
    整串命令的执行过程是:

    1. 打开输入(标准输入或者 <)。
    2. 打开输出(标准输出或者 > 或者 >>)。
    3. 启动程序,并把输入和输出喂给他们。

    用操作系统的话来说:

    open(输入) 打开输入
    open(输出) 打开输出
    pipe; pipe; 创建两条管道
    fork 出 proc1,proc2,proc3
    proc1.stdin = 输入 fd
    proc1.stdout = 管道 1 入口
    proc2.stdin = 管道 1 出口
    proc2.stdout = 管道 2 入口
    proc3.stdin = 管道 2 出口
    proc3.stdout = 输出 fd
    proc1/2/3 分别 exec 执行目标程序。

    你的文件就是在第二步打开输出的时候被覆盖的,在三个程序还没启动的时候,输出就已经清空了。
        21
    ps1aniuge   182 天前
    tee 命令用于写入文件-----------这谁告诉你的?
    tee 在我脑中“是把管道输入,输出到 [管道输出] ,并克隆一份,在标准输出”,即屏幕显示。

    tee 一个大文件,,中文件,,,的话,因为有屏幕输出,导致这命令执行结果很慢了。
    还会对用户产生 [刷屏] 攻击,捣乱屏幕输出。

    结论:
    把>换成 set-content,是高杆。
    把>换成 tee,是幺蛾子。是从屎窝挪到尿窝。

    我的格言:
    win+bat 界,linux+bash 界,对待 powershell 的态度,就是脚本运维人进步的尺度。
        22
    hjq98765   182 天前
    cat file.txt >> file.txt

    会提示 input file is output file
        23
    guog   182 天前
    @ps1aniuge #21 改个文件名就 vans 了。啥,你不想改名,sed -i 啊,整这么多幺蛾子
        24
    guog   182 天前
    @hjq98765 #22 mac 上死循环,无限写文件
        25
    PTLin   182 天前
    可以用 moreutils 里的 sponge 来解决这个问题,cat file.txt | sponge file.txt ,对实现感兴趣也可以去看看源码。
        26
    masker   182 天前 via Android
    @ps1aniuge 求求你,不要来这里当布道者了
        27
    masker   182 天前 via Android
    @livid #18 这种贬低他人抬高自己的布道者是否应该封禁
        28
    momocraft   182 天前
    The tee utility copies standard input to standard output, making a copy in zero or more files. The output is unbuffered.

    求求你 RTFM 一下吧,微软员工看到都会监介的
        29
    guanzhangzhang   182 天前
    @masker 18 楼这哥们我见过,其他群里也在那吹 powershell
        30
    catcalse   182 天前
    直接 sed 完事了。为啥要 cat 下。。。为啥要 grep。这个是伪需求。。。
        31
    newmind   182 天前 via Android
    这难道不是常用的清空文件内容的方法的一种😄
        32
    snoopygao   182 天前
    这命令让我想起了人体蜈蚣
        33
    msg7086   182 天前
    @guog @masker 之前我还在犹豫要不要 block,现在发现无需犹豫了~ 顺便 @Livid 一下。
        34
    scriptB0y   182 天前
    @msg7086 说的这个过程,其实楼主可以用 strace 自己验证一下

    strace -f sh -c "cat file.txt | grep xxx | sed xxx > file.txt " 可以看到整个过程的。
        35
    siteshen   182 天前
    本来用例 1 想质疑 #3 @vuuv 的答案,然而重读一遍后,又用例 2 推翻了我的质疑。

    cat hello.txt | grep a | (sleep 1; cat > hello.txt) # 例 1:文件不会被清空
    cat hello.txt | (sleep 1; grep a) | cat > hello.txt # 例 2:文件会被清空
        36
    FrankHB   182 天前
    @ps1aniuge
    你似乎不知道啥叫“规范”。
    POSIX shell 是屑,bash 有 private extensions,不妨碍 POSIX 标准化了带有 I/O redirection 的 shell command language。
    相比之下,ps1 的设计好上那么一丢丢,也改不掉 M$的写 spec 落后的现状。ECMA-262 还落后 VC#几年呢……你指望 ps1 能规范就猴年马月了。
    另外,所谓屏幕输出也是你自以为是的笑话,尽管偶然符合事实。tee 命令操作的是标准输入和标准输出。在 UNIX 的万物皆文件的邪教下,对应文件还真没错。下面那个叫你 RTFM 的咣的还真是时候。
    另外你漏了 POSIX.1 标准化的 tee 命令还有一个破事:禁止忽略错误。
        37
    FrankHB   182 天前
    ok,婊 js 魔怔了,262→334 ……
        38
    vuuv   181 天前 via Android
    @siteshen #35 文件真的没有被清空吗?看下文件的修改时间?
    你写的的 hello.txt 是不是每行都恰好包含字母 a 呢?加一行不含字母 a 的内容试试?

    例 1 里的圆括号“()”标记会 fork 一个 shell (暂称为子 shell )来执行。于是命令等价为这样的:
    cat hello.txt | grep a | bash -c "sleep 1; cat > hello.txt"

    如果没有 sleep 1,那么会立刻在子 shell 里发生文件清空。不过此时 cat 和子 shell 是同时 fork 的,而且子 shell 启动后的初始化及对命令的语法解析会花费一些时间(也就几十毫秒而已)。
    如果 hello.txt 文件较小,等到子 shell 开始奉命清空文件时,cat 是有充足的时间读到文件全部内容的。如果文件超出了缓冲区大小(缓冲区默认是 4k,不过程序可以设置其他大小,内核也可能会多预读点内容。),就不保证 cat 能读到正确的内容了。

    所以一些软件系统会设计为“对关键文件的修改加锁”,就是为了防止多个进程同时修改某个文件。
    典型的代表就是 yum 的 install 子命令。
        39
    ps1aniuge   181 天前
    @masker
    v2 是高智商、程序员的技术社区。
    而你这智商,明显差强任意。拉低了社区平均值。

    什么贬低他人 ? “他人”是指谁? 技术和人你都分不清楚么?
        40
    james122333   180 天前
    pipe 能实现的 导向都能实现 多写就了解了
    这需求 shell 完全能实现 据我看到的所有写 pipe 一行流的大型应用都写的很丑 举例:steam 的启动脚本
    shell 是能写的优雅好维护的 精随少人在讲而已...
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2637 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 39ms · UTC 00:46 · PVG 08:46 · LAX 16:46 · JFK 19:46
    ♥ Do have faith in what you're doing.