web-dev-qa-db-ja.com

文字列を順次インデックスで置き換える

誰かがこれを達成するためのエレガントな方法を提案できますか?

入力:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

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

test      instant1  ()

test      instant2  ()

test      instant1000()

空の行が入力ファイルにあり、同じディレクトリの下に一度に処理する必要のあるファイルがたくさんあります。

同じディレクトリにある多くのファイルを置き換えるためにこれを試してみましたが、機能しませんでした。

for file in ./*; do Perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

エラー:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

そして私もこれを試しました:Perl -i -pe 's/instant/$& . ++$n/ge' *.vs

それは機能しましたが、インデックスは1つのファイルから別のファイルに増分し続けました。差分ファイルの場合、それを1にリセットしたいと思います。何か良い提案はありますか?

find . -type f -exec Perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

動作しますが、他のすべてのファイルは置き換えられません。ファイルを「* .txt」のみに置き換えることを好みます。

9
user3342338
Perl -pe 's/instant/$& . ++$n/ge'

またはGNU awk

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

ファイルをその場で編集するには、Perl-iオプションを追加します。

Perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*

または再帰的に:

find . -type f -exec Perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

解説

Perl -pe 's/instant/$& . ++$n/ge'

-pは、入力を1行ずつ処理し、-eに渡された式を行ごとに評価して出力します。各行について、(s/re/repl/flags演算子を使用して)instantをそれ自体($&)と変数++$nの増分値に置き換えます。 gフラグは、置換をグローバルに(一度だけではなく)行うためのものであり、eは、置換がPerlコードとして解釈されるように 評価(固定文字列ではない)。

1回のPerl呼び出しで複数のファイルを処理するインプレース編集の場合、$nを各ファイルでリセットする必要があります。代わりに、$n{$ARGV}を使用します($ARGVは現在処理されているファイルです)。

awkの1つは少し説明に値します。

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

GNU awkの機能を使用して、任意の文字列(正規表現も含む)のレコードを分離します。-vRS=instantを使用して、r̲ecord s̲eparator to instantRTRSと一致したものを保持する変数であるため、通常、instantは最後のレコードを除いて上記の入力では、レコード($0)とレコードターミネータ(RT)は([$0|RT])です。

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

したがって、最初のレコードを除くすべてのレコードの先頭にインクリメントする番号を挿入するだけです。

これが、私たちが上記で行っていることです。最初のレコードの場合、nは空になります。 ORS(o̲utput r̲ecord s̲eparator)をRTに設定して、awkn $0 RTを出力するようにします。 2番目の式(++n)で実行されます。これは常にtrue(ゼロ以外の数値)と評価されるため、デフォルトのアクション($0 ORSの出力)がすべてのレコードに対して実行されます。 。

13

sedは実際にはジョブに最適なツールではありません。より優れたスクリプト機能を備えたものが必要です。ここにいくつかの選択肢があります:

  • Perl

    Perl -000pe 's/instant/$& . $./e' file 
    

    -pは、-eで指定されたスクリプトを適用した後、「すべての行を印刷する」ことを意味します。 -000は「段落モード」をオンにするので、レコード(行)は連続する改行(\n)文字で定義され、これにより2重スペース行を正しく処理できます。 $&は最後に一致したパターンで、$.は入力ファイルの現在の行番号です。 s///eeを使用すると、置換演算子の式を評価できます。

  • awk(これは、データが示されているとおりであり、3つのスペースで区切られたフィールドがあることを前提としています)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 
    

    ここでは、現在の行が空でない場合にのみk変数kをインクリメントします/./この場合、必要な情報も出力します。空の行はそのまま印刷されます。

  • さまざまなシェル

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 
    

    ここで、各入力行は空白で自動的に分割され、フィールドは$a$bおよび$cとして保存されます。次に、ループ内で、$cは、$aが空でない各行に対して1ずつ増加し、その現在の値が2番目のフィールド$bの横に出力されます。

注:上記のすべてのソリューションは、ファイルのall行が同じ形式であることを前提としています。そうでない場合、@ Stephaneの答えは、進むべき道です。


多くのファイルを処理し、これを現在のディレクトリ内のallファイルに対して実行する場合は、次のように使用できます。

for file in ./*; do Perl -i -000pe 's/instant/$& . $./e' "$file"; done

注意:スペースのない単純なファイル名を想定しています。より複雑なものを処理する必要がある場合は、次のようにしてください(ksh93zshまたはbashを想定):

find . -type f -print0 | while IFS= read -r -d ''; do
    Perl -i -000pe 's/instant/$& . $./e' "$file"
done
4
terdon