如何将多个命令的输出发送到单个 shell 管道?
How can I send multiple commands' output to a single shell pipeline?
我有多个管道,看起来像:
tee -a $logfilename.txt | jq string2object.jq >> $logfilename.json
或
tee -a $logfilename.txt | jq array2object.jq >> $logfilename.json
对于每个管道,我想应用于多个命令。
每组命令看起来像:
echo "start filelist:"
printf '%s\n' "$PWD"/*
或
echo "start wget:"
wget -nv http://web.site.com/downloads/2017/file_1.zip 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip 2>&1
这些命令的输出都应该通过管道。
我过去尝试过的是将管道分别放在每个命令上:
echo "start filelist:" | tee -a $logfilename | jq -sRf array2object.jq >>$logfilename.json
printf '%s\n' "$PWD"/* | tee -a $logfilename | jq -sRf array2object.jq >>$logfilename.json
但在那种情况下,JSON 脚本一次只能看到一行,因此无法正常工作。
便携式方法
以下可移植到 POSIX sh:
#!/bin/sh
die() { rm -rf -- "$tempdir"; [ "$#" -gt 0 ] && echo "$*" >&2; exit 1; }
logfilename="whatever"
tempdir=$(mktemp -d "${TMPDIR:-/tmp}"/fifodir.XXXXXX) || exit
mkfifo "$tempdir/fifo" || die "mkfifo failed"
tee -a "$logfilename" <"$tempdir/fifo" \
| jq -sRf json_log_s2o.jq \
>>"$logfilename.json" & fifo_pid=$!
exec 3>"$tempdir/fifo" || die "could not open fifo for write"
echo "start filelist:" >&3
printf '%s\n' "$PWD"/* >&3
echo "start wget:" >&3
wget -nv http://web.site.com/downloads/2017/file_1.zip >&3 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip >&3 2>&1
exec 3>&- # close the write end of the FIFO
wait "$fifo_pid" # and wait for the process to exit
rm -rf "$tempdir" # delete the temporary directory with the FIFO
避免 FIFO 管理(使用 Bash)
使用 bash,可以避免使用进程替换来管理 FIFO:
#!/bin/bash
logfilename="whatever"
exec 3> >(tee -a "$logfilename" | jq -sRf json_log_s2o.jq >>"$logfilename.json")
echo "start filelist:" >&3
printf '%s\n' "$PWD/*" >&3
echo "start wget:" >&3
wget -nv http://web.site.com/downloads/2017/file_1.zip >&3 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip >&3 2>&1
exec 3>&1
等待退出(使用 Linux-y 工具)
然而,这个不让你做的事情(没有bash 4.4)是检测jq
何时失败,或者等待jq
在脚本退出之前完成写入。如果你想确保 jq
在你的脚本退出之前完成,那么你可以考虑使用 flock
,像这样:
writelogs() {
exec 4>".json"
flock -x 4
tee -a "" | jq -sRf json_log_s2o.jq >&4
}
exec 3> >(writelogs "$logfilename")
及以后:
exec 3>&-
flock -s "$logfilename.json" -c :
因为 writelogs
函数中的 jq
进程锁定了输出文件,最终的 flock -s
命令无法 also 锁定输出文件直到 jq
退出。
旁白:避免所有 >&3 重定向
在任一 shell 中,以下内容同样有效:
{
echo "start filelist:"
printf '%s\n' "$PWD"/*
echo "start wget:"
wget -nv http://web.site.com/downloads/2017/file_1.zip 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip 2>&1
} >&3
可能,但不建议将代码块通过管道传输到管道中,从而完全取代 FIFO 使用或进程替换:
{
echo "start filelist:"
printf '%s\n' "$PWD"/*
echo "start wget:"
wget -nv http://web.site.com/downloads/2017/file_1.zip 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip 2>&1
} | tee -a "$logfilename" | jq -sRf json_log_s2o.jq >>"${logfilename}.json"
...为什么不建议?因为在 POSIX sh 中无法保证管道的哪些组件(如果有的话)运行 在与脚本的其余部分相同的 shell 解释器中;如果上面的 不是 运行 在同一段脚本中,那么变量将被丢弃(并且没有 pipefail
等扩展名,退出状态以及)。有关详细信息,请参阅 BashFAQ #24。
等待退出 Bash 4.4
在 bash 4.4 中,进程替换在 $!
中导出它们的 PID,这些可以被 wait
编辑。因此,您可以使用另一种方法来等待 FIFO 退出:
exec 3> >(tee -a "$logfilename" | jq -sRf json_log_s2o.jq >>"$logfilename.json"); log_pid=$!
...然后,稍后:
wait "$log_pid"
作为先前给出的 flock
方法的替代方法。显然,仅当您有 bash 4.4 可用时才执行此操作。
我有多个管道,看起来像:
tee -a $logfilename.txt | jq string2object.jq >> $logfilename.json
或
tee -a $logfilename.txt | jq array2object.jq >> $logfilename.json
对于每个管道,我想应用于多个命令。
每组命令看起来像:
echo "start filelist:"
printf '%s\n' "$PWD"/*
或
echo "start wget:"
wget -nv http://web.site.com/downloads/2017/file_1.zip 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip 2>&1
这些命令的输出都应该通过管道。
我过去尝试过的是将管道分别放在每个命令上:
echo "start filelist:" | tee -a $logfilename | jq -sRf array2object.jq >>$logfilename.json
printf '%s\n' "$PWD"/* | tee -a $logfilename | jq -sRf array2object.jq >>$logfilename.json
但在那种情况下,JSON 脚本一次只能看到一行,因此无法正常工作。
便携式方法
以下可移植到 POSIX sh:
#!/bin/sh
die() { rm -rf -- "$tempdir"; [ "$#" -gt 0 ] && echo "$*" >&2; exit 1; }
logfilename="whatever"
tempdir=$(mktemp -d "${TMPDIR:-/tmp}"/fifodir.XXXXXX) || exit
mkfifo "$tempdir/fifo" || die "mkfifo failed"
tee -a "$logfilename" <"$tempdir/fifo" \
| jq -sRf json_log_s2o.jq \
>>"$logfilename.json" & fifo_pid=$!
exec 3>"$tempdir/fifo" || die "could not open fifo for write"
echo "start filelist:" >&3
printf '%s\n' "$PWD"/* >&3
echo "start wget:" >&3
wget -nv http://web.site.com/downloads/2017/file_1.zip >&3 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip >&3 2>&1
exec 3>&- # close the write end of the FIFO
wait "$fifo_pid" # and wait for the process to exit
rm -rf "$tempdir" # delete the temporary directory with the FIFO
避免 FIFO 管理(使用 Bash)
使用 bash,可以避免使用进程替换来管理 FIFO:
#!/bin/bash
logfilename="whatever"
exec 3> >(tee -a "$logfilename" | jq -sRf json_log_s2o.jq >>"$logfilename.json")
echo "start filelist:" >&3
printf '%s\n' "$PWD/*" >&3
echo "start wget:" >&3
wget -nv http://web.site.com/downloads/2017/file_1.zip >&3 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip >&3 2>&1
exec 3>&1
等待退出(使用 Linux-y 工具)
然而,这个不让你做的事情(没有bash 4.4)是检测jq
何时失败,或者等待jq
在脚本退出之前完成写入。如果你想确保 jq
在你的脚本退出之前完成,那么你可以考虑使用 flock
,像这样:
writelogs() {
exec 4>".json"
flock -x 4
tee -a "" | jq -sRf json_log_s2o.jq >&4
}
exec 3> >(writelogs "$logfilename")
及以后:
exec 3>&-
flock -s "$logfilename.json" -c :
因为 writelogs
函数中的 jq
进程锁定了输出文件,最终的 flock -s
命令无法 also 锁定输出文件直到 jq
退出。
旁白:避免所有 >&3 重定向
在任一 shell 中,以下内容同样有效:
{
echo "start filelist:"
printf '%s\n' "$PWD"/*
echo "start wget:"
wget -nv http://web.site.com/downloads/2017/file_1.zip 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip 2>&1
} >&3
可能,但不建议将代码块通过管道传输到管道中,从而完全取代 FIFO 使用或进程替换:
{
echo "start filelist:"
printf '%s\n' "$PWD"/*
echo "start wget:"
wget -nv http://web.site.com/downloads/2017/file_1.zip 2>&1
wget -nv http://web.site.com/downloads/2017/file_2.zip 2>&1
} | tee -a "$logfilename" | jq -sRf json_log_s2o.jq >>"${logfilename}.json"
...为什么不建议?因为在 POSIX sh 中无法保证管道的哪些组件(如果有的话)运行 在与脚本的其余部分相同的 shell 解释器中;如果上面的 不是 运行 在同一段脚本中,那么变量将被丢弃(并且没有 pipefail
等扩展名,退出状态以及)。有关详细信息,请参阅 BashFAQ #24。
等待退出 Bash 4.4
在 bash 4.4 中,进程替换在 $!
中导出它们的 PID,这些可以被 wait
编辑。因此,您可以使用另一种方法来等待 FIFO 退出:
exec 3> >(tee -a "$logfilename" | jq -sRf json_log_s2o.jq >>"$logfilename.json"); log_pid=$!
...然后,稍后:
wait "$log_pid"
作为先前给出的 flock
方法的替代方法。显然,仅当您有 bash 4.4 可用时才执行此操作。