AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 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

Por que o MacOS sempre anexa a um descritor de arquivo redirecionado mesmo quando é instruído a sobrescrever? O Ubuntu só anexa quando é instruído a anexar estritamente

  • 772

Dado o seguinte código:

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"

No Ubuntu isso produz:

2$

No MacOS isso gera:

1$
2$

Ao anexar explicitamente, eles se comportam de forma consistente:

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"

No MacOS e no Ubuntu isso gera:

1$
2$

O exemplo mais confuso para mim é este:

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>&-

Que no MacOS gera:

0$
1$
2$
3$
4$

Que no Ubuntu gera:

4$

Eu esperava isso no Ubuntu:

0$
2$
4$

Estou completamente confuso sobre o porquê desse comportamento ocorrer neste exemplo e em todos os outros exemplos que criei para ilustrar essa discrepância.

Minhas perguntas:

  • O que é essa discrepância? O que está acontecendo? Essa discrepância é intencional?
  • Onde mais essa discrepância se aplica? Quais são suas origens?
  • Se essa discrepância é intencional, por que foi justificada? Qual deveria ser o comportamento correto?
  • O que pode ser feito para atenuar essas diferenças ao escrever scripts entre sistemas operacionais?
  • É shopt -o noclobbera resposta apropriada? É essa a verdadeira necessidade de noclobber?
linux
  • 1 1 respostas
  • 1353 Views

1 respostas

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

    Então, em

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

    alguém poderia realmente esperar que o redirecionamento > /dev/stdoutnão fizesse nada, já que o que >faz é redirecionar stdout , e redirecionar stdout para stdout realmente parece ser uma operação nula. Então parece que deveria ser o mesmo que isto:

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

    onde ambas as cópias de printf herdam fds que apontam para a mesma descrição de arquivo aberto (OFD), e como tal têm uma posição de gravação compartilhada. O que significa que as gravações da segunda começam de onde a primeira parou e a saída é gravada no arquivo em ordem natural.

    Ou seja, na maioria dos sistemas, a abertura /dev/stdoutatua como uma chamada dup()ao stdout (fd 1).

    Mas esse não é o caso no Linux! Em vez disso, no Linux, a abertura /dev/stdout(ou qualquer uma das similares) encontra o arquivo subjacente e o abre novamente. O novo descritor de arquivo (fd) aponta para um novo OFD, com a posição de leitura/gravação e o modo de gravação (anexar vs. normal) independente do original. E ele trunca o arquivo se estiver usando >.

    Então acima, o shell abre outputfilepara as chaves, obtém um fd apontando para um OFD com posição de gravação 0, truncando o arquivo. Então outputfileé aberto novamente para o primeiro printf, novamente na posição de gravação 0; e então outputfileé aberto novamente para o segundo printf, novamente na posição de gravação 0, truncando-o. A saída escrita pelo primeiro printf é perdida e a saída do segundo printf é tudo o que resta.

    É o mesmo que

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

    Quando você faz

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

    em vez disso, o OFD para o segundo printf é aberto no modo append (não truncando), o que significa que todas as gravações através dele sempre vão para o final do arquivo.


    Da mesma forma, aqui:

    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
    

    o shell abre outfilena posição de gravação 0 no modo append para o exec; então ele abre outfilenovamente através de /dev/stdout, no Linux truncando o arquivo e obtendo um OFD na posição de gravação 0 no modo normal não append, então faz isso de novo, depois de novo, depois de novo. As gravações de todos, exceto o último printf, são perdidas.

    Em outros sistemas operacionais, isso ocorre novamente sem os > /dev/stdoutredirecionamentos, e todos os dados são gravados em ordem no arquivo.


    Se você costuma 1<> /dev/stdoutsolicitar uma abertura de leitura e gravação não truncada, você verá parte dos dados sendo substituídos desde o início:

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

    no Linux, que resulta em outfileconter

    foo
    3
    4
    5
    

    (No macOS, isso requer que o redirecionamento externo também solicite uma abertura de leitura e gravação (ou seja, 1<> outfile), o que é bastante sensato, já que você não pode transformar um OFD somente de gravação em um de leitura e gravação sem reabrir.)


    Essa é a discrepância. Eu esperaria que fosse apenas um acidente histórico do Linux sempre ter feito isso por alguma razão, e agora não pode ser alterado. Não sei ao certo, no entanto.

    Qual é o comportamento correto? Bem, se um SO faz X e muitos outros fazem Y, pode-se dizer que Y é pelo menos uma maneira mais aceita. Mas não há autoridade para decidir em geral o que vários SOs podem fazer. AFAIU, POSIX apenas codifica o que o consenso já é, e não menciona /dev/stdoute tal, possivelmente por causa de variações como esta... (De todos os Unix-likes, não tenho ideia se algum outro também faz algo diferente wrt. /dev/stdout. Mas eu entendo que pelo menos os BSDs estão alinhados entre si, com o Linux sendo diferente.)

    O que pode ser feito para atenuar essas diferenças ao escrever scripts entre sistemas operacionais?

    Isso depende do que você está tentando alcançar (e você não disse), mas, para começar, não use /dev/stdout.

    Especialmente naquele primeiro exemplo, o redirecionamento para /dev/stdouté redundante, como mencionado acima. Se você quiser escrever para o mesmo stdout, apenas descarte o redirecionamento. Se você quiser abrir o arquivo novamente, faça isso em vez disso:

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

    Ou, talvez o caso mais provável seja que você tenha um programa que requer um nome de arquivo como saída (e não suporta, por exemplo, -stdout) e, portanto, tem que usar /dev/stdout. Então, use-o apenas para escrever em um pipe ou algo assim. Pipes não podem ser procurados, então não têm uma posição de escrita, e o modo append vs. normal não faz diferença quando tudo vai para o fim do pipe de qualquer maneira.

    Então isso:

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

    deve funcionar da mesma forma no Linux como no macOS e outros.

    Ou com gatos individuais (bobo aqui, mas talvez útil em alguns casos):

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

relate perguntas

  • Existe uma maneira de fazer ls mostrar arquivos ocultos apenas para determinados diretórios?

  • Inicie/pare o serviço systemd usando o atalho de teclado [fechado]

  • Necessidade de algumas chamadas de sistema

  • astyle não altera a formatação do arquivo de origem

  • Passe o sistema de arquivos raiz por rótulo para o kernel do Linux

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

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

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve