web-dev-qa-db-ja.com

「2パス」スクリプトは、ファイルまたはstdinからの入力の読み取りをどのようにサポートできますか?

以下は、「2パススクリプト」の意味する非常に単純な例です。

#!/bin/bash

INPUTFILE=$1

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

このスクリプト(twopass.shと呼びます)は、ファイルへのパスINPUTFILEを唯一の引数として受け取ります。次に、最初に#で始まるINPUTFILEのすべての行を元の順序で出力します。そして、次に、ソートされた順序でnotを実行する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

alsostdinに対して同じ操作を実行できるように、このスクリプトを変更するにはどうすればよいですか?

つまり、目的の新しいバージョンのスクリプトを使用すると、以下の行で上記と同じ出力が生成されます。

./twopass.sh < example.txt

bashzshの両方について、この質問への回答に興味があります。

3
kjo

一般的なケースでは、stdinを複数回処理できるようにするには、最初の読み取り後にシークして再度読み取ることができるようにする必要があります(パイプなどのすべてのタイプのファイルでは不可能です)。ソケット、ターミナル)、またはその入力を通常のファイルまたはメモリに保存し、複数回読み取ることができることがわかっている場合。

組み込みのシークとzshやksh93などの一時ファイル管理サポートを備えたシェルを使用すると、より簡単です。

_#! /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_はGNU grep拡張子です。grepがそれをサポートしていない場合は、 _cat -- "$@" | grep ..._)を使用します。

bashは一時ファイルのシークや作成をサポートしていませんが、zsh、_ksh93_またはPerl/pythonを呼び出すことができます。そのため。

ただし、特定のユースケースでは、次のようにすることもできます。

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

_-e_文字を含むファイル名を処理するには、_-E_ + _=_トリックが必要でした(_-_引数は、gawkによって引き続き解釈されます) stdinを意味し、_-_というファイルではありません)。

上記のソートされた出力は、コメントが表示されることが保証されていますsortが読み取る必要があるコメントallそのコメント何かを出力する前に入力してください。 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の出力がteeedでgrepと_grep -v | sort_の両方に出力される場合も機能します。 _4>&1_は、sortが書き込みを完了する前にgrepが出力を開始しないことを保証するために使用されます(実行中に_grep -v_へのパイプも開いているため)。

3