首页 > 解决方案 > 使用tee时如何将多个shell命令的输出分配给变量?

问题描述

我想tee从管道中连接的多个 shell 命令中获取结果。我做了一个简单的例子来解释这一点。假设我想计算“a”、“b”和“c”的数量。

echo "abcaabbcabc" | tee >(tr -dc 'a' | wc -m) >(tr -dc 'b' | wc -m) >(tr -dc 'c' | wc -m) > /dev/null

然后我尝试将每个计数的结果分配给一个 shell 变量,但它们最终都是空的。

echo "abcaabbcabc" | tee >(A=$(tr -dc 'a' | wc -m)) >(B=$(tr -dc 'b' | wc -m)) >(C=$(tr -dc 'c' | wc -m)) > /dev/null && echo $A $B $C

正确的方法是什么?

标签: bashvariablestee

解决方案


Use files. They are the single most reliable solution. Any of the commands may need different time to run. There is no easy way to synchronize command redirections. Then most reliable way is to use a separate "entity" to collect all the data:

tmpa=$(mktemp) tmpb=$(mktemp) tmpc=$(mktemp)
trap 'rm "$tmpa" "$tmpb" "$tmpc"' EXIT

echo "abcaabbcabc" | 
     tee >(tr -dc 'a' | wc -m > "$tmpa") >(tr -dc 'b' | wc -m > "$tmpb") | 
     tr -dc 'c' | wc -m > "$tmpc"
A=$(<"$tmpa")
B=$(<"$tmpb")
C=$(<"$tmpc")

rm "$tmpa" "$tmpb" "$tmpc"
trap '' EXIT

Second way:

You can prepend the data from each stream with a custom prefix. Then sort all lines (basically, buffer them) on the prefix and then read them. The example script will generate only a single number from each process substitution, so it's easy to do:

read -r A B C < <(
  echo "abcaabbcabc" | 
  tee >(
    tr -dc 'a' | wc -m | sed 's/^/A /'
  ) >(
    tr -dc 'b' | wc -m | sed 's/^/B /'
  ) >(
    tr -dc 'c' | wc -m | sed 's/^/C /'
  ) >/dev/null |
  sort |
  cut -d' ' -f2 |
  paste -sd' '
)
echo A="$A" B="$B" C="$C"

Using temporary files with flock to synchronize the output of child processes could look like this:

tmpa=$(mktemp) tmpb=$(mktemp) tmpc=$(mktemp)
trap 'rm "$tmpa" "$tmpb" "$tmpc"' EXIT

echo "abcaabbcabc" | 
(
  flock 3
  flock 4
  flock 5

  tee >(
    tr -dc 'a' | wc -m | 
    { sleep 0.1; cat; } > "$tmpa"

    # unblock main thread
    flock -u 3
  ) >(
    tr -dc 'b' | wc -m | 
    { sleep 0.2; cat; } > "$tmpb"

    # unblock main thread
    flock -u 4
  ) >(
    tr -dc 'c' | wc -m | 
    { sleep 0.3; cat; } > "$tmpc"

    # unblock main thread
    flock -u 5
  ) >/dev/null

  # wait for subprocesses to finish
  # need to re-open the files to block on them
  (
    flock 3
    flock 4
    flock 5
  ) 3<"$tmpa" 4<"$tmpb" 5<"$tmpc"
) 3<"$tmpa" 4<"$tmpb" 5<"$tmpc"

A=$(<"$tmpa")
B=$(<"$tmpb")
C=$(<"$tmpc")

declare -p A B C

推荐阅读