Bash条件管道

5
我怎样才能在命令返回 true 的情况下将输出传输到管道中?
function open
{
    TEMPFILE=$(mktemp -u)
    if ! gpg2 --quiet --decrypt --batch --passphrase "$2" "$1" 2> $TEMPFILE; then
        error $"Password errata od errore di lettura dal file\n\nDettagli:\n$(grep -v '^$' $TEMPFILE)"
        rm -f $TEMPFILE
        return 1
    fi
    rm -f $TEMPFILE
}

if ! open "$@" "$PASSWORD"; then
    exit 1
fi | <SOMECOMMAND>

这种方式只是简单地进行管道操作,而不检查open命令返回的true或false,因此永远不会执行"exit 1"。

我该如何解决这个问题,而又不使用文件(出于安全原因)。


完整源代码:在SourceForge上的fpasswd-0.2-alpha - Stefano d'Antonio
3个回答

9
在我提出解决方案之前,让我解释一下这个问题比你想象的要困难得多。基本问题是时间:`open...`函数在运行时生成输出;它在完成运行后生成退出状态(因此在生成输出后)。由于您希望根据退出状态执行不同的操作,因此必须将输出存储在某个临时地方,直到函数完成并且您可以决定如何处理输出为止。
管道本质上不适用于此,因为管道不存储数据(除了一些缓冲区空间)——它们从一个程序传递数据到另一个程序,并且在这种情况下,第二个程序无法在第一个程序完成之前启动。通常,临时文件非常适合此目的(存储数据就是文件的作用),但是由于安全原因,您不希望使用它。这几乎只能把数据放在RAM中的某个地方(尽管这也不完全安全...)。
@Karoly Horvath的答案建议将输出存储在bash变量中(存储在RAM中),但是由于bash无法处理变量值中的空字节,因此该方法无法奏效。所以,我提出了一种变体,您可以使用“安全”编码的数据,并将其放入bash变量中。我使用uuencode格式,但您也可以使用base64、十六进制转储等。
if result=$(open "$@" "$PASSWORD" | uuencode -; exit ${PIPESTATUS[0]}); then
    echo "$result" | uudecode -p | SOMECOMMAND
fi

请注意,PIPESTATUS 是 bash 的特定用法,因此您应该以 #!/bin/bash 开始脚本。此外,如果输出太长,您可能会遇到 bash 希望存储/扩展/等待多少数据的限制; 如果这证明是个问题,事情将变得更加复杂。
顺便提一下,如果您担心安全性,请勿使用 gpg2 的 --passphrase 选项--在命令行上传递口令会将其暴露给例如在正确时间运行 ps 的人,这是一个非常糟糕的想法。gpg2 有许多供应口令的选项,请使用更好的选项。

1
是的,使用 echo "$PASSWORD" | gpg2 ... --passphrase-fd 0(注意 $PASSWORD 周围的双引号)应该是安全的,因为 echo 不作为一个常规命令运行,而是在子shell中作为内置命令运行,因此它的参数不可见于 ps 等。 - Gordon Davisson
1
在这种情况下,那似乎是最好的方法。 - Gordon Davisson
实际上,我期望 uuencode 比任何特定的 base64 工具更普遍可用(尽管最近版本的 uuencode 实用程序支持使用“-m”标志进行 base64 编码)。 - Gordon Davisson
在我的 Gentoo 中,base64 在 coreutils 中,uuencode 在 sharutils 中,对系统来说不是很必要(事实上,我没有它)。 - Stefano d'Antonio
请返回已翻译的文本。 - Stefano d'Antonio
显示剩余4条评论

0
以下代码应该在文件成功打开的情况下有条件地将结果进行管道传输:
   result=open "$@" "$PASSWORD"
    if [ $? -gt 0 ]; then
      exit 1
    fi
    echo "$result" | <SOMECOMMAND>

换行不是问题...你的SOMECOMMAND是什么? - Karoly Horvath
你关于换行符的说法是正确的,但它还包含其他不可打印字符(如\0),这些字符并未被保留。SOMECOMMAND是一个函数,用于打印密码(如果是图形界面,则通过zenity打印,否则通过stdout打印)。出于安全原因,我不能使用文件。 - Stefano d'Antonio
1
@Uno 你总是需要引用子进程的输出。尝试使用 result="$(open $@ $PASSWORD)"(我讨厌反引号)。 - KurzedMetal
不,赋值是内部的,不会改变 $?。反引号也可以正常工作,引用不会有帮助。 - Karoly Horvath
@shellter 不,它肯定不会打扰! :) 无论如何,还是谢谢你! - Stefano d'Antonio
显示剩余4条评论

0
使用命名管道并存储返回值。
mkfifo piper;
open "$@" "$PASSWORD"; retval=$? > piper &
if [ x"$retval" != x0 ]
then
   rm piper
   exit 1
fi
<SOMECOMMAND> < piper
rm piper

无法工作。首先,retval在子shell中被设置(因为有&)。其次,open ...命令可能会阻塞等待从管道中读取数据,但在open ...完成之后才能从管道中读取任何内容。 - Gordon Davisson
我刚刚测试了这个(虽然没有使用open),它按预期工作...无论是在命令发送到管道失败还是成功时,都会进入then语句。而且open命令正在写入管道,而不是读取。 - Drake Clarris
对我没用。我尝试了false; retval=$? >/dev/null &,等待它完成,然后echo $retval,结果得到一行空白。至于open命令阻塞的问题,如果你试图向管道中写入过多的数据(通常是64kB,请参考这里),写入进程将会阻塞,等待读取数据并在管道中腾出空间。 - Gordon Davisson
刚刚意识到我的测试过于简化,以至于删除了 &.. 哎呀,我的错误。 - Drake Clarris
就像我之前说的,我不想使用文件或命名管道,因为内容中包含明文密码。 - Stefano d'Antonio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接