パイプラインの中央にある行数を計算する方法

パイプラインの中央にある行数を計算する方法

パイプラインの行数を計算し、結果に基づいてパイプラインを続けたいと思います。

頑張った

x=$(printf 'faa\nbor\nbaz\n' \
  | tee /dev/stderr | wc -l) 2>&1 \
  | if [[ $x -ge 2 ]]; then
      grep a
    else
      grep b
    fi

しかし、まったくフィルタリングしません(「a」も「b」でもありません)。少なくとも、次は期待通りに動作するので、これは非常に予期しないことです。

printf 'faa\nbor\nbaz\n' | if true; then grep a; else grep b; fi
printf 'faa\nbor\nbaz\n' | if false; then grep a; else grep b; fi

(bashでは)機能しないため、内部コマンドの置き換えでstderrをリダイレクトできないようです。 3行すべてを印刷します。

x=$(printf 'faa\nbor\nbaz\n' | tee /dev/stderr | wc -l) 2>&1 | grep a

zsh では 2 行だけ印刷します。

しかし、両方のシェルでは、変数xはパイプの後ろに設定されず、パイプの後半にも設定されません。

パイプラインの行数を計算し、その数に基づいてアクションを実行するにはどうすればよいですか?一時ファイルを避けたい。

ベストアンサー1

このコメントそれは真実です:

パイプラインの各部分は、同じパイプラインの他の部分とは独立して開始されます。つまり$x、他のステップのいずれかに設定すると、パイプラインの途中で使用できなくなります。

これはあなたが何もできないことを意味しません。パイプは基本データチャネルと見なすことができ、プロセスはファイル、名前付きfifo、または他のすべてのサイドチャネルを使用してまだ通信できます(時にはブロックしないように注意が必要です)。

後で行数を計算し、データストリーム全体を条件付きで処理しようとしています。これは、ストリーム全体を配信する前にストリームの終わりに到達する必要があることを意味します。したがって、何らかの方法でストリーム全体を保存する必要があります。一時ファイルは合理的なアプローチのように見えます。パイプを少なくとも2つの部分に分割する必要があります。最初の部分はデータをファイルに保存する必要があります。その後、行数を計算する必要があります(これは最初の部分に属することができると思います)。その後、最後の部分は数字を取得してファイルを読み取る必要があります。開始し、それに応じて行動します。


一時ファイルを避けるには、パイプラインの一部が同じでなければなりませんsponge。バイパスを防ぐには、行番号を出力の最初の行に渡す必要があり、残りのパイプラインはこのプロトコルを理解する必要があります。

次のコマンドを検討してください。

sed '$ {=; H; g; p;}; H; d'

予約済みスペースにラインを蓄積します。 1つ以上の行がある場合は、最後の行を受け取った後に行番号が印刷され、sedその後に空の行と実際の入力が表示されます。

空行は不要ですが、この単純なコードから「自然に」出てきます。私はそれを避けようとせず、sed後でパイプラインで処理します(例sed '2 d'

使用例:

#!/bin/sh

sed '$ {=; H; g; p;}; H; d' | sed '2 d' | {
   if ! IFS= read -r nlines; then
      echo "0 lines. Nothing to do." >&2
   else
      echo "$nlines lines. Processing accordingly." >&2
      if [ "$nlines" -ge 2 ]; then
         grep a
      else
         grep b
      fi
   fi
}

メモ:

  • IFS= read -r最初の行は、明確に定義されており、一意の数値が含まれているか存在しないため、過剰です。
  • 私はそれを使用しました/bin/sh。このコードはBashでも実行されます。
  • sedどんな量のデータも保存できると仮定することはできません。POSIX仕様説明する:

    パターンスペースとホールドスペースの両方が少なくとも8192バイトを収容できる必要があります。

    したがって、制限は8192バイトしかできません。一方、一時ファイルには1TBのデータを簡単に保存できると想像できます。どのような対価を取っても一時ファイルを避けないでください。


タイトルには「行数の計算」と呼ばれていますが、例ではその数が2以上(通常はN以上)であることを確認したいと思います。これらの質問は同等ではありません。 2行目(N)行を入力すると、後者の質問に対する答えがわかり、行まで無限に表示されます。上記のコードは未定義の入力を処理できません。ある程度直すようにします。

sed '
7~1 {p; d}
6 {H; g; i \
6+
p; d}
$ {=; H; g; p}
6! {H; d}
'

このコマンドは、6行に達したときに行番号を仮定(印刷)することを除いて、以前の解決策と同じように機能します6+。その後、この行が印刷され、次の行(存在する場合)が表示されるとすぐに印刷されます(cat同様の動作)。

使用例:

#!/bin/sh

threshold=6

sed "
$((threshold+1))~1 {p; d}
$threshold {H; g; i \
$threshold+
p; d}
$ {=; H; g; p}
${threshold}! {H; d}
" | sed '2 d' | {
   if ! IFS= read -r nlines; then
      echo "0 lines. Nothing to do." >&2
   else
      echo "$nlines lines. Processing accordingly." >&2
      if [ "$nlines" = "$threshold+" ]; then
         grep a
      else
         grep b
      fi
   fi
}

メモ:

  • sed(あなたの場合は制限が何であれ)制限はまだ適用されるため、「ある程度」修正されました。ただし、処理できるsed最大$threshold行数は$threshold十分です。
  • サンプルコードはテスト用ですが、$threshold+プロトコルを使用すると、0、1、2、...、しきい値減算1、しきい値を超える行を区別できます。

私はそれをうまくできませんsed。私のsedコードを単純化できる場合は、コメントに1行ずつ残してください。

おすすめ記事