連続した重複をすべて削除する


13

このようなファイルがあります。

Move to 230.00
Hold
Hold
Hold
Hold
Hold
Hold
Move to 00.00
Hold 
Hold 
Hold 
Hold 
Hold 
FooBar
Hold 
Spam
Hold

次のようになりたいです。

Move to 230.00
Hold
Move to 00.00
Hold 
FooBar
Hold
Spam
Hold

vimがこれをすばやく実行できる方法が必要であると確信していますが、どのように頭をかき回すことはできません。これはマクロの力を超えており、vimscriptが必要ですか?

また、「Holds」の各ブロックに同じマクロを適用する必要がある場合でも問題ありません。ファイル全体を取得する単一のマクロである必要はありませんが、それは素晴らしいことです。

回答:


13

私は次のコマンドが動作するはずだと思う:

 :%s/^\(.*\)\(\n\1\)\+$/\1/

説明 :

ファイル全体で代用コマンドを使用して、次のように変更patternstringます。

:%s/pattern/string/

ここpattern^\(.*\)\(\n\1\)\+$ありstringます\1

pattern このように分解することができます:

^\(subpattern1\)\(subpattern2\)\+$

^そして、$それぞれ行頭と行末に一致します。

\(そして、後で特別な番号で参照できる\)ように囲むsubpattern1ために使用され\1ます。量指定子で1回以上繰り返すことができるように、
囲みにも使用されます。subpattern2\+

subpattern1is .*
.は、改行を除く任意の文字に一致するメタ文字であり*、最後の文字に0回、1回以上一致する数量詞です。
したがって.*、改行を含まないテキストと一致します。

subpattern2された\n\1
\n新しい行を一致し、\1最初の内側にマッチした同じテキストと一致し\(\)ここにありますsubpattern1

だから、patternこのように読むことができます:
行の先頭(^)新しい行を(含まない任意のテキストに続く.*)改行(続く\n)その後、同じテキスト(\1)、後者の2つのビーイングは、(1回以上繰り返す\+)、および最後に行末($

どこpattern(同じラインのブロック)と一致している、置換コマンドは、に置き換えstringここでこれは\1(ブロックの最初の行)。

ファイル内で何も変更せずに影響を受ける行ブロックを確認したい場合は、hlsearchオプションを有効にしてn、コマンドの最後に置換フラグを追加できます。

:%s/^\(.*\)\(\n\1\)\+$/\1/n

よりきめ細かな制御を行うには、c代わりに置換フラグを追加して、行の各ブロックを変更する前に確認を求めることもできます。

:%s/^\(.*\)\(\n\1\)\+$/\1/c

置換コマンドの読み取りの詳細については:help :s
置換フラグのため:help s_flags
様々なメタ文字と数量を読むために:help pattern-atoms
とのためのvimの正規表現は読み、これを

編集:ワイルドカード$はを末尾に追加することでコマンドの問題を修正しましたpattern

また、BloodGainには、同じコマンドのより短くて読みやすいバージョンがあります。


1
いいね $ただし、コマンドにはそれが必要です。それ以外の場合、前の行と同じテキストで始まる行で予期しないことを行いますが、他の末尾の文字があります。また、あなたが与えた基本的なコマンドは:%!uniq、私の回答と機能的に同等ですが、ハイライトと確認フラグは素晴らしいことに注意してください。
ワイルドカード

あなたは正しい、私はチェックしたばかりで、重複する行の1つに異なる末尾文字が含まれている場合、コマンドは期待どおりに動作しません。私はそれを修正する方法がわかりません、アトム\nは行末に一致し、これを防ぐべきですが、そうではありません。成功せずに$直後に追加してみ.*ました。私はそれを試して修正しようとしますが、もしできなければ、答えを削除するか、最後に警告を追加します。この問題を指摘していただきありがとうございます。
サギノー

1
試してください:%s/^\(.*\)\(\n\1\)\+$/\1/
ワイルドカード

1
$行末ではなく、文字列の末尾に一致することを考慮する必要があります。これは技術的には正しくありませんが、いくつかの例外を除いてその後に文字を入力すると、$特別な文字ではなくリテラルに一致します。したがって\n、複数行の一致を使用する方が適しています。(参照:help /$
ワイルドカード

私はあなたが\n正規表現内のどこでも使用できるという点で正しいと思いますが、$おそらく最後にのみ使用されるべきです。2つを区別するために、\n改行に一致する(本能的にはテキストがまだ残っていると思わせる)一方$で、行の終わりに一致する(何もないと思わせる)ように書くことで回答を編集しました左)。
サギノー

10

以下を試してください:

:%s;\v^(.*)(\n\1)+$;\1;

saginaw's answer同様に、これはVimの:substituteコマンドを使用します。ただし、読みやすくするために、いくつかの追加機能を利用しています。

  1. Vimでは、バックスラッシュ(\)、二重引用符(")、またはパイプ(|)を除く英数字以外のASCII文字を使用して、一致/置換/フラグテキストを分割できます。ここでは、セミコロン(;)を選択しましたが、別のものを選びます。
  2. Vimは正規表現に「マジック」設定を提供するため、文字はバックスラッシュエスケープを必要とせずに特別な意味で解釈されます。これは、冗長性を減らすのに役立ちます。これは、「nomagic」デフォルトよりも一貫性があるためです。\v「非常に魔法」という意味で始まるか、英数字(A-z0-9)およびアンダースコア(_)を除くすべての文字には特別な意味があります。

コンポーネントの意味は次のとおりです。

ファイル全体の

Sの 代替

; 代替文字列を開始

\ v 「非常に魔法」

^ 行の始まり

(。*) 0個以上の任意の文字(グループ1)

(\ n \ 1)+ 改行とそれに続く(グループ1の一致テキスト)、1回以上(グループ2)

$ 行末(または、この場合、次の文字は改行である必要があると思います

; 置換文字列を開始

\ 1 グループ1の一致テキスト

; コマンドの終了または開始フラグ


1
私はあなたの答えが本当に好きです。それはより読みやすいだけでなく、との違いをよりよく理解させた\nから$です。\nパターンに何かを追加します。vimに次のテキストが新しい行にあることを伝える文字改行。一方で$、パターンには何も追加しません、それは単にパターンの次の文字の外には新しい行でない場合に行われる試合を禁止します。少なくとも、あなたの答えとを読んで理解したこと:help zero-widthです。
-saginaw

また、^パターンは何も追加されず、パターンの外側の前の文字が改行でない場合に一致するのを防ぐだけです。
saginaw

@saginawあなたはそれを正確に持っています、そしてそれは良い説明です。正規表現では、一部の文字は制御文字として扱うことができます。たとえば、+「前の表現(文字またはグループ)を1回以上繰り返す」という意味ですが、それ自体とは一致しません。^は「文字列の途中で開始することはできません」という意味で、「文字列$の途中で終了することはできません」という意味です。「行」ではなく「文字列」と言っていることに注意してください。Vimはデフォルトで各行を文字列として扱います-そしてそれが\n入ってくるのです。これは、この一致を試みるために改行を消費するようVimに指示します。
ブラッドゲイン

8

だけHoldでなく、すべての隣接する同一の行を削除する場合は、内部の外部フィルターを使用して非常に簡単に削除できますvim

:%!uniq (Unix環境の場合)。

で直接実行する場合vim、実際には非常に注意が必要です。方法はあると思いますが、一般的な場合、100%機能させるのは非常に難しく、まだすべてのバグを解決していません。

ただし、この特定のケースでは、重複していない次の行が同じ文字で始まっていないことが視覚的にわかるため、次を使用できます。

:+,./^[^H]/-d

+現在の行の後に行を意味します。。現在の行を指します。/^[^H]/-(前の行を意味-H.始まらない次の行)

その後、dは削除されます。


3
代替およびグローバルVimコマンドは良い練習ですが、uniq(vim内から、またはシェルを使用して)呼び出すことでこれを解決できます。一つにuniqは、空白/すべてのスペースの行を同等のものとして処理することはかなり確かですが(テストしませんでした)、正規表現でキャプチャするのははるかに困難です。また、仕事を終わらせようとしている間は、「車輪の再発明」をしないということです。
ブラッドゲイン

2
外部ツールを介してテキストをフィードする機能があるため、通常、WindowsでVim Cygwinをお勧めします。Vimとシェルは単に一緒に属します。
DevSolar

2

Vimベースの回答:

:%s/\(^.*\n\)\1\{1,}/\1

= 少なくとも1回それ自体が続くすべての行を、同じ行に置き換えます。


2

もう1つ、Vim 7.4.218以降を想定:

function! s:Uniq(line1, line2)
    let cursor = getcurpos()
    let lines = uniq(getline(a:line1, a:line2))
    if setline(a:line1, lines) == 0 && len(lines) <= a:line2 - a:line1
        silent execute (a:line1 + len(lines)) . ',' . a:line2 . 'd _'
    endif
    call setpos('.', cursor)
endfunction

command! -range=% Uniq call <SID>Uniq(<line1>, <line2>)

ただし、これは必ずしも他のソリューションよりも優れているとは限りません。


2

Preben GulbergとPiet Delportによる古い(2003)vim(golf)に基づいたソリューションがあります。

  • ルーツは %g/^\v(.*)\n\1$/d
  • 他のソリューションとは異なり、関数にカプセル化されているため、検索レジスタも名前のないレジスタも変更されません。
  • また、使用方法を簡素化するために、コマンドにカプセル化されています。
    • :Uniq(に相当:%Uniq)、
    • :1,Uniq (バッファの先頭から現在の行まで)、
    • 視覚的に行を選択+ヒット:Uniq<cr>(vimでに展開:'<,'>Uniq
    • など(:h range

コードは次のとおりです。

command! -range=% -nargs=0 Uniq <line1>,<line2>call s:EmuleUniq()

function! s:EmuleUniq() range
  let l1 = a:firstline
  let l2 = a:lastline
  if l1 < l2
    " Note the "-" to avoid spilling over the end of the range
    " Note also the use of ":delete", along with the black hole register "_"
    silent exe l1.','l2.'-g/^\(.*\)\n\1$/d _'

    call histdel('search', -1)          " necessary
    " let @/ = histget('search', -1)    " useless within a function
  endif
endfunction

注:最初の試みは次のとおりです。

" Version1 from: Preben 'Peppe' Guldberg <peppe {at} xs4all {dot} nl>
" silent exe l1 . ',' . (l2 - 1) . 's/^\(.*\)\%(\n\%<' . (l2 + 1)
      " \ . 'l\1$\)\+/\1/e'

" Version from: Piet Delport <pjd {at} 303.za {dot} net>
" silent exe l1.','l2.'g/^\%<'.l2.'l\(.*\)\n\1$/d'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.