web-dev-qa-db-ja.com

grepの-A-B -Cスイッチ(前後に数行を印刷するため)に代わるものはありますか?

grep -A 2 -B 3 

grep文字列の2行後に印刷し、3行前に印刷します。

grep -C 3

前に3行、後に3行を出力します

残念ながら、私が使用しているgrepはこれらのオプションをサポートしていません。これをシミュレートするために利用できる代替コマンドまたはスクリプトはありますか? sed/awk/Perl/Shellスクリプトを使用していますか?

10
Prashant Bhate

それを行うための適度に醜い方法の1つは

grep -v pattern file >file.tmp; diff -c file.tmp file

または、コンテキストのNUM行について、-c-C NUMに置き換えます。ただし、追加の出力が生成されます。 (diff-u/-U NUMをサポートしている場合は、よりクリーンになります。)

diff-c/-C/-uがない場合でも、それを行う方法はありますが、かなり醜いです。一方、diff-cをサポートしていないシステムは、おそらくPerlも持っていません。

6
geekosaur

この単純なPerlスクリプトは、grep -Aをある程度エミュレートします

#!/usr/bin/Perl

$pattern=shift; #patthern to search
$lines=shift; # number of lines to print

$n = 0;
while (<>) {
  $n = $lines if /$pattern/; # reset counting
  if ($n) { print; $n-- } # print if within
  $n = 0 if eof; # don't leak across file boundaries
}

スクリプトを読みやすく使用できるようにするために、usageステートメントを追加できることに注意してください;)

USAGE:    $./grep-A.pl <pattern> <numLines> <filename> 
5
Vijay Anant

ack はPerlのみを必要とし、grepのように機能する-A-B、および-Cオプションが含まれています。 grepの代わりにPerlの正規表現構文を使用し、検索するファイルを選択する方法はまったく異なります。使用する場合は、-fオプションを試してみてください(実際には何も検索せずに、検索するファイルを出力します)。

単一のスクリプト としてインストールできます。これは非コアモジュールを必要としません。それを~/binディレクトリ(または書き込みアクセス権があるPATH上の他の場所)にドロップし、chmodされた実行可能ファイルであることを確認してください。

5
cjm

GNU grep または Ack (Perlで記述され、GNU grepのオプションなどの多くを理解している)をインストールするだけです。

標準ツールと少しのスクリプトに固執したい場合は、GNU grepの-Aおよび-Bオプションの動作をエミュレートする awk スクリプトを次に示します。最小限のテスト済み。

#!/bin/sh
# grep-ac: a grep-like awk script
# Arguments: pattern = awk regexp to search for
#            before = number of lines to print before a match
#            after = number of lines to print after a match
{ "exec" "awk" "-f" "$0" "$@"; }
# The array h contains the history of lines that haven't been printed
# but are eligible for being "before" lines.
# The variable until contains the number of the last "after" line to print.
match($0, pattern) {   # the current line matches
    for (i in h) {
        print h[i];    # print each remaining before line
        delete h[i];   # delete each line as it's printed
    }
    until=NR+after;    # record the last after line to print
}
{
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    delete h[NR-before];        # remove line too old to be a before line
}
END {exit !until}               # exit status: 0 if there was a match, else 1

grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTERとして実行します。ここで、PATTERNは検索するパターンです( 拡張正規表現 いくつかの awk追加 )、およびNBEFOREおよびNAFTERは、それぞれ一致の前後に出力する行数です(デフォルトは0)。例:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'

-Bをエミュレートするのは非常に難しいことがわかります。これは、一致する行が互いに直接続く場合に問題が発生するためです。これは、あらゆる種類のシングルパススルーファイルスキャンの使用をほとんど許可しません。

私は次の近似で遊んでいる間にこれに気づきました:

Perl -pe 'if(/search_term/) {print foreach @A; print ">"; $B=4}; shift @A if Push(@A, $_)>7; $_ = "" unless ($B-- > 0);' target_file

これは、grep -A7 -B3とほぼ同じように正しく機能しますが、最初の段落で説明した警告があります。

この問題の代替(単一ファイル)ソリューションは、Perlを使用してsedにコマンド文字列をフィードすることです。

sed -n `Perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file
2
user455

sedを使用すると、最初に一致する行の行番号を取得し、whileループで特定の行番号をデクリメントおよびインクリメントしてから、sed -n "n1,n2p"を使用して先頭(n1)および末尾(n2)コンテキスト(user455によって提案されたsed代替案と同様)。ただし、多くの読み取りプロセスはパフォーマンスの低下につながる可能性があります。

edは、一致した行の前後の行を直接参照できますが、指定された行範囲が存在しない場合は失敗します。たとえば、一致する行は行番号2ですが、5つの一致前の行を印刷する必要があります。したがって、edを使用すると、最初と最後に適切な数の(空の)行を追加する必要があります。 (巨大なファイルについては、edは適切なツールではないかもしれませんが、 bfs-大きなファイルスキャナー を参照してください)。

# sample code to match lines with number 5 plus previous & following line
# (using Bash)
printf '%s\n' {1..20} > num.txt

# sed
sed -n '/5/=' num.txt | while read num; do
   n1=$((num - 1))
   n2=$((num + 1))
   [[ $n1 -lt 1 ]] && n1=1
   sed -n "${n1},${n2}p" num.txt
   echo --
done | sed -e '${/^--$/d;}'

# ed
cat <<-'EOF' | ed -s num.txt | sed -e $'N;N;a\\\n--' | sed -e '${/^--$/d;}'
H
0i
beginning: added line one
.
$a
end: added line one
.
,g/5/km\
'm-1,'m+1p
q
EOF
0
larz