Bash 条件管道

Posted

技术标签:

【中文标题】Bash 条件管道【英文标题】:Bash conditional piping 【发布时间】:2012-05-11 20:58:12 【问题描述】:

如何通过管道输出命令以防万一它返回 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”。

如何在不使用文件的情况下解决它(出于安全原因)。

【问题讨论】:

完整来源:fpasswd-0.2-alpha on SourceForge 【参考方案1】:

如果文件成功打开,以下代码应有条件地通过管道传输结果:

   result=open "$@" "$PASSWORD"
    if [ $? -gt 0 ]; then
      exit 1
    fi
    echo "$result" | <SOMECOMMAND>

【讨论】:

open 的输出包含换行符和其他不可打印的字符,它不能这样工作。 换行不是问题...你的命令是什么? 您对新行是正确的,但它还包含其他未保留的不可打印字符(如 \0)。 SOMECOMMAND 是一个打印密码的函数(如果是图形则通过 zenity,如果不是则通过 stdout)。出于安全原因,我无法使用文件。 @Uno 你总是要引用子进程的输出。试试result="$(open $@ $PASSWORD)"(我讨厌蜱虫) @KurzedMetal: open 需要打印返回码,即 printf "1\n"f 用于由 cmd-substitution 捕获的值(即 result=$(...))。 return 只设置调用者的$? 的值。只要分配 result=...whatever 不会导致语法错误,$? 的值现在可能会反映对 result 的成功分配,并且“返回”的值会丢失。没有时间测试它。祝大家好运。【参考方案2】:

使用命名管道并存储返回值

mkfifo piper;
open "$@" "$PASSWORD"; retval=$? > piper &
if [ x"$retval" != x0 ]
then
   rm piper
   exit 1
fi
<SOMECOMMAND> < piper
rm piper

【讨论】:

行不通。首先,retval 被设置在一个子shell 中(因为&amp;)。其次,open ... 命令可能会阻塞等待从管道中读取数据,但直到 open ... 完成后才能从管道中读取任何内容。 我刚刚对此进行了测试(尽管显然没有使用 open )并且它按预期工作......无论是发送到管道的命令失败还是成功,都在应该输入 then 语句时。并且打开命令正在写入管道,而不是读取 对我不起作用。我尝试了false; retval=$? &gt;/dev/null &amp;,等待它完成,然后echo $retval 得到一个空行。至于 open 命令阻塞,如果你尝试向管道写入过多(一般为 64kB,请参阅here),写入过程将阻塞等待读取数据并在管道中腾出空间。 刚刚意识到我已经过度简化了我的测试,以至于删除了 &.. 哎呀,我的错误 如我所说,我既不想使用文件,也不想使用命名管道,内容充满了明文密码。【参考方案3】:

在我提出解决方案之前,让我解释一下这比您意识到的更困难。基本问题是时间问题:open ... 函数在运行时会产生输出; 在它完成运行后(因此在它产生它的输出之后)会产生一个退出状态。由于您想根据退出状态对输出执行不同的操作,因此您必须将输出临时存储在某个地方,直到函数完成,您可以决定如何处理输出。

管道本来就不能解决这个问题,因为管道不存储数据(除了一点缓冲空间)——它们将数据“实时”从一个程序传递到另一个程序,在这种情况下,第二个程序可以'直到第一个完成后才开始。通常,临时文件将是完美的(存储数据是文件的用途),但出于安全原因,您不希望这样做。这几乎就是将数据放在 RAM 中的某个地方(尽管这也不是完全安全的......)。

@Karoly Horvath 的回答建议将输出存储在 bash 变量中(存储在 RAM 中),但这不起作用,因为 bash 无法处理变量值中的空字节。因此,我提出了一种变体,您可以在其中使用数据的“安全”编码,并将其放入 bash 变量中。我用的是uuencode格式,不过你也可以用base64、hex dump等等……

if result=$(open "$@" "$PASSWORD" | uuencode -; exit $PIPESTATUS[0]); then
    echo "$result" | uudecode -p | SOMECOMMAND
fi

请注意,PIPESTATUS 是一个 bashism,因此您应该以 #!/bin/bash 开始脚本。此外,如果输出太长,您可能会遇到 bash 想要存储/扩展/等多少数据的限制;如果这被证明是一个问题,事情就会变得更加复杂。

顺便说一句,如果您担心安全性,请不要使用 gpg2 的 --passphrase 选项 - 在命令行上传递密码短语会将其暴露给例如在正确的时间运行ps 的任何人,这是一个非常糟糕的主意。 gpg2 提供了很多选项来提供密码,所以请使用更好的选项。

【讨论】:

谢谢,稍后我尝试使用 uuencode 并让您知道。也感谢您的解释;现在我将使用“echo $PASS | gpg2 -d --batch --passphrase-fd 0 $FILE”,它是安全的,对吧? 但是,当我在寻找替代方案时,我使用了两次“打开”功能,一次是检查 (>/dev/null),另一次是使用数据...但这是一种资源和时间的浪费。 (但是,密码在 ps 中只显示了几秒钟,但您绝对正确,我不应该使用 --passphrase) 是的,使用echo "$PASSWORD" | gpg2 ... --passphrase-fd 0(注意$PASSWORD 周围的双引号)应该是安全的,因为echo 不是作为常规命令运行,而是作为子shell 中的内置命令运行,所以ps 等看不到它的参数。 在这种情况下,这似乎是最好的方法。 我希望我的软件具有更少的依赖性,所以我使用了 base64... 效果很好!!真的谢谢!还有我不知道的 $PIPESTATUS!

以上是关于Bash 条件管道的主要内容,如果未能解决你的问题,请参考以下文章

bash中的条件[重复]

Azure DevOps Pipelines 中的条件阶段执行

循环语句和函数

Python 管道中的条件元素

Azure DevOps 管道条件 - 为啥包含“并且始终”?

shell脚本