bash temukan garis yang dimulai dengan string

10

Saya memiliki banyak file dan saya ingin menemukan yang berisi garis berurutan yang dimulai dengan string tertentu.

Misalnya untuk file berikut:

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

Ada lebih dari satu baris yang dimulai dengan 'C', jadi saya ingin file ini ditemukan dengan perintah.
Misalnya untuk file berikut:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

Selalu ada satu baris yang dimulai dengan 'C', saya tidak ingin file ini. Saya berpikir untuk menggunakan a grepatau a sedtetapi saya tidak tahu persis bagaimana melakukannya. Mungkin menggunakan regexp ^C.*$^Catau sesuatu seperti itu. Ada ide ?

Jérémie
sumber
Ada dua baris yang dimulai dengan Ccontoh kedua Anda.
cuonglm
5
Pertanyaan ini tidak jelas. Apakah Anda mencari file yang memiliki lebih dari satu baris berturut-turut dimulai C?
Graeme
Ya inilah yang saya inginkan. Maaf atas kesalahpahaman ini.
Jérémie
2
@terdon, sepertinya pencarian multi-line dengan -P bekerja sampai 2.5.4 dan tidak lagi setelah itu, meskipun saya tidak dapat menemukan apa pun di changelog yang akan menjelaskan alasannya.
Stéphane Chazelas
1
@Graeme Anda mungkin ingin membatalkan penghapusan jawaban Anda, lihat komentar Stephane, tampaknya itu berfungsi untuk beberapa grepversi yang lebih lama .
terdon

Jawaban:

5

Dengan 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}' {} +

(meskipun itu berarti membaca semua file sepenuhnya dengan awkimplementasi yang tidak mendukung nextfile).


Dengan versi GNU grephingga 2.5.4:

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

tampaknya berfungsi, tetapi tidak disengaja dan tidak dijamin berfungsi.

Sebelum diperbaiki di 2.6 (dengan komit ini ), GNU greptelah mengabaikan bahwa fungsi pencarian pcre yang digunakannya akan cocok dengan seluruh buffer yang saat ini diproses grep, menyebabkan segala macam perilaku mengejutkan. Contohnya:

grep -P 'a\s*b'

akan cocok dengan file yang berisi:

bla
bla

Ini cocok dengan:

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

Tapi ini:

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

Atau:

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

tidak mau (karena 1\n2\nmelintasi dua buffer diproses oleh grep).

Perilaku itu akhirnya didokumentasikan:

15- Bagaimana saya bisa mencocokkan antar garis?

Grep standar tidak dapat melakukan ini, karena pada dasarnya berbasis garis. Karenanya, hanya menggunakan kelas karakter '[: spasi:]' tidak cocok dengan baris baru seperti yang Anda harapkan. Namun, jika grep Anda dikompilasi dengan pola Perl diaktifkan, pengubah Perl (yang membuat '.' Cocok dengan baris baru) dapat digunakan:

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

Setelah diperbaiki di 2.6, dokumentasi tidak diubah (saya pernah melaporkannya di sana ).

Stéphane Chazelas
sumber
Apakah ada alasan untuk tidak menggunakan exitdan -exec \;bukannya nextfile?
terdon
@terdon, itu berarti menjalankan satu awkper file. Anda ingin melakukannya hanya jika Anda awktidak mendukung nextfiledan Anda memiliki sebagian besar file yang besar dan memiliki garis yang cocok pada awal file.
Stéphane Chazelas
Bagaimana dengan teknik grep ini (saya kira dengan versi GNU grep yang lebih baru) yang memfasilitasi kecocokan multiline dengan membuat keseluruhan file terlihat seperti string tunggal dengan mengatur line terminator ke NUL - akankah Anda sadar jika ada batasan untuk itu?
iruvar
1
@ 1_CR, Itu akan memuat seluruh file dalam memori jika tidak ada karakter NUL di sana dan yang mengasumsikan garis tidak mengandung karakter NUL. Juga mencatat bahwa versi GNU grep (yang OP memiliki) tidak dapat menggunakan -zdengan -P. Tidak ada \Ntanpa -P, Anda harus menulisnya $'[\01-\011\013-\0377]'yang hanya akan berfungsi di C locales (lihat thread.gmane.org/gmane.comp.gnu.grep.bugs/5187 )
Stéphane Chazelas
@StephaneChazelas, detail yang sangat berguna, terima kasih
iruvar
2

Dengan awk:

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

Ini akan mencetak konten file jika ada baris berurutan yang dimulai dengan a C. Ekspresi (p ~ /^C/ && $1 ~ /^C/)akan melihat baris yang berurutan dalam file dan akan mengevaluasi true jika karakter pertama di kedua cocok C. Jika itu masalahnya, garis akan dicetak.

Untuk menemukan semua file yang memiliki pola seperti itu, Anda dapat menjalankan awk di atas melalui findperintah:

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

Dalam perintah ini, find+ execakan melalui masing-masing file dan melakukan awkpenyaringan yang sama pada setiap file dan mencetak namanya melalui FILENAMEjika ekspresi awk dievaluasi menjadi true. Untuk menghindari pencetakan FILENAMEbeberapa kali untuk satu file dengan banyak kecocokan, exitpernyataan tersebut digunakan (terima kasih @terdon).

mkc
sumber
Pertanyaan saya tidak cukup jelas, saya ingin tahu nama file dengan lebih dari satu baris berturut-turut dimulai denganC
Jérémie
@ Jérémie saya memperbarui jawaban saya.
mkc
Bisakah Anda menambahkan penjelasan tentang cara kerjanya? Juga, tidak perlu flag, hanya exitsaja. Dengan begitu, Anda tidak perlu terus memproses file setelah kecocokan ditemukan.
terdon
2

Opsi lain dengan GNU sed:

Untuk satu file:

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

(meskipun itu juga akan melaporkan file yang tidak dapat dibaca).

Untuk find:

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

Masalah dengan file yang tidak dapat dibaca yang sedang dicetak dapat dihindari dengan menuliskannya:

find . -type f -size +2c -exec sed -n '$q1;/^C/{n;/^C/q}' {} \; -print
buru-buru
sumber
Bisakah Anda memerinci sed -n '$q1;/^C/{n;/^C/q}'?
Jérémie
Adakah yang menjelaskan saya?
Jérémie
@ Jérémie $q1- memaksa sed untuk berhenti dengan kesalahan jika pola tidak ditemukan. Itu juga akan selesai dengan kesalahan jika ada sesuatu yang salah dengan file (itu tidak dapat dibaca atau rusak). Jadi ia akan keluar dengan status keluar 0 hanya jika ditemukan pola dan akan diteruskan untuk dicetak. Bagian dengan /^C/{n;/^C/qini cukup sederhana. Jika ia menemukan string yang dimulai dengan C, ia akan membaca baris berikutnya dan jika itu juga dimulai dengan C, ia akan berhenti dengan status keluar nol.
buru
1

Dengan asumsi file Anda cukup kecil untuk dibaca ke dalam memori:

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

Penjelasan:

  • - 000: ditetapkan \n\nsebagai pemisah rekaman, ini mengaktifkan mode paragraf yang akan memperlakukan paragraf (dipisahkan oleh baris baru berurutan) sebagai baris tunggal.
  • -ne: terapkan skrip yang diberikan sebagai argumen -euntuk setiap baris file input.
  • $ARGV : adalah file yang sedang diproses
  • /^C[^\n]*\nC/: cocokkan Cdi awal baris (lihat deskripsi smpengubah di bawah ini untuk alasan mengapa ini bekerja di sini) diikuti oleh 0 atau lebih karakter non-baris baru, baris baru dan kemudian C. lainnya Dengan kata lain, cari baris berturut-turut dimulai dengan C. * //sm: pengubah pertandingan ini (seperti yang didokumentasikan [di sini]):

    • m : Perlakukan string sebagai beberapa baris. Yaitu, ubah "^" dan "$" dari mencocokkan awal atau akhir baris hanya di ujung kiri dan kanan string untuk mencocokkan mereka di mana saja dalam string.

    • s : Perlakukan string sebagai satu baris. Artinya, ubah "." untuk mencocokkan karakter apa pun, bahkan baris baru, yang biasanya tidak akan cocok.

Anda juga bisa melakukan sesuatu yang jelek seperti:

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

Di sini, perlkode menggantikan baris baru dengan %%begitu, dengan asumsi Anda tidak memiliki %%dalam file input Anda (besar jika tentu saja), grepakan cocok dengan baris berturut-turut dimulai dengan C.

terdon
sumber
1

LARUTAN:

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

DEMO:

Pertama, kami akan membuat basis tes:

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

Di atas membuat 26 file /tmpbernama file1-26. Di setiap file ada 27 atau 28 baris dimulai dengan huruf a-zdan diikuti oleh sisa alfabet. Setiap file ke-3 berisi dua baris berturut-turut di mana karakter pertama diduplikasi.

SAMPEL:

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

Dan ketika saya berubah:

set -- *files

untuk:

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

Saya mendapat...

KELUARAN:

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

Jadi, secara singkat, solusinya bekerja seperti ini:

sets positionals subkulit untuk semua file Anda, dan untuk setiap

sets posisi subshell bersarang untuk huruf pertama dari setiap baris dalam setiap file saat loop.

[ tests ]jika $1meniadakan $2menunjukkan kecocokan, dan jika demikian

echoesnama file kemudian breaks iterasi loop saat ini

lain shifts ke posisi karakter tunggal berikutnya untuk mencoba lagi

mikeserv
sumber
0

Script ini menggunakan grepdan cutuntuk mendapatkan nomor baris dari baris yang cocok, dan memeriksa dua nomor berurutan. File diasumsikan nama file yang valid diberikan sebagai argumen pertama ke skrip:

#!/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
Michael Martinez
sumber