ファイルの特定のセクションをフィルターまたはパイプする


14

入力ファイルにはいくつかのセクションがあり、開始タグと終了タグで区切られています。次に例を示します。

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

このファイルに変換を適用して、行X、Y、Zを何らかのコマンド(nlなど)でフィルター処理し、残りの行は変更せずに通過させます。nl(number行)は行をまたいで状態を蓄積するため、行X、Y、Zのそれぞれに適用されるのは静的な変換ではないことに注意してください。(編集:それがあることが指摘されたnlモードでの缶の仕事が蓄積状態を必要としないが、私はちょうど使用していますnl質問を簡素化するための例として、実際にはコマンドは、より複雑なカスタムスクリプトです。。私が本当に探していますどのようなforは、入力ファイルのサブセクションに標準フィルターを適用する問題の一般的な解決策です

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

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

おそらくいくつかのキャッシングが進行中です。

私はこれについてすべて間違っていますか?これはかなり一般的な問題のようです。これを解決するシンプルなパイプラインが必要だと思います。


nl状態蓄積する必要はありません。セクション区切り文字の情報についてはnl -dman/ infoページを見て確認してください。nl
mikeserv

nlは単なる例です。私の場合、nlではなくカスタムスクリプトを実行しています。
ジェームズスクリベン

その場合は、スクリプトの実行内容を明確にしてください。
テルドン

質問の中で、nlフィルターの例としてのみ使用していることを明確にしました。フィルターが正確に何をしていたのかを詳細に説明することで質問を簡素化できると思いましたが、おそらくもっと混乱を招きました。実際、私は、独自の静的ブログジェネレーターのために、コードハイライターを通してサブセクションをフィルター処理しています。現在、私はgnuを使用してsource-highlightいますが、それは変更される可能性があり、フォーマッターなどのフィルターを追加する可能性があります。
ジェームズスクリ

回答:


7

私はあなたと同意するだろう-それはおそらくある一般的な問題。ただし、一部の一般的なユーティリティには、それを処理するための機能があります。


nl

nlたとえば、入力を2文字のセクション区切り文字で区切られた論理ページ-d分割します。1行に3つだけ出現すると、見出しの開始を示します。2つは本文、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 ''代わりにテストされていない入力に使用することを学びました。しかし、別の教訓nlは、sed上記のように、入力を少しだけ変更すれば、この日など、他の点で非常に便利に適用できることを学んだことです。

出力

  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 -wcount + -separator len)*スペースを挿入することにより、常にインデントと一致します。これにより、番号の付いていないコンテンツを番号の付いたコンテンツと比較することで、わずかな労力で正確に再現できます。あなたはそれが考えるときにnlあなたのための論理的なセクションにその入力を分割し、あなたが任意の挿入できるという-s番号を各行の先頭にそれをtringsを、それは、その出力を処理するために非常に簡単取得します。

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

GNU sed

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

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

上記のsedパターンスペースに集まる入力、それが正常に置換通過するのに十分になるまでT、ESTと停止bに牧場バック:lアベルを。それはありません、それはときexecutes nl入力してAのように表さ<<、パターンスペースの残りすべてのヒアドキュメント。

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

  1. /^@@.*start$/!b
    • ^行全体が上記のパターンに一致$!ない場合は、スクリプトから実行されて自動印刷されます。したがって、この時点からは、パターンで始まる一連の行のみを使用します。//b
  2. s//nl <<\\@@/
    • 空のs//フィールド/は、最後sedに一致しようとしたアドレスを表します。したがって、このコマンド@@.*startnl <<\\@@代わりに行全体を置き換えます。
  3. :l;N
    • この:コマンドはブランチラベルを定義します-ここでは:label という名前を設定します。NEXTコマンドが続くパターンスペースに入力の次の行を追加し\newline文字。これは\nsedパターンスペースでewline を取得する数少ない方法の1つです\n。ewline文字は、sedしばらくの間それを行っていたderの確実な区切り文字です。
  4. s/\(\n@@\)[^\n]*end$/\1/
    • このs///ubstitutionは、開始に遭遇した後、最初に続く終了行の発生時にのみ成功することができます。これは、最後の\newlineの直後にパターンスペースの@@.*end最後$をマークするパターンスペースに対してのみ機能します。動作すると、一致した文字列全体を\1最初の\(グループ\)、またはに置き換え\n@@ます。
  5. Tl
    • TラベルへのESTコマンド支店(提供された場合)成功した置換は、入力行をパターンスペースに引き込まれた最後の時間以降に発生していない場合(私は/ wがそうであるようにN。これは\n、終了区切り文字と一致しないパターンスペースにewlineが追加されるたびに、Testコマンドが失敗し、abelに分岐して戻り、ext行をプルして成功するまでループすること:lを意味します。sedN
  6. e

    • 置換は、エンドマッチは成功するために、スクリプトが失敗したため、分岐戻っていない場合はTESTは、sedeというコマンドをXECUTE lこのようooks:

      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$lineit readを出力してsed呼び出されます。

sedp、それが遭遇するまで、すべての行をRINT スタートマーカーを-それはときq完全に入力しuits。-unbufferedスイッチがGNUのために必要であるsed、それはそうでなければ、むしろ貪欲バッファリングすることができるので、しかし-スペックに応じ-他のPOSIX sed限り- Sは、特別な考慮せずに動作する必要があり<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

これらは単なる例です。ここでは、テストまたはブロックの実行のいずれでも実行できますが、最初のユーティリティはあまり多くの入力を消費してはいけません。

関係するすべてのユーティリティは、同じ入力を読み取り、結果を出力します。それぞれが順番に実行されます。別のユーティリティが他よりもバッファリングするため- -この種のことはのハングアップを取得することが困難なことはできますが、一般的に依存することができddheadおよびsed正しいことを行うために(GNUのために、かかわらずsed、あなたはcliのスイッチを必要とする)と、常に信頼できる必要がありますread-本来、非常に遅いためです。そして、それが上記のループが入力ブロックごとに1回だけ呼び出す理由です。


私はsedあなたが与えた2番目の例をテストしましたが、それは動作しますが、私は本当に構文を理解するのに苦労しています。(私のsedはかなり弱く、通常はs / findthis / replacethis / gに限定されます。座ってsedを本当に理解するために努力する必要があります。)
ジェームズ

@JamesScriven-わかりやすく説明するために編集しました。役に立たない場合は教えてください。また、コマンドを大幅に変更しました-小さく、より賢明な部分になりました。
mikeserv

4

1つの可能性は、vimテキストエディターでこれを行うことです。シェルコマンドを介して任意のセクションをパイプできます。

これを行う1つの方法は、行番号を使用すること:4,6!nlです。このexコマンドは4〜6行目でnlを実行し、入力例に必要なものを実現します。

別のよりインタラクティブな方法は、行選択モード(shift-V)と矢印キーまたは検索を使用して適切な行を選択し、次にを使用すること:!nlです。入力例の完全なコマンドシーケンスは次のようになります。

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

これは自動化にはあまり適していません(たとえばsedを使用した回答の方が適しています)が、1回限りの編集には20行のシェルスクリプトに頼る必要がないので非常に便利です。

vi(m)に慣れていない場合は、これらの変更後、を使用してファイルを保存できることを少なくとも知っている必要があります:wq


はい、vimは素晴らしいです!しかし、この場合、スクリプト可能なソリューションを探しています。
ジェームズスクリベン

@ JamesScriven、vimは十分に決定されていないのでスクリプト化できないと言う人。最初にプロジェクトディレクトリを作成し、そのディレクトリにホームディレクトリからすべてのvimスタートアップファイルをコピーします(修正しようとしている.vimrcとノイズで満たされている可能性のある.viminfoを除き、ln -sは正常に機能します)。作業を行う関数定義を新しい.vimrcファイルに追加し、vim asを呼び出しますHOME=$(pwd) vim -c 'call Mf()' f。xargsを使用している場合は、専用のxserverでgvimを使用して、ttyの破損を防ぐことができます(vncはビデオカードに依存せず、監視できます)。
15

うーん@hildred ...私はちょうど[XSendEvent](使用できませんでしtronche.com/gui/x/xlib/event-handling/XSendEvent.htmlのvimにシミュレートマウスクリックに)?
ジェームズスクリベン

2

私が考えることができる最も簡単な修正は、使用せずに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

どうもありがとう。質問を更新して、行の番号付けの特定の例ではなく、入力のサブセクションをフィルタリングする一般的なソリューションを探していることを明確にしました。おそらく、より良いコマンド例は「tac」(逆行)だったでしょう
ジェームズスクリベン

2

コードブロック全体を単一のプロセスインスタンスに送信することが目標である場合、コードブロックの最後に到達するまで行を蓄積し、パイピングを遅らせることができます。

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

これにより、テストケースを3回繰り返す入力ファイルに対して以下が生成されます。

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

コードブロックを使用して何か他のことを行うには(たとえば、リバースしてから番号を付けるなど)、それを他の何かにパイプしますecho -E "${acc:1}" | tac | nl。結果:

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

またはワードカウントecho -E "${acc:1}" | wc

line A
line B
      3       6      21
line C
line D

2

編集により、ユーザー指定のフィルターを定義するオプションが追加されました

#!/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の使用にはスケーリングの問題があります。buffersizeを超えるとブロックされる可能性があります。(私のマシンでは、64Kが10_000 x "line Y"に対応する場合のパイプバッファサイズ)。

より大きなものが必要な場合(10000個の「行Y」が必要ですか):

(1)インストールと使用 use Forks::Super 'open2';

(2)または関数pipeitを次のように置き換えます。

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

かっこいい。トリックは、行ごとに(再定義$/とフラグをs使用して)処理eしないことと、外部コマンドを実際に呼び出すためにフラグを使用することだと思います。私は2番目の(アスキーアート)の例が本当に好きです!
ジェームズスクリベン

私が気づいたのは、これはサブセクションの数千行を超えて拡大するようには見えないということです。これは、サブセクションを1つの大きなテキストブロックとして扱うことに関係していると思われます。
ジェームズスクリ

ありがとう。はい: `/ e` = eval; /s=( "。"は(.|\n)); $/レジスタ区切り文字を再定義します。
JJoao

@JamesScriven、あなたは正しいです(パイプがブロックされています)。私は何が起こっているかをテストしてみましょう...
JJoao

@JamesScriven、私の更新をご覧ください
...-JJoao

1

これはawkの仕事です。

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

スクリプトは開始マーカーを検出すると、にパイピングを開始する必要があることに注意しnlます。場合pipe変数は(ゼロ以外の)真であり、出力がにパイプされるnlコマンド。変数がfalse(未設定またはゼロ)の場合、出力は直接印刷されます。パイプで連結されたコマンドは、各コマンド文字列に対してパイプ構造が最初に検出されたときに分岐します。同じ文字列を使用したパイプ演算子の後続の評価では、既存のパイプが再利用されます。異なる文字列値は異なるパイプを作成します。このclose関数は、指定されたコマンド文字列のパイプを閉じます。


これは、名前付きパイプを使用するシェルスクリプトと本質的に同じロジックですが、綴り方がはるかに簡単で、クローズロジックは正しく実行されます。適切なタイミングでパイプを閉じて、nlコマンドを終了し、バッファーをフラッシュする必要があります。実際、スクリプトはパイプをあまりにも早く閉じますecho $line >myfifo。最初のパイプが実行を終了するとすぐにパイプが閉じられます。ただし、nl次回スクリプトが実行される前にタイムスライスを取得した場合、コマンドはファイルの終わりのみを認識しますecho $line >myfifo。大量のデータがある場合、またはsleep 1への書き込み後に追加する場合myfifoは、nl、それはその入力の終わりを見ているため、それが終了し、最初の行のみまたは行の最初の迅速な束を処理します。

構造を使用して、不要になるまでパイプを開いたままにする必要があります。パイプへの単一の出力リダイレクトが必要です。

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(また、適切な引用符などを追加する機会を得ました。なぜシェルスクリプトが空白文字やその他の特殊文字でチョークするのかを参照してください

その場合は、名前付きパイプではなくパイプラインを使用することもできます。

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done

あなたのawkソリューションは本当に素晴らしいです!これは、最も簡潔な(まだ非常に読みやすい)ソリューションだと思います。nlにパイプを再利用するawkの動作は保証されていますか、それともawkが「ねえ、あなたは今のところ十分にパイプしました。このパイプを閉じて新しいパイプを開きます」と決めることができますか?あなたの「パイプライン」ソリューションも本当に素晴らしいです。私はそれが少し混乱するかもしれないと思ったので、埋め込みwhileループを使用するアプローチを元々割引きましたが、あなたが持っているものは素晴らしいと思います。の前にセミコロンがありませんdo。(ここに小さな編集を行う担当者はいません。)
ジェームズスクリベン

1
...名前付きパイプソリューションを機能させることができませんでした。nlにパイプされたセクションが完全に失われるような競合状態があるようです。また、ffには2番目の@@ inline-code-start / endセクションがあり、常に失われます。
ジェームズスクリベン

0

OK、まずは。ファイルのセクションの行に番号を付ける方法を探しているのではないことを理解しています。フィルターの実際の例を示していないので(以外nl)、次のように仮定します。

tr "[[:lower:]]" "[[:upper:]]"

すなわち、テキストをすべて大文字に変換します。したがって、の入力に対して

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

あなたはの出力が必要です

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

解の最初の近似は次のとおりです。

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

@@文字列の前と最後の行の終わり近くのスペースはタブです。nl 私は自分の目的のために使用していることに注意してください。(もちろん、あなたの問題を解決するために行っていますが、行番号付きの出力を提供するためではありません。)

これにより、入力の行に番号が付けられるので、セクションマーカーで分割して、後で再び結合する方法を知ることができます。ループの本体は、セクションマーカーに行番号があるという事実を考慮して、最初の試行に基づいています。:それは離れて2つのファイルに入力を壊す file0;そして(いないセクションの非アクティブ)file1(;アクティブセクション)。上記の入力では、次のようになります。

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

次に、大文字化フィルタを実行しますfile1(セクション内のすべての行の連結です)。それをフィルター処理されていないセクション外の行と組み合わせます。ソートして、元の順序に戻します。そして、行番号を取り除きます。これにより、回答の上部に表示される出力が生成されます。

これは、フィルターが行番号のみを残すことを前提としています。そうでない場合(たとえば、行の先頭に文字を挿入または削除する場合)、この一般的なアプローチは引き続き使用できますが、少し複雑なコーディングが必要になります。


nlすでにほとんどの作業をそこで行っています-それがその-d区切り記号オプションの目的です。
mikeserv

0

sedを使用して非境界線のチャンクを出力し、境界線で区切られたチャンクをフィルタープログラムに入力するシェルスクリプト:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

このスクリプトをdetagger.shという名前のファイルに書き込み、次のように使用しました./detagger.sh infile.txt。問題のフィルタリング機能を模倣するために、別のfilter.shファイルを作成しました。

#!/bin/bash
awk '{ print "\t" NR " " $0}'

ただし、フィルタリング操作はコード内で変更できます。

私は 行の番号付けなどの操作で追加/内部のカウントを必要としないようにこれで一般的なソリューションの。このスクリプトは、境界タグがペアになっており、ネストされたタグを正常に処理しないことを確認するために、基本的なチェックをいくつか行います。


-1

すべての素晴らしいアイデアをありがとう。一時ファイルのサブセクションを追跡し、それを一度に外部コマンドにパイプすることで、独自のソリューションを考え出しました。これは、Suprが提案したものと非常に似ています(ただし、一時ファイルの代わりにシェル変数を使用)。また、私はsedを使用するというアイデアが本当に好きですが、この場合の構文は私にとって少し上に見えます。

私の解決策:

nlフィルタの例として使用しています)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

私は一時ファイルの管理に対処する必要はありませんが、シェル変数にはかなり低いサイズ制限がある可能性があることを理解しています。一時ファイルのように機能するbashコンストラクトは知りませんが、プロセスは終了します。


私は、例えば、マイクのテストデータ、ラインを使用して、あなたは「行全体で蓄積状態」にできるようにしたかったと思ったMNO番号をされるだろう456。これはそれをしません。私の答えはそうです(現在の化身ではnl、フィルターとしては機能しません)。場合は、この答えは、あなたがしたい出力を与えている、あなたは、「行にわたって蓄積状態」によって何を意味するのですか?状態各セクションでのみ保持、セクション(セクション)ではなく状態を維持したいという意味ですか?(なぜ複数のセクションの例を質問に入れなかったのですか?)
スコット

@Scott-を使用nl -pして取得しM,N,O==4,5,6ます。
mikeserv

質問を更新して、サブセクション内で状態を維持することにのみ関心があることを明確にしましたが、他の解釈も同様に興味深いと思います。
ジェームズスクリベン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.