Maneira elegante de construir um pipeline com base no valor de retorno e não no código de saída?


8

Quando o código de status é inútil, existe alguma maneira de construir um pipeline com base na saída do stdout?

Eu preferiria que a resposta não abordasse o caso de uso, mas a pergunta no escopo do script de shell. O que estou tentando fazer é encontrar o pacote mais específico disponível no repositório, adivinhando o nome com base nos códigos de país e idioma.

Tomemos, por exemplo, isso,

  • $PACKAGE1=hunspell-en-zz
  • $PACKAGE2=hunspell-en

O primeiro palpite é mais apropriado, mas pode não existir. Nesse caso, desejo return hunspell-en( $PACKAGE2) porque a primeira opção hunspell-en-zz( $PACKAGE1) não existe.

pipelines do apt-cache

O comando apt-cacheretorna sucesso (que é definido pelo shell como código de saída zero) sempre que o comando é capaz de executar (a partir dos documentos de apt-cache)

apt-cache retorna zero em operação normal, decimal 100 em erro.

Isso dificulta o uso do comando em um pipeline. Normalmente, espero que o equivalente à pesquisa de pacotes de um 404 resulte em um erro (como aconteceria com curlou wget). Quero pesquisar para ver se existe um pacote e, se não, voltar para outro pacote, se existir .

Isso não retorna nada, pois o primeiro comando retorna com êxito (então o rhs no ||nunca é executado)

apt-cache search hunspell-en-zz || apt-cache search hunspell-en

apt-cache search com dois argumentos

Isso não retorna nada, pois apt-cacheANDs seus argumentos,

apt-cache search hunspell-en-zz hunspell-en

Dos documentos de apt-cache

Argumentos separados podem ser usados ​​para especificar vários padrões de pesquisa que são juntos.

Portanto, como um desses argumentos claramente não existe, isso não retorna nada.

A questão

Qual é o idioma do shell para lidar com convenções como as encontradas em apt-cacheonde o código de retorno é inútil para a tarefa? E o sucesso é determinado apenas pela presença de saída no STDOUT?

Igual a

  • fazer encontrar falhar quando nada foi encontrado

    ambos decorrem do mesmo problema. A resposta escolhida aqui menciona find -zque infelizmente não é a solução aplicável aqui e é específica para casos de uso. Não há menção de um idioma ou construção de um pipeline sem o uso de terminação nula (não é uma opção ativada apt-cache)


Você tem certeza de que hunspell-enexiste? De qualquer forma, você pode usar apt-cache policye grep para ^$PACKAGENAME:.
26417 AlexP

@AlexP, estes são apenas exemplos. O hunspell-en não existe porque eles empacotam com nomes de países, hunspell-arexistem e não existem pacotes com nomes de países . Preciso encontrar o pacote mais preciso para um determinado país e idioma.
Evan Carroll

2
findé exatamente a apt-cacheesse respeito - código de retorno inútil, o sucesso é baseado na saída.
muru

1
Sim, concordo que ambos são decorrentes do mesmo problema. A resposta escolhida menciona -zque, infelizmente, não é uma solução aqui, portanto o problema específico do caso de uso não é aplicável. E não há menção de um idioma ou a construção de um pipeline sem o uso de terminação nula (não opção apt-cache)
Evan Carroll

1
@EvanCarroll, a rescisão nula é totalmente opcional. Eu o usei apenas porque é a maneira mais segura de lidar com nomes de arquivos, portanto, é de se esperar findque eles sejam usados -print0e cumprimentados -z. Como o apt-cache não está fornecendo saída terminada em nulo, você não precisa -z.
Muru

Respostas:


5

Crie uma função que aceite um comando e retorne true se houver alguma saída.

r() { local x=$("$@"); [ -n "$x" ] && echo "$x"; }

( ( r echo -n ) || echo 'nada' ) | cat      # Prints 'nada'
( ( r echo -n foo ) || echo 'nada' ) | cat  # Prints 'foo'

Portanto, para este caso de uso, funcionará assim,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en

Observe que r printf '\n\n\n'retornaria falso. Com conchas diferentes de zsh, r printf '\0\0\0'também retornaria falso. O mesmo aconteceria r printf '\0a\0b\0c'com algumas conchas.
Stéphane Chazelas

3

Até onde eu sei, não existe uma maneira padrão de lidar com os casos em que o sucesso de um comando é determinado pela presença de saída. Você pode escrever soluções alternativas, no entanto.

Por exemplo, você pode salvar a saída do comando em uma variável e, em seguida, verificar se essa variável está vazia ou não:

output="$(command)"

if [[ -n "${output}" ]]; then
  # Code to execute if command succeded
else
  # Code to execute if command failed
fi

Eu acho que isso responde à pergunta de uma maneira geral, mas se falamos sobre apt-cache searchalgumas soluções, vem à minha mente.

Eu tenho um script que facilita o gerenciamento de pacotes. Algumas de suas funções são estas:

search() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | cut -d ' ' -f '1' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_all() {
  local 'package'
  for package; do
    apt-cache search "${package}" | sort
  done
}


search_description() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_names_only() {
  local 'package'
  for package; do
    apt-cache search --names-only "${package}" | sort
  done
}

Isso permite fazer várias pesquisas em um único comando. Por exemplo:

$ search hunspell-en-zz hunspell-en
hunspell-en-au
hunspell-en-ca
hunspell-en-gb
hunspell-en-med
hunspell-en-us
hunspell-en-za

Todas as funções pesquisam o banco de dados de uma maneira diferente, portanto os resultados podem variar dependendo da função que você usa:

$ search gnome | wc -l
538
$ search_all gnome | wc -l
1322
$ search_description gnome | wc -l
822
$ search_names_only gnome | wc -l
550

2

Eu não chamaria isso de elegante, mas acho que pode fazer o trabalho:

search_packages () {
    local packages=($@)
    local results=()
    for package in "${packages[@]}"; do
        results=($(apt-cache -n search "$package"))
        if [[ "${#results[@]}" -eq 0 ]]; then
            echo "$package not found."
        elif [[ "${#results[@]}" -eq 1 ]]; then
            do stuff with "$package"
        else
            echo "Warning! Found multiple packages for ${package}:"
            printf '\t-> %s\n' "${results[@]}"
        fi
    done
}

Infelizmente não tenho uma máquina debian para testar. Incluí a -nopção "somente nomes" apt-cachepara tentar limitar os resultados da pesquisa, pois parece que você tem quase certeza do que está pesquisando.

Pode ser executado como:

$ search_packages hunspell-en-zz hunspell-en
$ my_packages=('hunspell-en-zz' 'hunspell-en')
$ search_packages "${my_packages[@]}"

1
Isso é exatamente o que eu estava pensando em fazer, mas estava procurando algo um pouco mais elegante, então vamos ver se alguém tem mais alguma coisa inteligente (como uma solução mais abstrata do caso de uso), se não, vou marcar como escolhido.
Evan Carroll

1
Idealmente, o apt-cache retornaria algo menos estúpido.
Evan Carroll

1
@EvanCarroll, Você já tentou mexer com a -qopção silenciosa? A página de manual não é muito detalhada, mas talvez mude os valores de retorno?
jesse_b

1
ainda retorna 0. = (
Evan Carroll

2

Muru esclareceu que nos comentários grepretornará um status 1 se não houver entrada. Assim, você pode adicionar grep .ao fluxo e, se não houver entrada para corresponder ao padrão ., ele mudará o código de status:

( ( echo -n | grep . ) || echo 'nada' ) | cat      # prints 'nada'
( ( echo -n foo | grep . ) || echo 'nada' ) | cat  # prints 'foo'

Para o caso de uso que se parece com isso. No abaixo, não há, -pl-plentão ele volta e retornahunspell-pl

apt-cache search hunspell-pl-pl | grep . || apt-cache search hunspell-pl

Ou,

apt-cache search hunspell-en-US | grep . || apt-cache search hunspell-en

Existe um -en-USpor isso retorna hunspell-en-us.

Veja também,


grep .retorna true se a entrada contiver pelo menos uma linha (totalmente delimitada com algumas implementações) que contenha pelo menos um caractere (bem formado com a maioria das implementações) e, caso contrário, removerá as linhas vazias. grep '^'funcionaria melhor para verificar se há alguma saída, embora com alguma implementação ainda possa retornar false se a entrada for uma linha não delimitada (e poderia remover essa linha, ou com outras implementações, retorne true, mas adicione a nova linha ausente). Algumas implementações grep também engasgam com o caráter NUL.
Stéphane Chazelas

2

Você pode definir um:

has_output() {
  LC_ALL=C awk '1;END{exit!NR}'
}

E depois:

if cmd | has_output; then
  echo cmd did produce some output
fi

Algumas awkimplementações podem engasgar com caracteres NUL na entrada.

Ao contrário grep '^', seria garantido que o acima funcione em uma entrada que não termine com um caractere de nova linha, mas adicione a nova linha ausente.

Para evitar isso e ser portátil para sistemas onde o awkNUL é bloqueado, você pode usar perl:

has_output() {
  perl -pe '}{exit!$.'
}

Com perl, você também pode definir uma variante que lida com arquivos arbitrários com mais facilidade:

has_output() {
  PERLIO=:unix perl -pe 'BEGIN{$/=\65536} END{exit!$.}'
}

Isso limita o uso de memória (como arquivos que não têm caracteres de nova linha, como arquivos grandes e esparsos).

Você também pode criar variantes como:

has_at_least_one_non_empty_line() {
  LC_ALL=C awk '$0 != "" {n++};1; END{exit!n}'
}

ou:

has_at_least_one_non_blank_line() {
  awk 'NF {n++};1; END{exit!n}'
}

(tenha em atenção que a definição de espaço em branco varia entre awkimplementações, algumas onde estão limitadas a espaço e tabulação, outras onde também inclui caracteres de espaçamento vertical ASCII como CR ou FF, outras onde considera os espaços em branco da localidade)

Idealmente, no Linux, você desejaria usar a splice()chamada do sistema para maximizar o desempenho. Não conheço um comando que o exponha, mas você sempre pode usar python's ctypes:

has_output() {
  python -c 'if 1:
    from ctypes import *
    import sys
    l = CDLL("libc.so.6")
    ret = 1
    while l.splice(0,0,1,0,65536,0) > 0:
      ret = 0
    sys.exit(ret)'
}

(observe que o has_outputstdin ou stdout (ou ambos) deve ser um canal para o splice()trabalho).


0

Eu sugeriria usar funções internas muito básicas do shell:

ck_command() { [ -n $("$@") ] ; }

Aqui está o caso de teste mais simples:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Então você pode usá-lo facilmente com a ||construção à qual está acostumado:

ck_command command_1 || ck_command command_2

Essa função simples funcionará como você gostaria com seu apt_cachecomportamento, independentemente do número de argumentos.


Exceto que isso perde STDOUT no processo, ck_command echo 'asdf' | catnão gera nada.
Evan Carroll

2
→ EvanCarroll: isso não estava no seu § "A questão". Para também obter essa conservação de saída, observe a resposta muito elegante e simples de @roaima: unix.stackexchange.com/a/413344/31707 .
dan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.