web-dev-qa-db-ja.com

sed(またはawk)でパターンの上の行の範囲を削除します

私は、パターンbananaのある行とその後の2行を削除する次のコードを持っています。

sed '/banana/I,+2 d' file

ここまでは順調ですね!しかし、私はそれを2行削除する必要がありますbeforebananaですが、「マイナス記号」などでそれを取得することはできません(grep -v -B2 banana fileが行うべきことと同様ですが、しない:)

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'
28
Teresa e Junior

Sedはバックトラックしません。ラインが処理されると完了します。そのため、「行を見つけて次のN行を印刷する」とは異なり、「簡単に行を見つけて次のN行を印刷する」とは異なり、そのままでは機能しません。

ファイルが長すぎない場合は、GNU拡張子で問題ないようなので、tacを使用してファイルの行を逆にすることができます。

tac | sed '/banana/I,+2 d' | tac

別の攻撃の角度は、awkのようなツールでスライディングウィンドウを維持することです。からの適応 grepの-A -B -Cスイッチに代わるものはありますか(前後に数行を出力するため)? (警告:最小限のテスト):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

使用法: /path/to/script -v pattern='banana' -v before=2

これはexまたはvim -eでかなり簡単です

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

式は次のようになります。現在の行-2から現在の行までの範囲でバナナを含むすべての行について、削除します。

クールなのは、範囲に後方検索と前方検索を含めることもできることです。たとえば、Appleを含む行で始まり、オレンジを含み、行を含む行で終わるファイルのすべてのセクションが削除されます。バナナ付き:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

また、インラインコマンドオプション「-c」を使用すると、最大10個のvim/exコマンドを送信できます。 manページを参照してください。

vim -e -c 'g/banana/.-2,.d' -c 'wq' $yourfilename

そして

ex -c 'g/banana/?apple?,/orange/d' -c 'wq' $yourfilename 
19
Justin Rowe

これは、sedを使用してかなり簡単に行うことができます。

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

私はなぜ誰もがそうでないと言うのかわかりませんが、行を見つけて前の行を印刷するsedには、組み込みのPrintプリミティブが組み込まれており、パターンスペースの最初の\newline文字。補完的なDeleteプリミティブは、残っているものでスクリプトを再帰的にリサイクルする前に、パターンスペースの同じセグメントを削除します。そして、四捨五入するために、挿入された\newline文字に続くパターンスペースにNext入力行を追加するためのプリミティブがあります。

したがって、sedの1行で十分です。あなたはただmatchを正規表現が何であるかで置き換えて、あなたは黄金です。これも非常に高速の解決策です。

match別の直前のmatchを、前の2行の出力を静止させるトリガーとして正しくカウントすることにも注意してください- and印刷も静かにします:


1
7match
8
11match

任意行数で機能するためには、リードを取得するだけです。

そう:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

...一致する前の5行を削除します。

7
mikeserv

Perlの「スライディングウィンドウ」を使用する:

Perl -ne 'Push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'
7
choroba

man 1 edの使用:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
1
larz