web-dev-qa-db-ja.com

テキストファイル内の特定の行ブロックを見つけて置き換えることができるコマンドラインユーティリティアプリはありますか?

UPDATE(質問の終わりを参照)

私が見たテキスト「検索と置換」ユーティリティプログラムは、行単位でのみ検索するようです...

command-lineツールはありますかlocate1行のブロック(テキストファイル内)、およびreplace別の行ブロックで置き換えます。

例:テストファイルファイルに次の行が含まれていますかexact group

'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,  
And the mome raths outgrabe. 

'Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!'

ファイル内の複数行のテキストを置き換え、間違った行を上書きしていないことを確認できるように、これが必要です。

「The Jabberwocky」(ルイス・キャロル)を置き換えることはありませんが、斬新な例になります:)

UPDATE
..(sub-update)sedを使用しない理由whenについての私の以下のコメントはonlyのコンテキストで;設計意図を超えてツールをプッシュしすぎないでください(私はsedを頻繁に使用しており、非常に貴重であると考えています)。

sedについての興味深いWebページと、それを使用しない場合を見つけました。
したがって、sedのすべての回答のため、リンクを投稿します。これは sedの一部ですFAQ sourceforgeで

また、何らかの方法があると確信していますdiffは、テキストのブロックを見つけることができます(一度見つかったら、置換は非常にまっすぐです前方; headおよびtailを使用)... 'diff'はすべての必要なデータをダンプしますが、それをフィルタリングする方法をまだ試していません...(まだ作業中です)

7
Peter.O

次の単純なpythonスクリプトでタスクを実行する必要があります。


#!/usr/bin/env python

# Syntax: multiline-replace.py input.txt search.txt replacement.txt

import sys

inp = open(sys.argv[1]).read()
needle = open(sys.argv[2]).read()
replacement = open(sys.argv[3]).read()

sys.stdout.write(inp.replace(needle,replacement))

他のほとんどのソリューションと同様に、ファイル全体が一度にメモリに丸thatみされるという欠点があります。ただし、小さなテキストファイルの場合は、十分に機能するはずです。

7
loevborg

アプローチ1:改行を一時的に別のものに変更する

次のスニペットは、改行をパイプと交換し、置換を実行して、セパレーターを元に戻します。ユーティリティは、ラインが極端に長い場合、停止する可能性があります。検索文字列にない限り、交換する任意の文字を選択できます。

<old.txt tr '\n' '|' |
sed 's/\(|\|^\)'\''Twas … toves|Did … Bandersnatch!'\''|/new line 1|new line 2|/g' |
tr '|' '\n' >new.txt

アプローチ2:ユーティリティのレコード区切り文字を変更する

AwkとPerlは、レコードセパレーターとして2つ以上の空白行の設定をサポートしています。 awkを使用して、-vRS=(空のRS変数)を渡します。 Perlでは、-000(「段落モード」)を渡すか、$,=""を設定します。ただし、複数段落の検索文字列があるため、ここでは役に立ちません。

AwkとPerlは、レコード区切り文字として任意の文字列を設定することもサポートしています。 RSまたは$,を検索文字列にない任意の文字列に設定します。

<old.txt Perl -pe '
    BEGIN {$, = "|"}
    s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt

アプローチ3:ファイル全体で作業する

一部のユーティリティでは、ファイル全体を簡単にメモリに読み込んで作業できます。

<old.txt Perl -0777 -pe '
    s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt

アプローチ4:プログラム

行を1つずつ読んでください。空のバッファーから始めます。 「 'Twas」行が表示され、バッファーが空の場合は、バッファーに入れます。 「Dy gyre」が表示され、バッファーに1行ある場合は、現在の行をバッファーに追加します。 「Bandersnatch line」を追加したばかりの場合は、置換テキストを出力します。現在の行がバッファに入っていない場合は、バッファの内容を印刷し、現在の行を印刷してバッファを空にします。

psusi はsed実装を示します。 sedでは、バッファーの概念が組み込まれています。ホールドスペースと呼ばれます。 awkまたはPerlでは、変数を使用するだけです(おそらく2つ、1つはバッファーの内容用、もう1つは行数用)。

3
Gilles

UPDATE:loevborgのpythonスクリプトは確かに最もシンプルで最適なソリューションです(それについては間違いありません)。それに満足していますが、(質問の最後に)提示したbashスクリプトは見た目ほど複雑ではないことを指摘したいと思います。それをテストするために使用したデバッグドロスをすべて削除しました。 。そしてここでも、このページを訪れる人のために、重荷はありません。これは、基本的にsedワンライナーであり、前と後の16進変換があります:

F=("$haystack"  "$needle"  "$replacement")
for f in "${F[@]}" ; do cat "$f" | hexdump -v -e '1/1 "%02x"' > "$f.hex" ; done
sed -i "s/$(cat "${F[1])}.hex")/$(cat "${F[2])}.hex")/p" "${F[0])}.hex"
cat "${F[0])}.hex" | xxd -r -p > "${F[0])}"
# delete the temp *.hex files.

帽子を指輪に投げ込むために、special正規表現文字で問題が発生しない「sed」ソリューションを考え出しました、1つでも使用しないためです! ..代わりに、ファイルのHexdumpedバージョンで動作します...

私はそれがあまりにも「重い」と思いますが、機能し、明らかにサイズ制限によって制限されていません。GNU sedには無制限のパターンがありますバッファサイズ。これは、検索行の16進ダンプブロックが終わる場所です。したがって、その点で大丈夫です...

私はまだdiffソリューションを探しています、なぜならそれは空白に関してより柔軟であるからです(そして私は期待するでしょう;より速い)...しかしまでそれから..それは有名なセッド氏です。 :)

このスクリプトはそのまま完全に実行されており、合理的にコメントされています...
より大きく見えます。必要なコードは7行のみです。
半現実的なテストのために、 Project Gutenberg (363.1 KB)...から本「鏡を通して見るアリス」をダウンロードし、元のJabberwockyの詩を行に置き換えます-それ自体の逆バージョン..(興味深いことに、逆方向に読むとそれほど違いはありません:)

PS。このメソッドの弱点は、元のファイルが改行として\ r\n(0xODOA)を使用し、「一致するテキスト」が\ n(0x0A)で保存される場合です。水...(「diff」にはそのような問題はありません)...


# In a text file, replace one block of lines with another block
#
# Keeping with the 'Jabberwocky' theme, 
#  and using 'sed' with 'hexdump', so 
#  there is no possible *special* char clash.
# 
# The current setup will replace only the first instance.
#   Using sed's 'g' command, it cah change all instances. 
#

  lookinglass="$HOME/Through the Looking-Glass by Lewis Carroll"
  jabberwocky="$lookinglass (jabberwocky)"
  ykcowrebbaj="$lookinglass (ykcowrebbaj)"

  ##### This section if FOR TEST PREPARATION ONLY
        fromURL="http://www.gutenberg.org/ebooks/12.txt.utf8"
        wget $fromURL -O "$lookinglass"
        if (($?==0))
        then  echo "Download OK"
        else  exit 1
        fi
        # Make a backup of the original (while testing)
        cp "$lookinglass" "$lookinglass(fromURL)"
        #
        # Extact the poem and write it to a file. (It runs from line 322-359)
        sed -n 322,359p "$lookinglass" > "$jabberwocky"
        cat "$jabberwocky"; read -p "This is the original.. (press Enter to continue)"
        #
        # Make a file containing a replacement block of lines
        tac "$jabberwocky" > "$ykcowrebbaj"
        cat "$ykcowrebbaj"; read -p "This is the REPLACEMENT.. (press Enter to continue)"
  ##### End TEST PREPARATION

# The main process
#
# Make 'hexdump' versions of the 3 files... source, expected, replacement 
  cat "$lookinglass" | hexdump -v -e '1/1 "%02x"' > "$lookinglass.xdig"
  cat "$jabberwocky" | hexdump -v -e '1/1 "%02x"' > "$jabberwocky.xdig"
  cat "$ykcowrebbaj" | hexdump -v -e '1/1 "%02x"' > "$ykcowrebbaj.xdig"
# Now use 'sed' in a safe (no special chrs) way.
# Note, all files are now each, a single line  ('\n' is now '0A')
  sed -i "s/$(cat "$jabberwocky.xdig")/$(cat "$ykcowrebbaj.xdig")/p" "$lookinglass.xdig"

  ##### This section if FOR CHECKING THE RESULTS ONLY
        # Check result 1
        read -p "About to test for the presence of  'jabberwocky.xdig'  within itself (Enter) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$jabberwocky.xdig"
        echo -e "\n\nA dump above this line, means: 'jabberwocky' is as expected\n" 
        # Check result 2
        read -p "About to test for the presence of  'ykcowrebbaj.xdig'  within itself (Enter) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$ykcowrebbaj.xdig"
        echo -e "\n\nA dump above this line, means: 'ykcowrebbaj' is as expected\n" 
        # Check result 3
        read -p "About to test for the presence of  'lookinglass.xdig'  within itself (Enter) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nA dump above this line, means: 'lookinglass' is as expected\n" 
        # Check result 4
        read -p "About to test for the presence of  'lookinglass.xdig'  within itself (Enter) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nNo dump above this line means: 'lookinglass' is as expected\n"
  ##### End of CHECKING THE RESULTS

# Now convert the hexdump to binary, and overwrite the original
  cat "$lookinglass.xdig" | xxd -r -p > "$lookinglass"
# Echo the "modified" poem to the screen
  sed -n 322,359p "$lookinglass"
  echo -e "\n\nYou are now looking at the REPLACEMENT text (dumped directly from the source 'book'"
2
Peter.O

私はsedでこれを行う方法がなければならないと確信していました。いくつかのグーグルの後に、私はこれに出くわしました:

http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/

それに基づいて、私は書いてしまいました:

sed -n '1h;1!H;${;g;s/foo\nbar/jar\nhead/g;p;}' < x

Xの内容を正しく取りました:

フーバー

そして吐き出します:

ジャーヘッド

2
psusi

あなたが白髪sedPerlを嫌うとしても、灰色のテンプルawkに好みがあるかもしれません。 この答え はあなたが探しているもののようです。ここで再現します。 3つのファイルがあり、needlereplacementhaystackに置き換えたいとします。


awk ' BEGIN { RS="" }
      FILENAME==ARGV[1] { s=$0 }
      FILENAME==ARGV[2] { r=$0 }
      FILENAME==ARGV[3] { sub(s,r) ; print }
    ' needle replacement haystack > output

これは正規表現を含まず、改行文字をサポートします。かなり大きなファイルで動作するようです。ファイル全体をメモリに丸involveみする必要があるため、任意のサイズのファイルでは機能しません。よりエレガントにしたい場合は、bashスクリプトでShebang全体を囲むか、awkスクリプトに変換できます。

2
loevborg