到目前为止我所做的是:
#!/bin/bash
exec 2> >(sed 's/^/ERROR= /')
var=$(
sleep 1 ;
hostname ;
ifconfig | wc -l ;
ls /sfsd;
ls hasdh;
mkdir /tmp/asdasasd/asdasd/asdasd;
ls /tmp ;
)
echo "$var"
这将在每个错误行的开头添加ERROR=,但首先显示所有错误,然后显示stdout,(不是按照执行的顺序)。
如果我们跳过将输出存储在变量中而直接执行命令,则输出将按照期望的顺序出现。
脚本的主要问题是命令替换$(...)
只捕获子shell的标准输出;子shell的标准错误仍然会传递给父shell的标准错误。碰巧的是,你重定向了父shell的标准错误,最终填充了父shell的标准输出;但这完全绕过了$(...)
,它只捕获子壳的标准输出。
var=$(
exec 2> >(sed 's/^/ERROR= /')
sleep 1
hostname
ifconfig | wc -l
ls /sfsd
ls hasdh
mkdir /tmp/asdasasd/asdasd/asdasd
ls /tmp
)
echo "$var"
即使如此,也不能保证的行顺序正确。问题是sed
与子shell中的其他所有内容并行运行,因此,当它刚刚收到错误行并忙于计划写入标准输出时,子shell中的一个后期命令可以向前推进,并且已经将更多的东西写入标准输出!您可以通过为每个命令分别启动sed
来改进这一点,以便shell在继续执行下一个命令之前等待sed
完成:
var=$(
sleep 1 2> >(sed 's/^/ERROR= /')
hostname 2> >(sed 's/^/ERROR= /')
{ ifconfig | wc -l ; } 2> >(sed 's/^/ERROR= /')
ls /sfsd 2> >(sed 's/^/ERROR= /')
ls hasdh 2> >(sed 's/^/ERROR= /')
mkdir /tmp/asdasasd/asdasd/asdasd 2> >(sed 's/^/ERROR= /')
ls /tmp 2> >(sed 's/^/ERROR= /')
)
echo "$var"
即便如此,sed
将与每个命令并发运行,因此,如果这些命令中的任何一个是既写入标准输出又写入标准错误的复杂命令,那么捕获该命令输出的顺序可能与命令实际写入它的顺序不匹配。但是对于你的目的来说,这应该已经足够了。
您可以通过为简单命令(非管道)情况创建一个包装器函数来提高可读性:
var=$(
function fix-stderr () {
"$@" 2> >(sed 's/^/ERROR= /')
}
fix-stderr sleep 1
fix-stderr hostname
fix-stderr eval 'ifconfig | wc -l' # using eval to get a simple command
fix-stderr ls /sfsd
fix-stderr ls hasdh
fix-stderr mkdir /tmp/asdasasd/asdasd/asdasd
fix-stderr ls /tmp
)
echo "$var"
sed
命令从shell的其余部分异步运行;一旦它处理命令替换中的命令的输入,它的输出就会变成标准错误。然而,这些命令的标准输出在$var
中被捕获,直到echo
命令运行后才显示。
即使您没有捕获输出,也有机会这些命令的标准错误和标准输出不会像您期望的那样出现,因为最终产生错误消息的sed
命令可能没有在您期望的时候被操作系统调度,从而延迟了错误消息的出现。
当您以通常的方式从终端运行命令时,该命令的标准错误和标准输出指向同一个文件:终端本身。因此,对文件的写入保持了它们在程序中发生的顺序。一旦您将其中一个或另一个管道到另一个进程,您就失去了对如何将两者重新拼接在一起的所有控制。在您的示例中,您将标准错误重定向到sed
,它将修改后的行写回标准输出。但是您无法控制操作系统何时调度sed
运行以及shell何时运行,因此您无法控制写入行的顺序。
它有助于为每个命令分别重定向标准错误:
tag_error () { sed 's/^/ERROR= /'; }
hostname 2> >(tag_error)
{ ifconfig | wc -l ; } 2> >(tag_error)
# etc
,但这仍然不能保证来自同一个程序的写操作的顺序,就好像它们都在写同一个文件一样。
(ruakh已经介绍了如何将它与捕获标准输出结合起来,所以我现在不再添加它了。
一种可能的解决方案是将命令放在数组中,然后在循环中执行:
declare -a cmds=('sleep 1' 'hostname' 'eval ifconfig | wc -l' 'ls /sfsd' 'ls /tmp' 'ls hasdh')
for i in "${cmds[@]}"; do
$i 2> >(sed -E 's/^/ERROR=/')
done
当错误发生时,它应该按照执行时的顺序打印。在数组中使用sh script.sh
这样的命令还应该显示来自结果外部脚本的任何stdout
或stderr
。对于管道命令,也可能需要eval
。