¿Forma elegante de construir una tubería basada en el valor de retorno y no en el código de salida?

8

Cuando el código de estado es inútil, ¿hay alguna forma de construir una tubería basada en la salida de stdout?

Prefiero que la respuesta no aborde el caso de uso, sino la pregunta en el ámbito de las secuencias de comandos de shell. Lo que intento hacer es encontrar el paquete más específico disponible en el repositorio adivinando el nombre en función de los códigos de país e idioma.

Toma por ejemplo esto,

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

La primera suposición es más apropiada pero puede no existir. En este caso, quiero devolver hunspell-en( $PACKAGE2) porque la primera opción hunspell-en-zz( $PACKAGE1) no existe.

tuberías de apt-cache

El comando apt-cachedevuelve el éxito (que se define como shell como código de salida cero) siempre que el comando pueda ejecutarse (desde los documentos de apt-cache)

apt-cache devuelve cero en operación normal, decimal 100 en error.

Eso hace que usar el comando en una tubería sea más difícil. Normalmente, espero que el equivalente de búsqueda de paquetes de un 404 produzca un error (como sucedería con curlo wget). Quiero buscar para ver si existe un paquete, y si no, recurrir a otro paquete si existe .

Esto no devuelve nada, ya que el primer comando devuelve el éxito (por lo que el rhs ||nunca se ejecuta)

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

apt-cache search con dos argumentos

Esto no devuelve nada, ya que apt-cachesus argumentos Y

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

De los documentos de apt-cache

Se pueden usar argumentos separados para especificar múltiples patrones de búsqueda que se unen.

Entonces, como uno de esos argumentos claramente no existe, esto no devuelve nada.

La pregunta

¿Cuál es el idioma de shell para manejar convenciones como las que se encuentran en las apt-cacheque el código de retorno es inútil para la tarea? ¿Y el éxito está determinado solo por la presencia de salida en STDOUT?

Similar a

  • hacer encontrar falla cuando no se encontró nada

    Ambos provienen del mismo problema. La respuesta elegida allí menciona find -zque lamentablemente no es una solución aplicable aquí y es específica del caso de uso. No se menciona una expresión idiomática o la construcción de una tubería sin usar terminación nula (no es una opción activada apt-cache)

Evan Carroll
fuente
¿Estás seguro de que hunspell-enexiste? De todos modos, puedes usar apt-cache policyy grep para ^$PACKAGENAME:.
AlexP
@AlexP estos son solo ejemplos hunspell-en no existe porque se empaquetan con nombres de países, hunspell-arexisten y no hay paquetes de nombres de países. Necesito encontrar el paquete más preciso para un país e idioma determinados.
Evan Carroll
2
findes como apt-cachea este respecto: código de retorno inútil, el éxito se basa en la salida.
muru
1
Sí, estoy de acuerdo en que ambos provienen del mismo problema. La respuesta elegida menciona -zque, lamentablemente, no es una solución aquí, por lo que el problema específico del caso de uso no es aplicable. Y no se menciona una expresión idiomática o la construcción de una tubería sin usar terminación nula (no es una opción apt-cache)
Evan Carroll
1
@EvanCarroll la terminación nula es completamente opcional. Solo lo usé porque es la forma más segura de lidiar con los nombres de archivos, por lo que uno esperaría findser utilizado -print0y muy útil -z. Dado que apt-cache no está dando salida terminada en nulo, no es necesario -z.
muru

Respuestas:

5

Cree una función que tome un comando y devuelva verdadero si tiene algún resultado.

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

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

Entonces para este caso de uso funcionará así,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en
roaima
fuente
Tenga en cuenta que r printf '\n\n\n'devolvería falso. Con conchas que no sean zsh, r printf '\0\0\0'también devolvería falso. Lo mismo haría r printf '\0a\0b\0c'con algunos proyectiles.
Stéphane Chazelas
3

Hasta donde yo sé, no hay una forma estándar de lidiar con aquellos casos en los que el éxito de un comando está determinado por la presencia de salida. Sin embargo, puede escribir soluciones alternativas.

Por ejemplo, puede guardar la salida del comando en una variable y luego verificar si esa variable está vacía o no:

output="$(command)"

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

Creo que esto responde a la pregunta de manera general, pero si hablamos de apt-cache searchalgunas soluciones me vienen a la mente.

Tengo un script que facilita la gestión de paquetes. Algunas de sus funciones son 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
}

Estos le permiten realizar múltiples búsquedas en un solo comando. Por ejemplo:

$ 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 función busca en la base de datos de manera diferente, por lo que los resultados pueden variar según la función que utilice:

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

No lo llamaría elegante, pero creo que podría hacer el trabajo:

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
}

Lamentablemente, no tengo una máquina Debian para probar. He incluido la -nopción de "solo nombres" apt-cachepara tratar de limitar los resultados de búsqueda, ya que parece que está casi seguro de lo que está buscando.

Se puede ejecutar como:

$ search_packages hunspell-en-zz hunspell-en
$ my_packages=('hunspell-en-zz' 'hunspell-en')
$ search_packages "${my_packages[@]}"
jesse_b
fuente
1
Esto es exactamente lo que estaba pensando hacer, sin embargo, estaba buscando algo un poco más elegante, así que veamos si alguien tiene algo más inteligente (como una solución más abstracta fuera del caso de uso), si no, marcaré como lo eligió
Evan Carroll
1
Idealmente, apt-cache devolvería algo menos estúpido.
Evan Carroll
1
@EvanCarroll, ¿Has intentado jugar con la -qopción silenciosa? La página del manual no es muy detallada pero ¿cambia los valores de retorno?
jesse_b
1
todavía devuelve 0. = (
Evan Carroll
2

Muru aclaró que esto en los comentarios grepdevolverá un estado de 1 si no hay entrada. Por lo tanto, puede agregar grep .a la secuencia y si no hay entrada para que coincida con el patrón ., cambiará el código de estado:

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

Para el caso de uso que se ve así. En el siguiente, no hay, -pl-plpor lo que retrocede y vuelvehunspell-pl

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

O,

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

Hay un -en-USpor lo que vuelve hunspell-en-us.

Ver también,

Evan Carroll
fuente
grep .devuelve verdadero si la entrada contiene al menos una línea (completamente delimitada con algunas implementaciones) que contiene al menos un carácter (bien formado con la mayoría de las implementaciones) y, de lo contrario, eliminará las líneas vacías. grep '^'funcionaría mejor para verificar que hay algo de salida, aunque con alguna implementación aún podría devolver falso si la entrada es una línea no delimitada (y podría eliminar esa línea, o con otras implementaciones, devolver verdadero pero agregar la nueva línea faltante). Algunas implementaciones grep también se ahogan con el carácter NUL.
Stéphane Chazelas
2

Podrías definir un:

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

Y entonces:

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

Algunas awkimplementaciones pueden ahogarse con los caracteres NUL en la entrada.

Al contrario grep '^', se garantizaría que lo anterior funcione en una entrada que no termine en un carácter de nueva línea, pero agregaría la nueva línea faltante.

Para evitar eso y ser portátil a sistemas donde se awkahoga NUL, puede usar perlen su lugar:

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

Con perl, también podría definir una variante que maneje archivos arbitrarios con más gracia:

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

Eso limita el uso de memoria (como para archivos que no tienen caracteres de nueva línea como archivos grandes y dispersos).

También puede crear variantes como:

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

o:

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

(tenga en cuenta que la definición de espacio en blanco varía entre las awkimplementaciones, algunas donde está limitado al espacio y la pestaña, otras donde también incluye caracteres de espaciado vertical ASCII como CR o FF, algunas donde considera los espacios en blanco de la configuración regional)

Idealmente, en Linux, desearía utilizar la splice()llamada del sistema para maximizar el rendimiento. No conozco un comando que lo exponga, pero siempre puedes 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)'
}

(tenga en cuenta que has_outputel stdin o stdout (o ambos) tiene que ser una tubería para splice()que funcione).

Stéphane Chazelas
fuente
0

Sugeriría utilizar funciones integradas muy básicas del shell:

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

Aquí está el caso de prueba más simple:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Entonces podría usarlo fácilmente con la ||construcción a la que está acostumbrado:

ck_command command_1 || ck_command command_2

Esta función simple funcionará como desee con su apt_cachecomportamiento, sea cual sea el número de argumentos.

dan
fuente
Excepto que esto pierde STDOUT en el proceso, ck_command echo 'asdf' | catno genera nada.
Evan Carroll
2
→ EvanCarroll: esto no estaba en tu § "La pregunta". Para lograr también esta conservación de salida, mire la respuesta muy elegante y simple de @roaima: unix.stackexchange.com/a/413344/31707 .
dan