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 / 782524
Accepted
Alice M.
Alice M.
Asked: 2024-08-25 22:51:23 +0800 CST2024-08-25 22:51:23 +0800 CST 2024-08-25 22:51:23 +0800 CST

Resultados inconsistentes de “unzip -l … | grep -q …” com pipefail

  • 772

A seguinte função Bash deu resultados inconsistentes:

# $1    Path to ZIP archive.
# Exits with 0 status iff it contains a “.mp3” or “.flac” file.
mp3_or_flac_in_zip() {
    local archive=${1:?No archive given.}
    (
        set -o pipefail
        unzip -l "$archive" | grep -iqE '.\.(flac|mp3)$'
    )
}

Quando executado n vezes seguidas no mesmo ZIP contendo música, ele relatou aleatoriamente que não havia música nele (cerca de 1–5% das vezes, mas variou muito entre os ZIPs).

Mudar para uma variável intermediária em vez de um pipe (com &&instead of set -o pipefailpara ainda ter certeza de unzipque estava funcionando bem) corrigiu as inconsistências:

# $1    Path to ZIP archive.
# Exits with 0 status iff it contains a “.mp3” or “.flac” file.
mp3_or_flac_in_zip() {
    local archive=${1:?No archive given.}
    local listing
    listing=$(unzip -l "$archive") &&
    grep -iqE '.\.(flac|mp3)$' <<< "$listing"
}

Qual poderia ser o problema aí? E há outros contextos em que pipes não são uma ideia tão boa?

bash
  • 3 3 respostas
  • 827 Views

3 respostas

  • Voted
  1. Best Answer
    Alice M.
    2024-08-25T22:51:23+08:002024-08-25T22:51:23+08:00

    Basicamente parece outro caso de https://stackoverflow.com/questions/19120263/why-exit-code-141-with-grep-q , que descobri ao verificar o status de retorno que estava recebendo (141).

    Em algumas execuções, unzipparece ter “tempo suficiente” para terminar seu trabalho, enquanto em outras execuções, ele é morto por grepporque grepencontra rapidamente uma correspondência . Como pipefailestava ligado, isso associa um status de erro a todo o foo | barconjunto de comandos. Conclui-se que é provavelmente possível encontrar problemas semelhantes com praticamente qualquer foo | grep -qcombinado com pipefail.

    As discrepâncias em termos de probabilidade de falha entre os ZIPs podem ser devidas ao número de arquivos nesses ZIPs (listas mais curtas vs. mais longas) e à posição do primeiro arquivo correspondente ao grep nessas listas.

    A foo_output=$(foo)abordagem garante que foosempre haja a oportunidade de terminar seu trabalho sem ser eliminado , mas é claro que isso pode levar a desempenhos mais baixos ( grep -qeliminar o comando de entrada após a primeira partida é intencional e economiza tempo e recursos).

    Compromisso provavelmente fraco

    Ainda use um pipe, mas sem pipefail, e capture stderrpara verificar se está vazio, e assuma que a ausência de erros ou avisos significa que unzipcorreu bem. O principal problema que vejo aí é que pode levar a falhas devido a avisos básicos associados a peculiaridades não fatais de ZIPs específicos, mas isso é, até certo ponto, também o caso com as outras abordagens da questão, eu acho:

    err=$(
        {
            foo | grep -q …
        } 2>&1
    ) &&
    [[ -z $err ]]
    

    (Isso me faz pensar se há maneiras melhores de verificar a presença de tipos específicos de arquivos em um ZIP. Isso parece altamente provável.)

    Editar: Abordagem mais realista

    Conforme apontado por discussões naquela página , a utilidade de pipefailnesse contexto é duvidosa na melhor das hipóteses, pois de qualquer forma uma falha unzipnão produzirá (no stdout) nada que satisfaça o grep. Além disso, se algo desagrada unzip, o motivo provavelmente aparecerá via stderr. E para tornar as coisas ainda mais interessantes, problemas não fatais (que tendem a produzir status diferentes de zero de acordo com man unzip) também não nos impedirão de encontrar arquivos, dessa forma.

    Considerações finais

    Na minha humilde opinião, esse é mais um argumento para dizer que não devemos alternar inúmeras flags Bash o tempo todo sem estar muito cientes das consequências potenciais, mesmo que alguns “modelos de script” multifuncionais anunciem set -o pipefaile set -e(entre outras coisas) como formas de evitar bugs magicamente. Para cada problema que eles evitam, eles geralmente criam o dobro da quantidade de problemas potenciais se você não ficar de olho (e as coisas pioram ainda mais em um ambiente empresarial onde você não pode forçar todos a passar um dia lendo man bash). Eu ainda ocasionalmente caio em advertências como essa depois de anos de script. No caso presente, pensei que usar um ( … )subshell para manter o pipefailefeito o mais local possível me manteria seguro, apenas para tropeçar em um problema dentro do subshell, bem debaixo do meu nariz.

    • 9
  2. Stéphane Chazelas
    2024-08-25T23:51:25+08:002024-08-25T23:51:25+08:00

    Você pode fazer:

    mp3_or_flac_in_archive() (
      archive=${1?No archive given.}
      set -o pipefail
      bsdtar tf - < "$archive" | grep -iE '[^/]\.(flac|mp3)$' > /dev/null
    )
    

    Para grepler toda a entrada (encontrar todas as correspondências e relatá-las, que descartamos, mesmo que só nos importemos se houve pelo menos uma) e evitar bsdtarser morto por um SIGPIPE após grepas saídas após a primeira correspondência.

    Substituí unzippela interface CLI do libarchive , pois não é possível processar caminhos de arquivo arbitrários, pois ele interpreta curingas¹. Isso também significa que podemos processar outros tipos de arquivo.bsdtarunzip

    Movi a archiveatribuição de variáveis ​​para dentro do subshell, então não precisamos do não padrão local, e ${var?error}só sai desse subshell, ou seja, da função, e não do script inteiro.

    E eu substituí .por [^/]para evitar corresponder a path/to/.flaccomo presumo que era sua intenção com isso ..

    Em qualquer caso, se você quiser verificar se unzip/ bsdtare grepforam bem-sucedidos, porque, por exemplo, você quer ser capaz de detectar um arquivo corrompido mesmo que alguns .mp3arquivos possam ser encontrados, então você precisa deixar unzip/ bsdtarser executado até o final, então você precisa consumir toda a sua saída.

    Além de mudar de grep -qpara grep > /dev/null, como uma abordagem mais genérica, você poderia fazer:

    (set -o pipefail
      bsdtar... | (
        grep -q pattern; ret=$?
        cat>/dev/null # consume and discard the remaining output if any
        exit "$ret"
      )
    )
    

    Uma alternativa para obter bsdtar/ unzippara imprimir a lista completa de membros do arquivo e processá-la com grep, você pode usar bsdcpiopara listar apenas os membros que deseja e apenas verificar se há alguma saída:

    mp3_or_flac_in_archive() (
      archive=${1?No archive given.}
      output=$(
        <"$archive" bsdcpio --quiet -it '*[^/].[mM][pP]3' '*[^/].[fF][lL][aA][cC]'
      ) && [ -n "$output" ]
    )
    

    Ou:

    mp3_or_flac_in_archive() (
      archive=${1?No archive given.}
      set -o pipefail
      <"$archive" bsdcpio --quiet -it '*[^/].[mM][pP]3' '*[^/].[fF][lL][aA][cC]' |
        awk 'END{exit !NR}'
    )
    

    Agora, um problema com essa abordagem é que ela retornará true mesmo se todos os arquivos que têm um nome terminando em .mp3ou .flacforem do tipo directory . A bsdtar|grepabordagem teria excluído aqueles como na bsdtarlistagem, arquivos do tipo directory aparecem com um /anexado ao seu nome. Há um problema semelhante, independentemente da abordagem com symlinks.


    Observe que, como alternativa a um subshell, desde o bash 4.4, você pode usar local -como no shell Almquish ou como o set -o localoptionsof zshpara que as alterações nas configurações de opções (apenas aquelas definidas por set, não aquelas definidas por shopt) sejam locais para a função:

    mp3_or_flac_in_archive() {
      local - archive=${1?No archive given.}
      set -o pipefail
      ...
    }
    

    O mesmo que o do zsh:

    mp3_or_flac_in_archive() {
      set -o localoptions -o pipefail
      local archive=${1?No archive given.}
      ...
    }
    

    Ou ksh93 (de onde a pipefailopção veio inicialmente):

    function mp3_or_flac_in_archive {
      set -o pipefail
      typeset archive=${1?No archive given.}
      ...
    }
    

    Onde as mudanças nas opções são sempre locais, desde que você use o estilo Korn de definição de função. Observe também que no ksh93, essas mudanças de opções (e esse é o caso da $archivevariável local também) não se propagam para outras funções chamadas dentro, então sua função pode invocar com segurança outras funções que esperam que a pipefailopção esteja desligada sem fazer isso set +o pipefailelas mesmas.


    ¹ Mesmo em sistemas do tipo Unix, onde *, ?são caracteres tão válidos quanto quaisquer outros em um caminho de arquivo e a geração do nome de arquivo deve ser feita pelo shell, unziptambém tenta ler o arquivo com .zipou .ZIPanexado se não puder abrir o arquivo sem e, ao contrário, bsdtarnão oferece suporte a arquivos não pesquisáveis; é mais um programa do MSDOS.

    zipinfo, que geralmente vem com unzip, com sua -1opção também teria sido uma alternativa melhor, embora sofra dos mesmos problemas que unzip.

    • 7
  3. ilkkachu
    2024-08-26T01:33:55+08:002024-08-26T01:33:55+08:00

    Se for importante que o comando do lado esquerdo termine, você pode executar grepsem -qe apenas redirecionar a saída para /dev/null. O status de saída deve ser o mesmo (mas é claro que grep faz algum trabalho desnecessário aqui).

    unzip -l "$archive" | grep -iE '.\.(flac|mp3)$' > /dev/null
    

    Se não importa se o lado esquerdo termina ou é encerrado pelo fechamento do pipe, você pode apenas verificar especificamente o status de saída correspondente ao SIGPIPE e tratá-lo como zero.

    unzip -l "$archive" | grep -q -iE '.\.(flac|mp3)$'
    ret=$?
    [[ $ret == 141 ]] && ret=0
    return "$ret"
    

    No Bash, você também pode verificar a $PIPESTATUSmatriz para verificar qual comando falha e com qual status.

    • 3

relate perguntas

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

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