使用 tee 时如何将多个 shell 命令的输出分配给变量?
How to assign output of multiple shell commmands to variable when using tee?
我想 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
正确的做法是什么?
使用文件。它们是唯一最可靠的解决方案。任何命令可能需要不同的时间 运行。没有简单的方法来同步命令重定向。那么最可靠的方法是使用单独的"entity"来收集所有数据:
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
第二种方式:
您可以在每个流的数据前加上自定义前缀。然后对前缀上的所有行(基本上,缓冲它们)进行排序,然后读取它们。示例脚本将从每个进程替换中仅生成一个数字,因此很容易做到:
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"
使用 flock
的临时文件来同步子进程的输出可能如下所示:
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
你可以使用这个特色字母频率分析
#!/usr/bin/env bash
declare -A letter_frequency
while read -r v k; do
letter_frequency[$k]="$v"
done < <(
grep -o '[[:alnum:]]' <<<"abcaabbcabc" |
sort |
uniq -c
)
for k in "${!letter_frequency[@]}"; do
printf '%c = %d\n' "$k" "${letter_frequency[$k]}"
done
输出:
c = 3
b = 4
a = 4
或者仅分配 $A
、$B
和 $C
,如您的示例所示:
#!/usr/bin/env bash
{
read -r A _
read -r B _
read -r C _
}< <(
grep -o '[[:alnum:]]' <<<"abcaabbcabc" |
sort |
uniq -c
)
printf 'a=%d\nb=%d\nc=%d\n' "$A" "$B" "$C"
grep -o '[[:alnum:]]'
: 将每个字母数字字符拆分成一行
sort
: 排序字符行
uniq -c
:计算每个实例并输出每个实例的计数和字符
< <( command group; )
:这个命令组的输出是针对之前 命令组的stdin
如果您需要计算不可打印字符、换行符、空格、制表符的出现次数,您必须使所有这些命令输出并处理空分隔列表。使用这些工具的 GNU 版本肯定可以完成。我把它作为练习交给你。
count任意字符除null的解决办法:
如图所示,也适用于 Unicode。
#!/usr/bin/env bash
declare -A character_frequency
declare -i v
while read -d '' -r -N 8 v && read -r -d '' -N 1 k; do
character_frequency[$k]="$v"
done < <(
grep --only-matching --null-data . <<<$'a¹bc✓ ✓\n\t\t\u263A☺ ☺ aabbcabc' |
head --bytes -2 | # trim the newline added by grep
sort --zero-terminated | # sort null delimited list
uniq --count --zero-terminated # count occurences of char (null delim)
)
for k in "${!character_frequency[@]}"; do
printf '%q = %d\n' "$k" "${character_frequency[$k]}"
done
输出:
$'\n' = 1
$'\t' = 2
☺ = 3
\ = 7
✓ = 2
¹ = 1
c = 3
b = 4
a = 4
我想 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
正确的做法是什么?
使用文件。它们是唯一最可靠的解决方案。任何命令可能需要不同的时间 运行。没有简单的方法来同步命令重定向。那么最可靠的方法是使用单独的"entity"来收集所有数据:
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
第二种方式:
您可以在每个流的数据前加上自定义前缀。然后对前缀上的所有行(基本上,缓冲它们)进行排序,然后读取它们。示例脚本将从每个进程替换中仅生成一个数字,因此很容易做到:
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"
使用 flock
的临时文件来同步子进程的输出可能如下所示:
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
你可以使用这个特色字母频率分析
#!/usr/bin/env bash
declare -A letter_frequency
while read -r v k; do
letter_frequency[$k]="$v"
done < <(
grep -o '[[:alnum:]]' <<<"abcaabbcabc" |
sort |
uniq -c
)
for k in "${!letter_frequency[@]}"; do
printf '%c = %d\n' "$k" "${letter_frequency[$k]}"
done
输出:
c = 3
b = 4
a = 4
或者仅分配 $A
、$B
和 $C
,如您的示例所示:
#!/usr/bin/env bash
{
read -r A _
read -r B _
read -r C _
}< <(
grep -o '[[:alnum:]]' <<<"abcaabbcabc" |
sort |
uniq -c
)
printf 'a=%d\nb=%d\nc=%d\n' "$A" "$B" "$C"
grep -o '[[:alnum:]]'
: 将每个字母数字字符拆分成一行sort
: 排序字符行uniq -c
:计算每个实例并输出每个实例的计数和字符< <( command group; )
:这个命令组的输出是针对之前 命令组的stdin
如果您需要计算不可打印字符、换行符、空格、制表符的出现次数,您必须使所有这些命令输出并处理空分隔列表。使用这些工具的 GNU 版本肯定可以完成。我把它作为练习交给你。
count任意字符除null的解决办法:
如图所示,也适用于 Unicode。
#!/usr/bin/env bash
declare -A character_frequency
declare -i v
while read -d '' -r -N 8 v && read -r -d '' -N 1 k; do
character_frequency[$k]="$v"
done < <(
grep --only-matching --null-data . <<<$'a¹bc✓ ✓\n\t\t\u263A☺ ☺ aabbcabc' |
head --bytes -2 | # trim the newline added by grep
sort --zero-terminated | # sort null delimited list
uniq --count --zero-terminated # count occurences of char (null delim)
)
for k in "${!character_frequency[@]}"; do
printf '%q = %d\n' "$k" "${character_frequency[$k]}"
done
输出:
$'\n' = 1
$'\t' = 2
☺ = 3
\ = 7
✓ = 2
¹ = 1
c = 3
b = 4
a = 4