我想写一个函数,总是有一个非空输出或失败,但我缺少一个命令,读取标准输入并管道到标准输出,如果非空或失败,如:
example() {
do_something_interesting_here $1 | cat_or_fails
}
这个想法是,如果命令cat_or_fails
的输入是空的,它就会失败(所以函数会失败),或者输入没有任何改变就输出(像cat
)。
但是我找不到任何标准的实用程序能够做到这一点,或者可能是我不确定如何使用这些工具。
遵循评论中@William Pursell的想法,grep似乎可以使用以下内容:
example() {
do_something_interesting_here $1 | grep "[[:alnum:]]"
}
正如在注释中提到的,它将使用任何空行(或任何不匹配给定正则表达式的行)。
确保至少找到文本文件的一行的常用技巧如下。这都是标准的壳的东西…Bash, ksh, zsh.
example() {
set -o pipefail #assure failure on do_something is not suppressed
do_something_interesting_here $1 |
grep . #assure no output returns error
}
如果您需要在函数返回时取消管道失败:
example() {
( #isolate pipefail in subshell
set -o pipefail #assure failure on do_something is not suppressed
do_something_interesting_here $1 |
grep . #assure no output returns error
)
}
实现此目的的简单算法很简单,很明显:尝试读取一个字节。如果它工作,写字节回来,然后运行cat
。如果失败,则以非零状态退出。
下面两种变体都可以用问题(... | cat_or_fails
)中描述的方式使用。
一个非常简单的实现(不尝试处理以NUL字符开头的二进制文件)看起来像:
cat_or_fails() {
local firstbyte
IFS= read -r -n 1 firstbyte || return
printf '%s' "${firstbyte:-$'n'}"
cat
}
尝试正确处理二进制文件的稍微不那么简单的实现可能看起来像:
cat_or_fails() {
local firstbyte
IFS= read -r -d '' -n 1 firstbyte || return
if [[ $firstbyte ]]; then
printf '%s' "$firstbyte"
else
printf ' '
fi
cat
}
有多种方法可以做到这一点,但最简单的是使用awk
:
function cat_or_fail () { awk '1;END{exit !NR}' "$@"; }
这里,如果没有读取任何一行,awk
返回非零状态。
或者,moreutils
包中提供了一个工具。命令ifne
允许执行命令,具体取决于/dev/stdin
是否为空:
ifne command
:如果标准输入不为空,运行command
ifne -n command
:如果标准输入为空,则执行command
。如果标准输入不为空,则将标准输入发送到标准输出。
后者本质上是OP所期望的。函数cat_or_fail
看起来像
function cat_or_fail () { ifne -n false; }
如果OP想要具有与cat
相似的行为,您可以将其写为:
function cat_or_fail () { cat -- "${@}" | ifne -n false; }
后者可以接受文件作为参数,也可以从类似于cat
的管道中读取输入。
如果您想将其扩展到command_or_fail
,其中command
使用标准输入执行,或者失败。你的工作方式要有一点不同。
ifne
的返回状态不受标准输入内容(空或不空)的影响。所以你不能在and-or序列列表(foo && bar || baz
)中使用它。
为了创建请求的行为,需要使用pipefail
选项
$ set -o pipefail
$ function command_or_fail() { ifne -n false | ifne "$@"; }
$ echo foo | command_or_fail cat -n
1 foo
$ echo $?
0
$ </dev/null | command_or_fail cat -n
$ echo $?
1