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


8

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

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

これを例にとると、

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

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

apt-cacheのパイプライン

apt-cacheコマンドが実行できる場合は常に、シェルによって終了コード0として定義されるコマンドが成功を返します(のドキュメントからapt-cache

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

これにより、パイプラインでのコマンドの使用がより困難になります。通常、私は404に相当するパッケージ検索でエラーが発生することを期待しています(curlまたはで発生しますwget)。パッケージが存在するかどうかを確認し、存在しない場合は別のパッケージにフォールバックしたい

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

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

apt-cache search 2つの引数

これは、apt-cacheその引数のANDを取るため、何も返しません。

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

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

別々の引数を使用して、AND結合された複数の検索パターンを指定できます。

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

質問

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

に似ている

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

    どちらも同じ問題に起因しています。そこで選択された答えはfind -z、残念なことに、ここで適用できる解決策ではなく、ユースケース固有のものです。ヌル終了を使用せずにイディオムやパイプラインを構築することについての言及はありません(のオプションではありませんapt-cache


それはhunspell-en存在しますか?とにかく、あなたはapt-cache policygrep forを使うことができます^$PACKAGENAME:
AlexP 2017

@AlexPこれらは例にすぎません。hunspell-enは国名でパッケージ化されているため存在せず、存在し、国名パッケージがないためhunspell-arです。特定の国と言語で最も正確なパッケージを見つける必要があります。
エヴァンキャロル

2
findこれはまさにapt-cacheこの点に似ています-役に立たない戻りコード、成功は出力に基づいています。
muru

1
はい、どちらも同じ問題が原因であることに同意します。選択した回答は-z、残念ながらここでは解決策ではないため、ユースケース固有の問題は適用されないことを言及しています。そして、イディオムやnull終了を使用せずにパイプラインを構築することについては言及されていません(のオプションではありませんapt-cache
Evan Carroll

1
@EvanCarroll null終了は完全にオプションです。それは、ファイル名に対処するための最も安全な方法だから1が期待されるので、私は、それを使用findして使用すること-print0ではgrepとなります-z。apt-cacheはnullで終了する出力を提供しないため、は必要ありません-z
muru

回答:


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を返すことに注意してください。以外のシェルではzshr printf '\0\0\0'falseも返されます。r printf '\0a\0b\0c'いくつかの砲弾もそうでしょう。
ステファンChazelas

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
}

これらを使用すると、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

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「名前のみ」のforオプションを含めて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を返します。=(
Evan Carroll

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 .入力に少なくとも1つの(ほとんどの実装では整形式)文字を含む(一部の実装では完全に区切られている)行が含まれている場合はtrueを返し、そうでない場合は空の行を削除します。grep '^'一部の実装では、入力が区切られていない1行の場合にfalseを返す場合があります(その行を削除することも、他の実装ではtrueを返すが、欠落している改行を追加することもあります)。一部のgrep実装もNUL文字を詰まらせます。
ステファンChazelas

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

これを回避し、awkNUL でチョークが発生するシステムに移植できるよう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実装によって異なり、スペースとタブに制限されている場合や、CRやFFのようなASCII垂直スペース文字も含まれている場合や、ロケールの空白と見なされる場合があります)

理想的には、Linuxでは、splice()システムコールを使用してパフォーマンスを最大化する必要があります。私はそれを公開するコマンドを知りませんが、あなたは常に使用できる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)'
}

has_outputのstdinまたは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
dan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.