web-dev-qa-db-ja.com

bash文字列で始まる行を見つける

たくさんのファイルがあり、特定の文字列で始まる連続した行が含まれているファイルを見つけたい。

たとえば、次のファイルの場合:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Cyyyyyyyyy
Czzzzzzzzz
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd
Ceeeeee

「C」で始まる行が複数あるので、このファイルをコマンドで見つけたいと思います。
たとえば、次のファイルの場合:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

「C」で始まる行は常に1つあります。このファイルは必要ありません。 grepまたはsedを使用することを考えましたが、正確な方法がわかりません。多分正規表現^C.*$^Cまたはそのようなもの。何か案が ?

10
Jérémie

pcregrepの場合:

pcregrep -rMl '^C.*\nC' .

POSIXly:

find . -type f -exec awk '
  FNR==1 {last=0; printed=0; next}
  printed {next}
  /^C/ {if (last) {print FILENAME; printed=1; nextfile} else last=1; next}
  {last=0}' {} +

(ただし、awkをサポートしていないnextfile実装ですべてのファイルを完全に読み取ることを意味します)。


GNU grepのバージョン2.5.4まで:

grep -rlP '^C.*\nC' .

が表示されますが動作しますが、これは偶然によるものであり、動作が保証されていません。

2.6で修正される前( this commit )によって、GNU grepは、使用しているpcre検索機能が全体的に一致することを見落としていたgrepによって現在処理されているバッファは、あらゆる種類の驚くべき動作を引き起こします。

grep -P 'a\s*b'

以下を含むファイルに一致します:

bla
bla

これは一致します:

printf '1\n2\n' | grep -P '1\n2'

しかしこれは:

(printf '1\n'; sleep 1; printf '2\n') | grep -P '1\n2'

または:

(yes | head -c 32766; printf '1\n2\n') > file; grep -P '1\n2' file

しない(1\n2\nは、grepによって処理された2つのバッファにまたがっています。

しかし、その動作は文書化されていました:

15-どうすれば行を越えて照合できますか?

基本的に行ベースであるため、標準のgrepはこれを実行できません。したがって、「[:space:]」文字クラスを使用するだけでは、期待した方法で改行が一致しません。ただし、grepがPerlパターンを有効にしてコンパイルされている場合、Perlの「s」修飾子(「。」を改行に一致させる)を使用できます。

     printf 'foo\nbar\n' | grep -P '(?s)foo.*?bar'

2.6で修正された後、ドキュメントは修正されませんでした(以前に報告した there )。

5

awkの場合:

awk '{if (p ~ /^C/ && $1 ~ /^C/) print; p=$1}' afile.txt

Cで始まる連続した行がある場合、これはファイルの内容を出力します。表現 (p ~ /^C/ && $1 ~ /^C/)はファイル内の連続する行を調べ、両方の最初の文字がCと一致する場合にtrueと評価します。その場合、行が印刷されます。

このようなパターンを持つすべてのファイルを見つけるには、findコマンドを使用して上記のawkを実行できます。

find /your/path -type f -exec awk '{if (p ~ /^C/ && $1 ~ /^C/) {print FILENAME; exit;} p=$1}' {} \;

このコマンドでは、find + execは各ファイルを通過し、各ファイルに対して同様のawkフィルタリングを実行し、FILENAMEを介してその名前を出力します。 awk式はtrueと評価されました。複数の一致がある単一のファイルでFILENAMEを複数回印刷しないようにするために、exitステートメントが使用されます(@terdonに感謝)。

2
mkc

GNU sedを使用したさらに別のオプション:

単一ファイルの場合:

sed -n -- '/^C/{n;/^C/q 1}' "$file" || printf '%s\n' "$file"

(ただし、読み取れないファイルも報告されます)。

findの場合:

find . -type f ! -exec sed -n '/^C/{n;/^C/q 1}' {} \; -print

読み取れないファイルが印刷される問題は、次のように書くことで回避できます。

find . -type f -size +2c -exec sed -n '$q1;/^C/{n;/^C/q}' {} \; -print
2
rush

ファイルがメモリに読み込まれるほど小さいと仮定します。

Perl -000ne 'print "$ARGV\n" if /^C[^\n]*\nC/sm' *

説明:

  • -000\n\nをレコードセパレータとして設定します。これにより、段落モードがオンになり、(連続する改行で区切られた)段落が1行として扱われます。
  • -ne-eの引数として指定されたスクリプトを、入力ファイルの各行に適用します。
  • $ARGV:現在処理中のファイルです
  • /^C[^\n]*\nC/:行の先頭でCに一致(これが機能する理由については、以下のsm修飾子の説明を参照)の後に0個以上の非改行文字、a改行、次に別のC。つまり、Cで始まる連続する行を見つけます。 * //sm:これらの一致修飾子は([ここに記載]のとおり):

    • m:文字列を複数行として扱います。つまり、 "^"と "$"を、文字列の左端と右端のみの行の最初または最後と一致するようから、文字列内の任意の場所と一致するように変更します。

    • s:文字列を1行として扱います。つまり、「。」を変更します。通常は一致しない改行を含め、すべての文字に一致します。

次のような醜いこともできます:

for f in *; do Perl -pe 's/\n/%%/' "$f" | grep -q 'C[^%]*%%C' && echo "$f"; done

ここで、Perlコードは改行を%%に置き換えます。したがって、入力ファイルに%%がないと想定します(bigifもちろん)、grepCで始まる連続する行に一致します。

1
terdon

解決:

( set -- *files ; for f ; do (
set -- $(printf %c\  `cat <$f`)
while [ $# -ge 1 ] ;do [ -z "${1#"$2"}" ] && {
    echo "$f"; break ; } || shift
done ) ; done )

デモ:

まず、テストベースを作成します。

abc="a b c d e f g h i j k l m n o p q r s t u v w x y z" 
for l in $abc ; do { i=$((i+1)) h= c= ;
    [ $((i%3)) -eq 0 ] && c="$l" h="${abc%"$l"*}"
    line="$(printf '%s ' $h $c ${abc#"$h"})"
    printf "%s$(printf %s $line)\n" $line >|/tmp/file${i}
} ; done

上記は/tmpという名前の26個のファイルを作成しますfile1-26各ファイルには、文字a-zで始まり、残りが続く27行または28行がありますアルファベットの。 3番目のファイルごとに、最初の文字が重複する2つの連続した行が含まれています。

サンプル:

cat /tmp/file12
...
aabcdefghijkllmnopqrstuvwxyz
babcdefghijkllmnopqrstuvwxyz
cabcdefghijkllmnopqrstuvwxyz
...
kabcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
mabcdefghijkllmnopqrstuvwxyz
...

そして私が変わるとき:

set -- *files

に:

set -- /tmp/file[0-9]*

わかった...

出力:

/tmp/file12
/tmp/file15
/tmp/file18
/tmp/file21
/tmp/file24
/tmp/file3
/tmp/file6
/tmp/file9

つまり、簡単に言うと、ソリューションは次のように機能します。

setsすべてのファイルに対するサブシェルの位置、およびそれぞれの

setsループする各ファイルの各行の最初の文字に対するネストされたサブシェルの位置。

[ tests ] if $1否定$2一致を示し、そうであれば

echoesファイル名次にbreaks現在のループ反復

else shifts再試行する次の1文字の位置に

1
mikeserv

このスクリプトは、grepおよびcutを使用して一致する行の行番号を取得し、2つの連続する番号があるかどうかを確認します。ファイルは、スクリプトの最初の引数として渡された有効なファイル名と見なされます。

#!/bin/bash

checkfile () {
 echo checking $1
 grep -n -E "^C.*$" $1 | cut -d: -f1 | while read linenum
     do
        : $[ ++PRV ] 
        if [ $linenum == $PRV ]; then return 1; fi
        PRV=$linenum
     done
     return 0
}

PRV="-1"
checkfile $1
if [ $? == 0 ]; then
   echo Consecutive matching lines found in file $1
fi
0