如何发送多个命令'输出到单个shell管道?

时间:2017-05-17 09:40:17

标签: bash shell unix exec jq

我有多个管道,如下所示:

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脚本一次只能看到一行,因此它无法正常工作。

1 个答案:

答案 0 :(得分:4)

便携式方法

以下内容可移植到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工具)

然而,当jq失败或等待jq在您的脚本之前完成写入时,会检测到让您(没有bash 4.4)的事情退出。如果您希望在脚本退出之前确保jq完成,那么您可以考虑使用flock,如下所示:

writelogs() {
  exec 4>"${1}.json"
  flock -x 4
  tee -a "$1" | jq -sRf json_log_s2o.jq >&4
}
exec 3> >(writelogs "$logfilename")

以后:

exec 3>&-
flock -s "$logfilename.json" -c :

由于jq函数中的writelogs进程会对输出文件产生锁定,因此最终flock -s命令无法同时获取锁定输出文件,直到jq退出。

Aside:避免所有&gt;&amp; 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的情况下才能这样做。

相关问题