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 prefiro que a resposta não aborde o caso de uso, mas a questão 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.

Tome por exemplo isso,

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

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

pipelines do apt-cache

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

apt-cache returns zero on normal operation, decimal 100 on error.

Isso torna mais difícil o uso do comando em um pipeline. Normalmente, espero que o equivalente de busca de pacote de um 404 resulte em um erro (como aconteceria com curl ou wget ). Eu quero pesquisar para ver se existe um pacote, e se não for voltar para outro pacote, se existir .

Isso não retorna nada, já que o primeiro comando retorna sucesso (então os rhs no || nunca são executados)

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

apt-cache search com dois argumentos

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

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

Nos documentos de apt-cache

Separate arguments can be used to specify multiple search patterns that are and'ed together.

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

A questão

O que é o idioma do shell para tratar convenções como aquelas encontradas em apt-cache , em que o código de retorno é inútil para a tarefa? E o sucesso é determinado apenas pela presença de saída no STDOUT?

Semelhante a

  • faça com que o find falhe quando nada for encontrado

    they both stemming from the same problem. The chosen answer there mentions find -z which sadly isn't applicable solution here and is use-case specific. There is no mention of an idiom or constructing a pipeline without using null-termination (not an option on apt-cache)

por Evan Carroll 26.12.2017 / 23:46

6 respostas

5

Crie uma função que receba um comando e retorne true se tiver 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'

Então, para este caso de uso, funciona assim,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en
    
por 28.12.2017 / 00:55
4

Até onde sei, não há 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.

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

Acho que isso responde à pergunta de maneira geral, mas se falarmos sobre apt-cache search , algumas soluções vêm à 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
}

Eles permitem que você faça 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

Cada função pesquisa o banco de dados de uma maneira diferente, portanto os resultados podem variar dependendo de qual função 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
    
por 27.12.2017 / 03:08
2

Eu não chamaria isso de elegante, mas acho que poderia 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
}

Eu não tenho uma máquina debian para testar infelizmente. Eu incluí a opção -n para "somente nomes" de apt-cache para tentar limitar os resultados da pesquisa, pois parece que você está mais certo 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[@]}"
    
por 27.12.2017 / 00:42
2

Muru esclareceu isso nos comentários grep retornará um status de 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 alterará 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-pl , então ele retorna e retorna hunspell-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-US , pelo que devolve hunspell-en-us .

Veja também

por 27.12.2017 / 05:01
2

Você pode definir um:

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

E então:

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

Algumas implementações de awk podem sufocar em caracteres NUL na entrada.

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

Para evitar isso e ser portátil para sistemas em que awk bloqueia o NUL, você pode usar perl :

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

Com perl , você também pode definir uma variante que manipule arquivos arbitrários com mais graça:

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

Isso limita o uso de memória (como para arquivos que não possuem 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}'
}

(cuidado com a definição de blank varia entre awk implementations, alguns onde é limitado a espaço e tab, alguns onde também inclui caracteres de espaçamento vertical ASCII como CR ou FF, alguns onde considera os espaços em branco do locale)

O ideal é que, no Linux, você queira usar a chamada do sistema splice() para maximizar o desempenho. Não sei de um comando que exponha isso, mas você sempre pode usar python ' 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 stdin ou stdout de has_output (ou ambos) tem que ser um canal para que splice() funcione).

    
por 04.01.2018 / 22:56
0

Sugiro 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 $?

Você pode facilmente usá-lo com a construção || a que está acostumado:

ck_command command_1 || ck_command command_2

Esta função simples funcionará como você gostaria com seu comportamento apt_cache , qualquer que seja o número de argumentos.

    
por 27.12.2017 / 12:46