在不使用子 shell 的情况下复制 tee 行为,以便变量范围不受影响

在不使用子 shell 的情况下复制 tee 行为,以便变量范围不受影响

我需要将 BASH 中命令组的输出捕获到 STDOUT 和日志文件。考虑此代码与命令分组及其输出

#!/usr/bin/bash

main(){
declare -i mycode=1
echo "Declared mycode:${mycode}"

{
  #command group
  echo "mycode:${mycode}"
  mycode=2
  echo "mycode:${mycode}"
} 2>&1

echo "mycode:${mycode}"
}

main

输出是:

Declared mycode:1
mycode:1
mycode:2
mycode:2

我需要将命令组输出捕获到日志文件和 STDOUT,因此我添加 tee,如下所示:

#!/usr/bin/bash

main(){
declare -i mycode=1
echo "Declared mycode:${mycode}"

{
  #command group
  echo "mycode:${mycode}"
  mycode=2
  echo "mycode:${mycode}"
} 2>&1 | tee ~/log.log

 echo "mycode:${mycode}"
}

main

但现在输出如下:

Declared mycode:1
mycode:1
mycode:2
mycode:1

所以价值我的代码当使用 tee 时,变量不会在外部作用域中设置为 2,因为 tee 的左侧将在子 shell 中运行。由于各种原因我需要我的代码在全局范围内定义,因此我需要避免使用子 shell。

如何在没有子 shell 的情况下实现 tee 的行为,从而将输出流式传输到 STDOUT 和日志文件。

答案1

解决这个问题的一种方法是自己实现管道连接器:

#!/bin/bash

# Initialisation
mycode=1

# Tidy up
trap 'ss=$?; [ -n "$tmpd" ] && [ -d "$tmpd" ] && rm -rf "$tmpd"; exit $ss' 1 2 15

# Unique temporary directory
tmpd=$(mktemp --directory "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX")

# Create pipe
pipe="$tmpd/pipe"
mknod "$pipe" p

# Output
tee <"$pipe" "$HOME/log.log" &

# Subprocess
{
    echo "mycode:${mycode}"

    mycode=2
    echo "mycode:${mycode}"

} >"$pipe" 2>&1

# Destroy temporary, including pipe
wait
rm -rf "$tmpd"

# Done
echo "final mycode:${mycode}"

输出

mycode:1
mycode:2
final mycode:2

cat ~/log.log

mycode:1
mycode:2

答案2

根据您想要通过日志文件实现的目标,您可能希望将其附加到描述符 #3。 (记住标准输出是#1并且标准错误是#3。)

#!/bin/bash

# Initialisation
mycode=1

# Attach fd3 to the logfile and stdout
exec 3> >(tee "$HOME/log.log" >&1)

# Demonstrations
echo this is to stdout
echo this is to stderr >&2
echo this is to logfile and stdout >&3

# Subprocess
{
    echo "mycode:${mycode}"

    mycode=2
    echo "mycode:${mycode}"

} 2>&1 1>&3

# Done
echo "final mycode:${mycode}"
sleep 0.25

这种方法的一个优点是,只需重定向到文件描述符 3,就可以很容易地从任何地方写入日志文件(请参阅上面的“演示”代码块)。

缺点是tee异步运行,因此输出可能会错误地交错。最后sleep 0.25的目的是留出时间来tee完成其输出的编写。根据您使用该功能的方式,交错可能是也可能不是现实世界的问题。

输出示例:

this is to stdout
this is to stderr
final mycode:2
this is to logfile and stdout
mycode:1
mycode:2

日志档案 (cat "$HOME/log.log"):

this is to logfile and stdout
mycode:1
mycode:2

相关内容