web-dev-qa-db-ja.com

終了コードではなく戻り値に基づいてパイプラインを構築するエレガントな方法は?

ステータスコードが役に立たない場合、stdoutからの出力に基づいてパイプラインを構築する方法はありますか?

答えはユースケースではなく、シェルスクリプトの範囲内の質問に対応することをお勧めします。私がやろうとしているのは、国と言語コードに基づいて名前を推測することにより、リポジトリで利用可能な最も具体的なパッケージを見つけることです。

これを例にとって、

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

最初の推測の方が適切ですが、存在しない可能性があります。この場合、最初のオプションhunspell-en$PACKAGE2)はnotが存在しないため、hunspell-en-zz$PACKAGE1)を返します。

apt-cacheのパイプライン

コマンドapt-cacheは、コマンドを実行できるときはいつでも成功(Shellによって終了コードゼロとして定義されています)を返します(apt-cacheのドキュメントから)

apt-cacheは、通常の操作ではゼロを返し、エラーでは10進数の100を返します。

そのため、パイプラインでコマンドを使用するのが難しくなります。通常、404と同等のパッケージ検索でエラーが発生することが予想されます(curlまたはwgetで発生するように)。パッケージが存在するかどうかを検索し、存在しない場合存在する場合は別のパッケージにフォールバックします

最初のコマンドは成功を返すため、これは何も返しません(したがって、||のrhsは実行されません)

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

apt-cache search2つの引数

これは何も返しません。apt-cacheがその引数とANDをとるので

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

apt-cacheのドキュメントから

個別の引数を使用して、一緒に使用される複数の検索パターンを指定できます。

したがって、これらの引数の1つが明らかに存在しないため、これは何も返しません。

質問

戻りコードがタスクに役に立たないapt-cacheに見られるような規則を処理するためのシェルイディオムは何ですか?そして成功はSTDOUTの出力の存在によってのみ決定されますか?

に似ている

  • 何も見つからなかったときに検索を失敗させる

    それらは両方とも同じ問題から生じています。そこで選択された回答はfind -zに言及していますが、これは残念ながらここでは適用できないソリューションであり、ユースケース固有です。ヌル終了を使用せずにイディオムやパイプラインを構築することについての言及はありません(apt-cacheのオプションではありません)

9
Evan Carroll

コマンドを受け取り、出力がある場合は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
5
roaima

私の知る限り、コマンドの成功が出力の存在によって決定されるケースに対処する標準的な方法はありません。ただし、回避策を書くことはできます。

たとえば、コマンドの出力を変数に保存してから、その変数が空かどうかを確認できます。

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
}

これらを使用すると、1つのコマンドで複数の検索を実行できます。例えば:

$ 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
3
nxnev

私はこれをエレガントとは呼びませんが、それが仕事をするかもしれないと思います:

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[@]}"
2
jesse_b

Muruはコメントでこれを明確にしました。入力がない場合、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を返します。

も参照してください、

2
Evan Carroll

次のように定義できます。

_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 '^'_とは異なり、上記は改行文字で終わらない入力で機能することが保証されますが、欠落している改行が追加されます。

これを回避し、NULでawkがチョークするシステムに移植できるようにするには、代わりに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()システムコールを使用することをお勧めします。それを公開するコマンドはわかりませんが、いつでもpythonctypesを使用できます。

_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_output_のstdinまたはstdout(または両方)のいずれかがsplice()が機能するためのパイプである必要があることに注意してください)。

2

シェルの非常に基本的な組み込み関数を使用することをお勧めします。

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

最も単純なテストケースは次のとおりです。

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

次に、慣れている||構造で簡単に使用できます。

ck_command command_1 || ck_command command_2

この単純な関数は、引数の数に関係なく、apt_cacheの動作で希望どおりに機能します。

0
dan