AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 790936
Accepted
balupton
balupton
Asked: 2025-02-12 04:17:31 +0800 CST2025-02-12 04:17:31 +0800 CST 2025-02-12 04:17:31 +0800 CST

为什么 MacOS 总是在被告知要覆盖时附加到重定向文件描述符?Ubuntu 仅在严格要求附加时才会附加

  • 772

给出以下代码:

out="$(mktemp)"
rm -f "$out"
clear

printf '%s\n' 0 >"$out"
{
    printf '%s\n' '1' >/dev/stdout
    printf '%s\n' '2' >/dev/stdout
} >"$out"
cat -e -- "$out"
rm -f "$out"

在 Ubuntu 上输出:

2$

在 MacOS 上输出:

1$
2$

当明确附加时,它们的行为一致:

out="$(mktemp)"
rm -f "$out"
clear

printf '%s\n' 0 >"$out"
{
    printf '%s\n' '1' >/dev/stdout
    printf '%s\n' '2' >>/dev/stdout
} >"$out"
cat -e -- "$out"
rm -f "$out"

在 MacOS 和 Ubuntu 上输出:

1$
2$

对我来说最令人困惑的例子是这个:

out="$(mktemp)"
rm -f "$out"
clear

printf '%s\n' 0 >"$out"
exec 3>>"$out"
{
    printf '%s\n' '1' >/dev/stdout
    printf '%s\n' '2' >/dev/stdout
} >&3
{
    printf '%s\n' '3' >/dev/stdout
    printf '%s\n' '4' >/dev/stdout
} >&3
cat -e -- "$out"
rm -f "$out"
exec 3>&-

在 MacOS 上输出:

0$
1$
2$
3$
4$

在 Ubuntu 上输出:

4$

我期望在 Ubuntu 上实现这一点:

0$
2$
4$

我完全搞不清楚为什么会出现这种行为,在这个例子中,以及在我设计的所有其他例子中,都出现了这种行为,以说明这种差异。

我的问题:

  • 这种差异是什么?发生了什么?这种差异是故意的吗?
  • 这种差异还适用于哪些地方?其根源是什么?
  • 如果这种差异是故意的,那么为什么要这样做呢?哪一个应该是正确的行为?
  • 在编写跨操作系统脚本时,如何才能减轻这些差异?
  • 这是shopt -o noclobber适当的回应吗?这是真正的必要吗noclobber?
linux
  • 1 1 个回答
  • 1353 Views

1 个回答

  • Voted
  1. Best Answer
    ilkkachu
    2025-02-12T14:02:17+08:002025-02-12T14:02:17+08:00

    因此,在

    {
        printf '%s\n' '1' >/dev/stdout
        printf '%s\n' '2' >/dev/stdout
    } > outputfile
    

    人们可能实际上希望重定向> /dev/stdout不执行任何操作,因为它>所做的就是重定向stdout,而将stdout重定向到stdout似乎真的是一个无操作。因此看起来应该与此相同:

    {
        printf '%s\n' '1'
        printf '%s\n' '2'
    } > outputfile
    

    printf 的两个副本都继承了指向相同打开文件描述(OFD) 的 fd,因此具有共享的写入位置。这意味着第二个副本的写入从第一个副本离开的位置开始,并且输出按自然顺序写入文件。

    也就是说,在大多数系统上,打开/dev/stdout就像调用dup()stdout (fd 1)。

    但在 Linux 上情况并非如此!相反,在 Linux 上,打开/dev/stdout(或任何类似的操作)会找到底层文件,然后重新打开它。新的文件描述符 (fd) 指向一个新的 OFD,其读/写位置和写入模式(追加还是正常)与原始文件无关。如果使用 ,它会截断文件>。

    因此,上面的 shell 打开outputfile大括号,获取指向写入位置为 0 的 OFD 的 fd,截断文件。然后outputfile再次打开第一个printf,再次在写入位置 0;然后outputfile再次打开第二个printf,再次在写入位置 0,截断它。第一个 printf 写入的输出丢失,第二个 printf 的输出是剩下的全部内容。

    和

    {
        printf '%s\n' '1' > outputfile
        printf '%s\n' '2' > outputfile
    } > outputfile
    

    当你这样做

    {
        printf '%s\n' '1' >/dev/stdout
        printf '%s\n' '2' >>/dev/stdout
    } > outfile
    

    相反,第二个 printf 的 OFD 以追加模式(而不是截断)打开,这意味着通过它的所有写入总是到达文件末尾。


    同样地,这里:

    exec 3>> outfile
    {
        printf '%s\n' '1' >/dev/stdout
        printf '%s\n' '2' >/dev/stdout
    } >&3
    {
        printf '%s\n' '3' >/dev/stdout
        printf '%s\n' '4' >/dev/stdout
    } >&3
    

    shelloutfile在写入位置 0 处以追加模式打开,以便执行 exec;然后outfile通过 重新打开/dev/stdout,在 Linux 上截断文件并在写入位置 0 处以正常非追加模式获取 OFD,然后重复此操作,然后重复,然后重复。除了最后一个 printf 之外,所有写入均会丢失。

    在其他操作系统上,这与没有重定向的情况相同> /dev/stdout,所有数据都会按顺序写入文件。


    如果您1<> /dev/stdout要求进行非截断读写打开,您将看到部分数据从一开始就被覆盖:

    {
        printf "%s\n" 1 2 3 4 5
        printf "foo\n" 1<> /dev/stdout
    } > outfile
    

    在 Linux 上,结果outfile包含

    foo
    3
    4
    5
    

    (在 macOS 上,这需要外部重定向也请求读写打开(即1<> outfile),这是相当合理的,因为如果不重新打开,就无法将只写 OFD 变成读写 OFD。)


    这就是矛盾之处。我认为这只是 Linux 出于某种原因一直这样做的历史偶然,现在无法改变。不过,我不确定。

    正确的行为是什么?好吧,如果一个操作系统执行 X,而许多其他操作系统执行 Y,那么人们可能会说 Y 至少是一种更可接受的方式。但没有权威来决定各种操作系统可以做什么。据我所知,POSIX 只编纂了已经达成共识的内容,并没有提及/dev/stdout其他内容,可能是因为像这样的变化……(在所有类 Unix 中,我不知道其他系统是否也做了不同的事情。/dev/stdout但我知道至少 BSD 是一致的,而 Linux 则不同。)

    在编写跨操作系统脚本时,如何才能减轻这些差异?

    这取决于您想要实现的目标(并且您没有说),但首先,不要使用/dev/stdout。

    /dev/stdout尤其是在第一个例子中,如上所述,重定向到是多余的。如果您想写入相同的 stdout,只需删除重定向。如果您想重新打开文件,只需执行以下操作:

    {
        printf '%s\n' '1' >outputfile
        printf '%s\n' '2' >outputfile
    } > outputfile
    

    或者,更可能的情况是,你有一个程序需要文件名作为输出(并且不支持例如-stdout),因此必须使用/dev/stdout。然后,只使用它来写入管道或类似的东西。管道无法定位,因此没有写入位置,并且当所有内容都只是到达管道末尾时,追加模式与正常模式没有区别。

    因此:

    writeto() {
        # write to a file named on the command line
        printf "hello\n" > "$1"
    }
    
    {
        writeto /dev/stdout
        writeto /dev/stdout
    } | cat > outputfile
    

    在 Linux 中的工作方式应该与在 macOS 和其他系统中相同。

    或者针对单独的猫(这里很傻,但在某些情况下可能有用):

    {
        writeto /dev/stdout |cat
        writeto /dev/stdout |cat
    } > outputfile
    
    • 18

相关问题

  • 有没有办法让 ls 只显示某些目录的隐藏文件?

  • 使用键盘快捷键启动/停止 systemd 服务 [关闭]

  • 需要一些系统调用

  • astyle 不会更改源文件格式

  • 通过标签将根文件系统传递给linux内核

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve