出现管道错误时,使用Bash单行方式保留原始管道输出文件?

时间:2016-12-13 03:22:32

标签: linux bash

使用中间文件和函数或追加/截断有很多方法可以做到这一点,但我很好奇是否有人知道如何表达条件非破坏性覆盖的单行方式如下:

# outfile not touched if script1 fails
script1  > outfile
# outfile not touched if script1 or script2 fails
script1 | script2 > outfile
# or maybe a "lazy >" command, but I'd rather see how bash itself can do this
script1 | script2 | magicproxy outfile

其中outfile的先前内容保持不变如果其管道无法输出任何有效的流内容。

如果某些输出流,可以搞砸outfile,但如果没有输出流则不行。

我不知道有什么打击方式来解释这一点,因为我相当肯定它不会在与管道标准输出文件有关的基本文件设置之前看到输出流。除非有潜伏的魔法套装 - 懒惰选项。

因此,我正在寻找一种单行方式,使管道比基本的stdout文件设置更加懒惰,在第一次实际输出"触发",使用bash约定。

1 个答案:

答案 0 :(得分:2)

未来读者的快速注释

这里的原始问题仅询问在修改输出文件之前是否等待至少一行输出通过管道。这与不一样确保管道在覆盖目标文件之前以非零状态退出,但是部分"取决于管道成功"已添加有关该方法的详细信息。

取决于管道成功

不幸的是,这并不像单行一样容易写。

shopt -s pipefail # fail if any component of a pipeline doesn't succeed

tempfile=$(mktemp "$1".XXXXXX)
if script1 | script2 >"$tempfile"; then
  mv -- "$tempfile" "$1"
else
  rm -f -- "$tempfile"
fi

一种显而易见的方法是使用eval,但这需要非常小心以避免安全漏洞:

# DANGER: command_source *must* be a constant string; MUST NOT substitute argument values
#         directly into source -- instead, refer to variables from that string.
eval_to_output_file() {
  local command_source destfile tempfile retval
  command_source=$1
  destfile=$2
  tempfile=$(mktemp -- "$destfile.XXXXXX")
  if eval "$command_source" >"$tempfile"; then
    mv -- "$tempfile" "$destfile"
  else
    retval=$?
    rm -f -- "$tempfile"
    return "$retval"
  fi
}

...用法:

# CRITICAL that script argument is single-quoted, and all arguments expanded by eval
# ...otherwise, expansions can perform code injection.
eval_to_output_file 'script1 "$arg1" | script2 "$arg2"' outfile

更安全的是,管道可以封装在一个函数中:

# Safer alternative to eval_to_output_file
# Requires that a pipeline be encapsulated into a function
run_to_output_file() {
  local destfile retval
  destfile=$1; shift
  tempfile=$(mktemp -- "$destfile.XXXXXX")
  if "$@" >"$tempfile"; then
    mv -- "$tempfile" "$destfile"
  else
    retval=$?
    rm -f -- "$tempfile"
    return "$retval"
  fi
}

# example of a function running your pipeline
# note that arguments are passed through by the above
myfunc() (          # myfunc() ( ) instead of myfunc() { } means execute in a subshell
  shopt -s pipefail # because we're in a subshell, this won't propagate out
  script1 "$1" | script2 "$2"
)

run_to_output_file outfile myfunc arg1 arg2

仅等待第一行

magicproxy() {
  [[ $1 ]] || { echo "Usage: magicproxy filename" >&2; return 1; }

  if IFS= read -r first_line; then
    cat <(printf '%s\n' "$first_line") - >"$1"
  fi
}

请注意,这意味着您的输出文件在某些​​时候将存在部分内容。

在整个流完成后重命名

magicproxy() {
  [[ $1 ]] || { echo "Usage: magicproxy filename" >&2; return 1; }

  local tempfile
  tempfile=$(mktemp -- "$1.XXXXXX") || return
  if cat >"$tempfile" && [[ -s "$tempfile" ]]; then
    mv -- "$tempfile" "$1"
  else
    rm -f -- "$tempfile"
  fi
}

...将适用于您提议的管道:

script1 | script2 | magicproxy outfile

也就是说,如果您使用的是GNU系统并且mktemp使用的限制权限不适合您,您可能还想添加:

# give your temporary file the same permissions as your destination
chmod --reference="$1" -- "$tempfile"

... mv之前。