基于返回值而不是退出代码构造管道的优美方法?


8

当状态代码无效时,是否仍然可以根据stdout的输出构造管道?

我希望答案不是解决用例,而是shell脚本范围内的问题。我要尝试做的是根据国家和语言代码猜测名称,从而找到存储库中最具体的软件包。

以这个为例

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

第一个猜测更合适,但可能不存在。在这种情况下,我想回到hunspell-en$PACKAGE2),因为第一个选项hunspell-en-zz$PACKAGE1)也不会存在。

apt-cache的管道

apt-cache只要命令能够运行(从的文档apt-cache),该命令就会返回成功(由shell定义为退出代码零)

apt-cache在正常运行时返回零,在错误时返回十进制。

这使得在管道中使用该命令更加困难。通常,我期望404的等效程序包搜索会导致错误(如curl或会发生wget)。我想搜索以查看是否存在一个软件包,如果不存在,则回退到另一个软件包

这不会返回任何内容,因为第一个命令将返回成功(因此,||从不运行rhs )

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

apt-cache search 有两个参数

这不会返回任何内容,因为apt-cache它的参数是AND,

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在这方面一样-无用的返回码,成功取决于输出。
muru

1
是的,我同意他们都是出于同一个问题。选择的答案提到这里提到的-z可悲的不是解决方案,因此用例特定的问题不适用。而且也没有提到不使用空终止(没有一个选项apt-cache)的惯用法或构造管道
Evan Carroll

1
@EvanCarroll空终止完全是可选的。我之所以使用它,是因为它是处理文件名的最安全的方法,因此人们希望find它与-print0和一起使用grep -z。由于apt-cache不会提供以null结尾的输出,因此您不需要-z
muru

Answers:


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。如果使用非的shell 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
}

这些使您可以在单个命令中进行多次搜索。例如:

$ 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安静选项?手册页不是很详细,但是也许它会更改返回值?
jesse_b

1
仍然返回0。=(
Evan Carroll

2

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

也可以看看,


grep .如果输入包含至少一行(在某些实现中完全定界)且包含至少一个(在大多数实现中格式良好)字符的行,则返回true,否则将删除空行。grep '^'可以更好地检查是否有某些输出,尽管如果输入是一条非定界线,则对于某些实现,它仍然可以返回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 '^',上述内容将保证能在不以换行符结尾的输入上工作,但会添加缺少的换行符。

为避免这种情况并移植到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实现中有所不同,其中一些限制为空格和制表符,有些还包含诸如CR或FF之类的ASCII垂直间距字符,有些考虑了语言环境的空白)

理想情况下,在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_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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.