以下是一个非常简单的示例,说明了“两遍脚本”的含义:
#!/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
我对这个问题的答案很感兴趣bash
和zsh
。
答案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
,ksh93
或perl
/ 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
的输出cat
是tee
ed 到两者grep
并且grep -v | sort
也应该有效。用于4>&1
保证 在完成写入sort
之前不会开始输出grep
(因为它还保持管道grep -v
在运行时打开)。
答案2
只是sort
您想要排序的输出部分。grep -E '^#' "$INPUTFILE";(grep -E -v '^#' "$INPUTFILE" | sort )