Элегантный способ построить конвейер на основе возвращаемого значения, а не кода выхода?


8

Когда код состояния бесполезен, есть ли способ построить конвейер на основе вывода из stdout?

Я бы предпочел, чтобы ответ был не на сценарии использования, а на вопросе сценариев оболочки. Я пытаюсь найти наиболее конкретный пакет, доступный в репозитории, угадав имя на основе кодов страны и языка.

Возьмите, к примеру, это,

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

Первое предположение более уместно, но оно может не существовать. В этом случае я хочу return hunspell-en( $PACKAGE2), потому что первая опция hunspell-en-zz( $PACKAGE1) не существует.

конвейеры apt-кеша

Команда apt-cacheвозвращает успех (который определяется оболочкой как код выхода ноль) всякий раз, когда команда может быть запущена (из документов apt-cache)

apt-cache возвращает ноль при нормальной работе, десятичное 100 при ошибке.

Это делает использование команды в конвейере более сложным. Обычно я ожидаю, что эквивалент 404 при поиске пакета приведет к ошибке (как это случилось бы с curlили wget). Я хочу выполнить поиск, чтобы увидеть, существует ли пакет, и если нет, использовать другой пакет, если он существует .

Это ничего не возвращает, так как первая команда возвращает успех (поэтому rhs в ||никогда не выполняется)

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

apt-cache search с двумя аргументами

Это ничего не возвращает, поскольку apt-cacheANDs его аргументы,

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

Из документов apt-cache

Отдельные аргументы могут использоваться для указания нескольких шаблонов поиска, которые объединяются и объединяются.

Так как один из этих аргументов явно не существует, это ничего не возвращает.

Вопрос

Какова идиома оболочки для обработки соглашений, подобных тем, в apt-cacheкоторых код возврата бесполезен для задачи? А успех определяется только наличием выхода на STDOUT?

Похожий на

  • потерпеть неудачу, когда ничего не было найдено

    они оба вытекают из одной и той же проблемы. Выбранный ответ там упоминает, find -zчто, к сожалению, не применимо решение здесь и является конкретным вариантом использования. Там нет упоминания об идиоме или построении конвейера без использования нулевого завершения (опция не включена apt-cache)


Вы уверены, что hunspell-enсуществует? В любом случае, вы можете использовать apt-cache policyи grep для ^$PACKAGENAME:.
AlexP

@AlexP это только примеры hunspell-en не существует, потому что они упаковывают с названиями стран, hunspell-arсуществуют и пакетов с названиями стран нет. Мне нужно найти наиболее точный пакет для данной страны и языка.
Эван Кэрролл

2
findкак apt-cacheв этом отношении - бесполезный код возврата, успех основан на выводе.
Муру

1
Да, я согласен, что они оба связаны с одной и той же проблемой. В выбранном ответе упоминается, -zчто, к сожалению, это не решение, поэтому проблема, связанная с конкретным случаем, не применима. И нет никакого упоминания об идиоме или построении конвейера без использования нулевого завершения (не apt-cache
Эван Кэрролл

1
@EvanCarroll нулевое окончание является необязательным. Я использовал его только потому, что это самый безопасный способ иметь дело с именами файлов, поэтому можно было бы ожидать, findчто он будет использоваться с -print0и, таким образом, grep с -z. Поскольку apt-cache не дает вывод с нулевым символом в конце, вам это не нужно -z.
Муру

Ответы:


5

Создайте функцию, которая принимает команду и возвращает true, если она имеет какой-либо вывод.

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

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

Так что для этого варианта использования это будет работать так,

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

Обратите внимание, r printf '\n\n\n'что вернет false. С другими оболочками zsh, r printf '\0\0\0'также будет возвращено ложное Так было бы r printf '\0a\0b\0c'с некоторыми снарядами.
Стефан Шазелас

3

Насколько я знаю, не существует стандартного способа справиться с теми случаями, когда успех команды определяется наличием выходных данных. Вы можете написать обходные пути, хотя.

Например, вы можете сохранить выходные данные команды в переменной, а затем проверить, является ли эта переменная пустой или нет:

output="$(command)"

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

Я думаю, что это отвечает на вопрос в общих чертах, но если мы поговорим о apt-cache searchнекоторых решениях, мне в голову.

У меня есть скрипт, который делает управление пакетами проще. Вот некоторые из его функций:

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
}

Они позволяют выполнять несколько поисков в одной команде. Например:

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

Каждая функция выполняет поиск в базе данных по-своему, поэтому результаты могут отличаться в зависимости от используемой вами функции:

$ 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

Я не назвал бы это изящным, но я думаю, что это могло бы сделать работу:

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
}

У меня нет машины Debian для тестирования, к сожалению. Я включил -nопцию «только для имен», apt-cacheчтобы попытаться ограничить результаты поиска, так как похоже, что вы в основном уверены в том, что ищете.

Может быть запущен как:

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

1
Это именно то, о чем я думал, однако я искал что-то более изящное, поэтому давайте посмотрим, есть ли у кого-нибудь что-нибудь умнее (например, более абстрактное решение вне варианта использования), если нет, я отмечу это как выбрано.
Эван Кэрролл

1
В идеале, apt-cache просто возвращает что-то менее глупое.
Эван Кэрролл

1
@EvanCarroll, ты пробовал возиться с -qтихим вариантом? Страница man не очень многословна, но, возможно, она меняет возвращаемые значения?
jesse_b

1
по-прежнему возвращает 0. = (
Эван Кэрролл

2

Муру уточнил это в комментариях grepвернет статус 1, если нет ввода. Таким образом, вы можете добавить grep .в поток, и если нет ввода, соответствующего шаблону ., он изменит код состояния:

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

Для варианта использования, который выглядит следующим образом. В ниже нет, -pl-plпоэтому он отступает и возвращаетсяhunspell-pl

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

Или,

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

Там -en-USтак и возвращается hunspell-en-us.

Смотрите также,


grep .возвращает значение true, если входные данные содержат хотя бы одну (полностью ограниченную некоторыми реализациями) строку, которая содержит хотя бы один символ (хорошо сформированный для большинства реализаций), и в противном случае удалят пустые строки. grep '^'будет лучше работать при проверке наличия выходных данных, хотя в некоторых реализациях может все еще возвращать false, если входные данные представляют собой одну строку без разделителей (и может удалить эту строку или в других реализациях вернуть true, но добавить отсутствующий символ новой строки). Некоторые реализации grep также подавляют символ NUL.
Стефан Шазелас

2

Вы можете определить:

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

А потом:

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

Некоторые awkреализации могут подавить NUL-символы на входе.

В противоположность grep '^'этому вышеописанное гарантированно будет работать с вводом, который не заканчивается символом новой строки, но добавляет недостающий символ новой строки.

Чтобы избежать этого и быть переносимым на системы, где awkзадыхается NUL, вы можете использовать perlвместо этого:

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

С помощью perlвы также можете определить вариант, который обрабатывает произвольные файлы более изящно:

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

Это ограничивает использование памяти (например, для файлов, в которых нет символов новой строки, таких как большие разреженные файлы).

Вы также можете создавать варианты, такие как:

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

или:

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

(имейте в виду, что определение пробела варьируется в зависимости от awkреализации, в некоторых случаях оно ограничено пробелом и табуляцией, в некоторых также включены символы ASCII с вертикальным интервалом, такие как CR или FF, а в некоторых - пробелы локали)

В идеале в Linux вы должны использовать splice()системный вызов для максимизации производительности. Я не знаю команду, которая бы выставляла ее, но вы всегда можете использовать pythons 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)'
}

(обратите внимание, что либо has_outputstdin, либо stdout (или оба) должны быть каналом для splice()работы).


0

Я бы предложил использовать очень простые встроенные функции оболочки:

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

Вот самый простой тестовый пример:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Тогда вы можете легко использовать его с той ||конструкцией, к которой вы привыкли:

ck_command command_1 || ck_command command_2

Эта простая функция будет работать так, как вам хотелось бы с вашим apt_cacheповедением, каким бы ни было количество аргументов.


За исключением этого теряет STDOUT в процессе, ck_command echo 'asdf' | catничего не выводит.
Эван Кэрролл

2
→ EvanCarroll: этого не было в вашем «Вопросе». Чтобы также добиться такого сохранения выходных данных, посмотрите на очень элегантный и простой ответ от @roaima: unix.stackexchange.com/a/413344/31707 .
дан
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.