Shell:捕获变量中的输出文件

时间:2018-01-28 13:14:17

标签: bash shell sh zsh

openssl之类的命令对输出文件有-out <file>之类的参数。我想在shell变量中捕获这些输出文件的内容,以便在不创建临时文件的情况下在其他命令中使用。例如,要生成自签名证书,可以使用:

openssl req -new -newkey rsa:2048 -subj / -days 365 -nodes -x509 -keyout KEYFILE -out CERTFILE 2>/dev/null

我必须捕获两个输出文件的最接近的是通过进程替换将它们回显到stdout,但这并不理想,因为仍然需要将它们分开。

openssl req -new -newkey rsa:2048 -subj / -days 365 -nodes -x509 -keyout >(key=$(cat -); echo $key) -out >(cert=$(cat -); echo $cert) 2>/dev/null

是否有一种干净的方法来捕获shell变量中输出文件的内容?

2 个答案:

答案 0 :(得分:3)

现在大多数现代shell支持/ dev / stdout作为文件名重定向到stdout。这对于单个文件解决方案来说已经足够了,但对于两个输出文件,您需要使用“进程替换”。

eval "$( openssl req -new -newkey rsa:2048 -subj / -days 365 -nodes -x509 -keyout >(echo "keyout='$( cat )'" ) -out >(echo out="'$( cat )'" ) )"

这使用进程替换将每个“文件”定向到一个单独的进程,该进程打印到stdout计算值的赋值。然后将整个事物传递给eval进行实际分配。

保持stderr的输出以显示弹出的任何错误消息。有用的是在遇到麻烦时记录它。

编辑:合并Charles Duffy's好偏执:

flockf="$(mktemp -t tmp.lock.$$.XXXXXX )" || exit $?;

eval "$( openssl req -new -newkey rsa:2048 -subj / -days 365 -nodes -x509 \
        -keyout >( set -x; 99>"$flockf" && \
                flock -x "$flockf" printf "keyout=%q " "$( cat )"; ) \ 
        -out    >( set -x; 99>"$flockf" && \
                flock -x "$flockf" printf "out=%q "    "$( cat )"; ) \ 
        )"  ;
rm -f "$flockf"

答案 1 :(得分:2)

Gilbert's answer的扩展,提供额外的偏执狂:

eval "$( openssl req -new -newkey rsa:2048 -subj / -days 365 -nodes -x509 \
         -keyout >(printf 'keyout=%q\n' "$(</dev/stdin)") \
         -out >(printf 'out=%q\n' "$(</dev/stdin)") )"

(请注意,如果您的数据包含NUL,这不适用,bash无法存储在本机shell变量中;在这种情况下,您需要以base64 - 编码形式将内容分配给变量)。

echo "keyout='$(cat)'"不同,printf 'keyout=%q\n' "$(cat)"确保即使恶意内容也不能被shell eval用作命令替换,重定向或除文字数据之外的其他内容。

为了解释为什么这是必要的,让我们来看一个简化的案例:

write_to_two_files() { printf 'one\n' >"$1"; printf 'two\n' >"$2"; }
write_to_two_files >(echo "one='$(cat)'") >(echo "two='$(cat)'")

...我们得到的输出类似于(但没有特定的排序):

two='two'
one='one'

...当eval编辑时,设置两个变量:

$ eval "$(write_to_two_files >(echo "one='$(cat)'") >(echo "two='$(cat)'"))"
$ declare -p one two
declare -- one="one"
declare -- two="two"

但是,让我们说我们的程序行为有点不同:

## Demonstrate why eval'ing content created by echoing data is dangerous
write_to_two_files() {
  printf "'%s'\n" '$(touch /tmp/i-pwned-your-box)' >"$1"
  echo "two" >"$2"
}
eval "$(write_to_two_files >(echo "one='$(cat)'") >(echo "two='$(cat)'"))"
ls -l /tmp/i-pwned-your-box

我们将其评估为代码,而不是仅仅将输出分配给变量。

如果您对确保两次打印操作在不同时间发生(防止其输出混合)更感兴趣,则进一步添加锁定非常有用。这个 涉及一个临时文件,但是没有将你的密钥材料写入磁盘(避免这是避免临时文件使用的最有说服力的理由):

lockfile=$(mktemp -t output.lck.XXXXXX)
eval "$( openssl req -new -newkey rsa:2048 -subj / -days 365 -nodes -x509 \
         -keyout >(in=$(cat); exec 99>"$lockfile" && flock -x 99 && printf 'keyout=%q\n' "$in") \
         -out    >(in=$(cat); exec 99>"$lockfile" && flock -x 99 && printf 'out=%q\n' "$in") )"

请注意,我们只是阻止写入而不是读取,因此我们无法进入竞争条件(即openssl未完成写入文件A的情况,因为它在写入文件时被阻止 - B,永远无法完成,因为文件A读取端的子shell保存锁。)