4

ファイルを並べ替えずに、ファイルから重複行を削除したい。

これが私にとって役立つ理由の例:$HISTFILE時系列の順序を変更せずにBashから重複を削除します。

このページには、それを行うためのワンライナーがあります。

http://sed.sourceforge.net/sed1line.txt

これがワンライナーです:

sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'

システム管理者に聞いたところ、「スクリプトをコピーするだけで機能します。これについては哲学的に考えないでください」と言われました。これは問題ないので、開発者フォーラムであり、人々が私のようであると信じているので、ここで質問します。彼らが理解していないものを使用することについて疑わしい:

その「黒魔術」スクリプトが何をしているのかを擬似コードで説明していただけませんか。頭の中の呪文を解析してみましたが、特に中央部分はかなり硬いです。

4

3 に答える 3

5

sedこのスクリプトは、現在のロケールの (GNU sed 4.1.5) のコピーでは機能しないようです。私がそれを実行すると、正常に動作しLC_ALL=Cます。

これは、スクリプトの注釈付きバージョンです。 sed基本的に 2 つのレジスタがあり、1 つは「パターン スペース」と呼ばれ、(基本的に) 現在の入力行に使用され、もう 1 つは「ホールド スペース」であり、スクリプトで一時的な格納などに使用できます。

sed -n '                    # -n: by default, do not print
    G                       # Append hold space to current input line
    s/\n/&&/                # Add empty line after current input line
    /^\([ -~]*\n\).*\n\1/d  # If the current input line is repeated in the hold space, skip this line
                            # Otherwise, clean up for storing all input in hold space:
    s/\n//                  # Remove empty line after current input line
    h                       # Copy entire pattern space back to hold space
    P                       # Print current input line'

空行の追加と削除は、中央のパターンを比較的単純に保つためにあると思います (現在の行の後、一致する行の先頭の前に改行があると期待できます)。

したがって、基本的には、入力ファイル全体 (複製を除く) が (逆の順序で) ホールド スペースに保持され、パターン スペースの最初の行 (現在の入力行) がパターン スペースの残りの場所 (これはスクリプトがこの行の処理を開始したときに保留スペースからコピーされた)、それをスキップして最初からやり直します。

条件の正規表現はさらに分解できます。

^    # Look at beginning of line (i.e. beginning of pattern space)
\(   # This starts group \1
[ -~] # Any printable character (in the C locale)
*     # Any number of times
\n    # Followed by a newline
\)   # End of group \1 -- it contains the current input line
.*\n # Skip any amount of lines as necessary
\1   # Another occurrence of the current input line, with newline and all

このパターンが一致する場合、スクリプトはパターン スペースを破棄し、次の入力行からやり直します ( d)。

[ -~]に変更することで、ロケールに関係なく動作させることができます[[:print:]]

于 2012-06-27T08:33:22.807 に答える
3

おそらくロケール設定が原因で、コードは機能しませんが、これは機能します:

                          vvv
sed -n 'G; s/\n/&&/; /^\([^\n]*\n\).*\n\1/d; s/\n//; h; P'
                          ^^^

まず、これを書籍 (つまり sed の情報ページ) に基づいて、perlish に翻訳してみましょう。

# The standard sed loop
my $hold = "";
while ($my pattern = <>) {
    chomp $pattern;

    $pattern = "$pattern\n$hold";           # G
    $pattern =~ s/(\n)/$1$1/;               # s/\n/&&/
    if ($pattern =~ /^([^\n]*\n).*\n\1/) {  # /…/
        next;                               # d
    }
    $pattern =~ s/\n//;                     # s/\n//
    $hold = $pattern;                       # h
    $pattern =~ /^([^\n]*\n?)/; print $1;   # P
}

OK、基本的な考え方は、保留スペースにはこれまでに見たすべての行が含まれているということです。

  1. G: 各サイクルの開始時に、その保持スペースを現在の行に追加します。これで、現在の行とそれより前のすべての一意の行で構成される単一の文字列ができました。
  2. s/\n/&&/: それらを区切る改行を二重の改行に変えて、後続の重複と後続でない重複を同じように一致させることができるようにします。次のステップを参照してください。
  3. ^\([^\n]*\n\).*\n\1/: 現在のテキストを調べて、次のことを確認します。すべての行の先頭( ) で、^末尾の改行を含む最初の行 ( \([^\n]*\n\))、次に何か ( .*)、改行 ( \n)、および改行を含む同じ最初の行の繰り返しを探します。再び(\1)。後続の 2 行が同じ場合.*、正規表現の は空の文字列と一致し\nますが、前の手順で改行が重複しているため、2 つの行は引き続き一致します。つまり、基本的に、これは最初の行が他の行の中で再び表示されるかどうかを尋ねます。
  4. d: 一致する場合、これは重複行です。この入力を破棄し、ホールド スペースをこれまでに確認されたすべての一意の行のバッファーとして保持し、次の入力行に進みます。
  5. s/\n//: それ以外の場合は続行し、次に二重の改行を単一の改行に戻します。
  6. h: 現在の行をすべての一意の行のリストに含めます。
  7. P: そして最後に、改行文字まで、この新しい一意の行を出力します。
于 2012-06-27T08:50:17.073 に答える
0

実際の問題を解決するには、awk を使用した簡単な解決策 (少なくともそう見える) を次に示します。

awk '!_[$0]++' FILE

要するに_[$0]、一意の行ごとの (出現の) カウンターであり、2$0回目に出現する任意の行 (_[$0] >= 1!_[$0]false

https://gist.github.com/ryenus/5866268を参照してください(クレジットは、私が最近訪れたフォーラムのものです)。

于 2013-07-01T10:09:48.373 に答える