ステータスコードが役に立たない場合、stdoutからの出力に基づいてパイプラインを構築する方法はありますか?
答えはユースケースではなく、シェルスクリプトの範囲内の質問に対応することをお勧めします。私がやろうとしているのは、国と言語コードに基づいて名前を推測することにより、リポジトリで利用可能な最も具体的なパッケージを見つけることです。
これを例にとって、
$PACKAGE1=hunspell-en-zz
$PACKAGE2=hunspell-en
最初の推測の方が適切ですが、存在しない可能性があります。この場合、最初のオプションhunspell-en
($PACKAGE2
)はnotが存在しないため、hunspell-en-zz
($PACKAGE1
)を返します。
コマンド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 search
2つの引数これは何も返しません。apt-cache
がその引数とANDをとるので
apt-cache search hunspell-en-zz hunspell-en
apt-cache
のドキュメントから
個別の引数を使用して、一緒に使用される複数の検索パターンを指定できます。
したがって、これらの引数の1つが明らかに存在しないため、これは何も返しません。
戻りコードがタスクに役に立たないapt-cache
に見られるような規則を処理するためのシェルイディオムは何ですか?そして成功はSTDOUTの出力の存在によってのみ決定されますか?
それらは両方とも同じ問題から生じています。そこで選択された回答は
find -z
に言及していますが、これは残念ながらここでは適用できないソリューションであり、ユースケース固有です。ヌル終了を使用せずにイディオムやパイプラインを構築することについての言及はありません(apt-cache
のオプションではありません)
コマンドを受け取り、出力がある場合は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
私の知る限り、コマンドの成功が出力の存在によって決定されるケースに対処する標準的な方法はありません。ただし、回避策を書くことはできます。
たとえば、コマンドの出力を変数に保存してから、その変数が空かどうかを確認できます。
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
私はこれをエレガントとは呼びませんが、それが仕事をするかもしれないと思います:
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[@]}"
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
を返します。
も参照してください、
次のように定義できます。
_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()
システムコールを使用することをお勧めします。それを公開するコマンドはわかりませんが、いつでも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()
が機能するためのパイプである必要があることに注意してください)。
シェルの非常に基本的な組み込み関数を使用することをお勧めします。
ck_command() { [ -n $("$@") ] ; }
最も単純なテストケースは次のとおりです。
ck_command echo 1 ; echo $?
ck_command echo ; echo $?
次に、慣れている||
構造で簡単に使用できます。
ck_command command_1 || ck_command command_2
この単純な関数は、引数の数に関係なく、apt_cache
の動作で希望どおりに機能します。