ファイルの特定部分のフィルタリングまたはパイプ

ファイルの特定部分のフィルタリングまたはパイプ

開始タグと終了タグで区切られたいくつかのセクションを含む入力ファイルがあります。たとえば、次のようになります。

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

nlX、Y、Z行は特定のコマンド(たとえば)でフィルタリングされますが、残りの行は変更されないようにこのファイルに変換を適用したいと思います。 (Number Line)はnl複数行にわたって状態を累積するため、各行X、Y、Zに適用される静的変換ではありません。 (編集するnl:累積状態が不要なモードでも作業が可能だと誰か指摘したが、nl問題を単純化するために例として使用しただけです。実際、このコマンドはより複雑なカスタムスクリプトです。私が本当に探しているのは、入力ファイルのサブセクションに標準フィルタを適用する問題に対する一般的な解決策です。)

出力は次のようになります。

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

ファイルには変換する必要があるいくつかのセクションがあります。

アップデート2最初は、いくつかの部分がある場合、何が起こるかを指定しませんでした。たとえば、次のようになります。

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

私の期待は、次のような場合、与えられたセクション内でのみ状態を維持する必要があるということです。

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

しかし、私はこの質問をさまざまな部分にわたって状態を保存する必要があると解釈することが多くの場合、妥当で有用であると思います。

アップデート 2 終了

私の最初のアイデアは、私たちがどのセクションにあるかを追跡するための単純なステートマシンを構築することでした。

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

私はそれを次のように実行します:

cat test-inline-codify | ./inline-codify

各呼び出しは独立しているため動作しないため、nl行番号は増加しません。

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

次の試みはfifoを使用することでした。

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

これは正しい出力を提供しますが、順序が間違っています。

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

一部のキャッシュが進行中である可能性があります。

これらすべてについて私は間違っていますか?これは非常に一般的な問題のようです。この問題を解決するには、単純なパイプラインが必要であると思います。

答え1

私はあなたの意見に同意します - おそらくはい一般的な質問です。ただし、一部の汎用ユーティリティには、これを処理するためのいくつかの機能があります。


nl

nlたとえば、入力を次に分割します。論理ページ-d2文字で区切られていますセクション区切り記号。 1行に3回発生すると、個別にイベントの開始を示します。タイトル、両方そして歩行者。入力で見つかったこれらのうちの1つを出力の空白行に置き換えます。これは印刷される唯一の空白行です。

他の部分を含むように例を変更します./infile

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

その後、次のコマンドを実行しました。

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlと言える累積状況論理ページ全体に渡っていますが、デフォルトではそうではありません。代わりに、次のように入力行の番号を付けます。スタイル部分。つまり、-ha数字はすべて意味します。ヘッダー-bnと手段ボディラインがない-始めるから状態。

nlこれを学ぶ前にすべての入力に使用しましたが、基本的なリミッターによって出力が歪む可能性nlがあることに気づいた後、より注意深く使用する方法を学び、テストされていない入力に使用し始めました。しかし、その日に学んだもう一つのレッスンは、上記のように入力を少しだけ変更すると、他の側面(この項目など)に非常に便利に適用できることです。-d\:grep -nF ''nlsed

出力

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

詳しくはこちらをご覧くださいnl。番号付きの行を除く上記のすべての行が空白で始まるのを見ましたか?数値行の場合は、nl各行の先頭に特定の数の文字を挿入します。この行の場合、番号は付けられません。空白の場合でも、常に(idth count + eparator len)*空白を番号のない行の頭に挿入してインデントと-w一致します。-sこれにより、番号付けされたコンテンツと比較して、番号のないコンテンツを多くの労力なしに正確に再現できます。これはnl、入力を論理部分に分割し、番号付きの各行の先頭に任意の文字列を挿入できることを考慮すると、-s出力処理が非常に簡単になります。

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

上記の印刷物...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

牛に似た一種の栄養sed

nlターゲットアプリケーションではない場合、GNUは一致に基づいて任意のシェルコマンドを実行sedできます。e

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

上記のコードはestを置き換え、abelで再び牧草地を停止し、sed成功するのに十分な入力があるまでパターンスペースから入力を収集します。実行されると、ここに文書で示されている入力を使用して、残りのパターン空間をすべて実行します。Tb:lenl<<

ワークフローは次のとおりです。

  1. /^@@.*start$/!b
    • ^行全体が上記のパターンと一致しない場合は、スクリプトから削除さ$!、自動的に印刷されます。したがって、これからはこのパターンで始まる一連の行だけを処理します。//b
  2. s//nl <<\\@@/
    • 空のフィールドs//は、/最後にsed試行された一致を示します。したがって、このコマンドは行@@.*start全体を置き換えますnl <<\\@@
  3. :l;N
    • この:コマンドは分岐ラベルを定義します。ここでは、:labelというラベルを設定します。 ext コマンドは、Nパターン空間に次の入力行を追加し、その後に\newline 文字を追加します。これは\nパターン空間でewlineを取得する唯一の方法の1つです。 ewline文字は、sedしばらくこの操作を実行してきたderの\n明確な区切り文字です。sed
  4. s/\(\n@@\)[^\n]*end$/\1/
    • このs///置換は、一定時間が経過した後にのみ成功します。スタート向き合い、また初めて向き合った終わりワイヤー。これは、パターンスペースの終わりを\n示す最後の改行の直後のパターンスペースにのみ作用します。うまくいけば、一致する文字列全体を最初のグループに置き換えます。@@.*end$\1\(\)\n@@
  5. Tl
    • est コマンドはTラベルに分岐します。(提供された場合)入力ラインがパターン空間に最後に引っ張られてから正常な置換が発生しなかった場合(私がそうしたようにN\nこれは、閉じ区切り文字と一致しないパターン空間にewlineが追加されるたびに、estTコマンドが失敗し、abelに再分岐し、ext行が追加され、:l成功するまで繰り返されることを意味します。sedN
  6. e

    • T一致を終了する置換が成功し、スクリプトが失敗したestに再分岐しない場合は、sed次のコマンドが実行されますel

      nl <<\\@@\nline X\nline Y\nline Z\n@@$
      

最後の行を編集して、次のように見えるように直接確認できますTl;l;e

次のように印刷されます。

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

最後の方法であり、おそらく最も簡単な方法はを使用することですが、while readその理由があります。殻 -(最も珍しい点はbash殻です)- 大規模または安定した量の入力を処理することはしばしば非常に失敗します。これも意味があります。シェルの仕事は、入力文字を文字ごとに処理し、より大きな項目を処理できる他のコマンドを呼び出すことです。

しかし、その役割の重要な点はシェルです必然ではない read入力が多すぎます。次のように指定されます。いいえ過度に消費したり、時間に十分に渡されないため、呼び出し元のコマンドのバイトが足りなくなるまで入力または出力をバッファリングします。したがって、read優れた入力を提供できます。テストreturn- 残りの入力があるかどうかについての情報を読むには、次のコマンドを呼び出す必要がありますが、通常は最善の方法ではありません。

ただし、以下は使用方法の例です。read そして入力を同期的に処理するその他のコマンド:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

繰り返すたびに最初に起こるのは、read線を引くことです。成功すると、ループがEOFに達していないことを意味するため、case一致する前にスタート区切り文字doブロックはすぐに実行されます。そうでない場合は、printf印刷して$line電話をread受けてくださいsed

sedp会うまで各行を印刷します。スタートqマークアップ - 入力内容に正確に収まる場合。 nbuffered スイッチは-uGNU に必要です。sedそうしないと、非常に貪欲にバッファリングできるからです。ただし、仕様によれば、他のPOSIXは通常のファイルである限り、sed特別な考慮事項なしに動作する必要があります。<infile

最初のsed quitが実行されると、シェルはdoループブロックを実行します。別のループブロックを呼び出して、sed会うまで各行を印刷します。終わり表示。pasteその行の行番号を印刷するときに出力をパイプします。このように:

1
line M
2
line N
3
line O

pasteその後、文字に貼り付けると、:全体の出力は次のようになります。

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

これは単なる例です。ここではテストやdoブロックで何でもできますが、最初のユーティリティはあまりにも多くの入力を消費しないでください。

関連するすべてのユーティリティは、同じ入力を順次読み込み、結果を印刷します。この種の作業は理解しにくい場合があります。他のユーティリティは他のユーティリティよりもバッファリングを多くするため、通常、ddおよびheadを使用sedして正しい操作を実行できます。(ただし、GNUの場合はsedcliスイッチが必要です)常に頼ることができるはずですread。本質的にそうです。非常に遅い。これが、上記のループが入力ブロックごとに一度だけ呼び出される理由です。

答え2

1つの可能性は、vimテキストエディタを使用してこれを行うことです。シェルコマンドで任意の部品を転送できます。

1つの方法は行番号を使用することです:4,6!nl。この ex コマンドは、行 4-6 (含む) で nl を実行して、サンプル入力で望ましい効果を得ます。

別の対話型の方法は、行選択モード(shift-V)と矢印キーを使用または検索して適切な行を選択してからを使用することです:!nl。たとえば、入力の完全なコマンド順序は次のとおりです。

/@@inline-code-start
jV/@@inline-code-end
k:!nl

これは自動化にはあまり適していませんが(たとえば、sedを使用する方が良いですが)、20行のシェルスクリプトに頼らずにワンタイム編集に非常に役立ちます。

vi(m)を初めて使用する場合は、少なくともこれらの変更の後に:wq

答え3

私が考えることができる最も簡単な解決策は、使用するのではなく、nl直接行数を数えることです。

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

次に、そのファイルで実行します。

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

答え4

編集するユーザー提供のフィルタを定義するオプションが追加されました。

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

デフォルトのフィルタは「nl」です。フィルタを変更するには、「-p」オプションといくつかのユーザー指定のコマンドを使用します。

codify -p="wc" file

または

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

最後のフィルタは以下を出力します。

line A
line B
 ╓─
 ║ line X
 ║ line Y
 ║ line Z
 ╙─
line C
line D

アップデート1 IPC::Open2 の使用にはサイズ変更の問題があります。バッファサイズを超えるとブロックされる可能性があります。 (マイコンピュータで64Kの場合、パイプバッファサイズは10_000 x "Y行"に対応します)。

より大きいものが必要な場合(10,000本の「Y線」が必要です):

(1) 設置・使用use Forks::Super 'open2';

(2) または Pipelineit 関数を次に置き換えます。

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

関連情報