“两遍”脚本如何支持从文件或标准输入读取输入?

“两遍”脚本如何支持从文件或标准输入读取输入?

以下是一个非常简单的示例,说明了“两遍脚本”的含义:

#!/bin/bash

INPUTFILE=$1

grep    '^#' "$INPUTFILE"
grep -v '^#' "$INPUTFILE" | sort

该脚本(我们称之为)将文件的twopass.sh路径作为其唯一参数。然后,它将首先按原始顺序打印出以开头的INPUTFILE所有行。其次,它会打印INPUTFILE#按排序顺序里面的所有行都INPUTFILE不是首先#

例如,如果文件example.txt包含以下行

# foo comes first
# bar comes second
# baz comes third
wobble
quux
wibble
frobozz

...然后将twopass.sh脚本应用到它应该产生以下结果:

% ./twopass.sh example.txt
# foo comes first
# bar comes second
# baz comes third
frobozz
quux
wibble
wobble

我怎样才能修改这个脚本,以便它可以stdin对?执行相同的操作

换句话说,使用所需的新版本脚本,下面的行应该产生与上面所示相同的输出:

./twopass.sh < example.txt

我对这个问题的答案很感兴趣bashzsh

答案1

在一般情况下,为了能够多次处理 stdin,您需要能够在第一次读取后回溯以便能够再次读取它(这对于所有类型的文件来说都是不可能的,例如管道,套接字、终端)或将输入存储到常规文件或内存中,您知道可以多次读取它。

使用具有内置搜索和临时文件管理支持(如 zsh 或 ksh93)的 shell 会更容易。

#! /bin/zsh -
zmodload zsh/system || exit

if (($#)); then
  # arguments are provided. They are assumed to be file arguments
  # to process (use ./- for the file called -)
  grep -h -- '^#' "$@"
  grep -vh -- '^#' "$@" | sort
else
  # process stdin
  if (( (pos = systell(0)) >= 0 )); then
    # input is seekable
    grep '^#'
    sysseek $pos || {
      syserror -p "Cannot go back: "
      exit 1
    }
    grep -v '^#' | sort
  else
    # not seekable, store input in a temporary file using =(cat)
    () {
      grep -- '^#' $1
      grep -v -- '^#' $1
    } =(cat)
  fi
fi

(请注意,-h跳过输出文件名是 GNUgrep扩展;如果您grep不支持它,可以将其替换为cat -- "$@" | grep ...)。

bash不支持查找或创建临时文件,但您可以让它调用zsh,ksh93perl/ python

不过,对于您的特定用例,您也可以这样做:

#! /bin/sh -
gawk -e '
  /^#/ {print; next}
  {print | "sort"}' -E /dev/null "$@"

-e+技巧-E需要能够处理包含=字符的文件名(请注意,-参数仍然被解释为gawk意味着 stdin,而不是名为 的文件-)。

保证显示上面排序的输出sort需要阅读的评论全部在开始输出任何内容之前它的输入。sort将数据保存在内存或临时文件中。

方法如下:

#! /bin/zsh -
{ cat -- "$@" > >(grep '^#' 4>&1 >&3) | grep -v '^#' | sort; } 3>&1

或者与 ksh93 或 bash 兼容:

{
  cat -- "$@" |
   { tee >(grep '^#' 4>&1 >&3); } |
   grep -v '^#' |
   sort
} 3>&1

的输出catteeed 到两者grep并且grep -v | sort也应该有效。用于4>&1保证 在完成写入sort之前不会开始输出grep(因为它还保持管道grep -v在运行时打开)。

答案2

只是sort您想要排序的输出部分。grep -E '^#' "$INPUTFILE";(grep -E -v '^#' "$INPUTFILE" | sort )

相关内容